mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-24 01:25:37 +01:00
Added initial privilege escalation api documentation
This commit is contained in:
parent
21cddc0a05
commit
fce965c0c8
@ -11,4 +11,5 @@ prompt commands or more complicated privilege escalation or persistence methods.
|
|||||||
:caption: Contents
|
:caption: Contents
|
||||||
|
|
||||||
commandparser.rst
|
commandparser.rst
|
||||||
|
privesc.rst
|
||||||
victim.rst
|
victim.rst
|
||||||
|
100
docs/source/api/privesc.rst
Normal file
100
docs/source/api/privesc.rst
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
Privilege Escalation Modules
|
||||||
|
============================
|
||||||
|
|
||||||
|
Privilege escalation in ``pwncat`` is implemented using a pluggable privilege escalation framework
|
||||||
|
which allows new methods to be easily implemented and integrated into ``pwncat``. All privilege
|
||||||
|
escalation methods inherit from the ``pwncat.privesc.base.Method`` class and are implemented under
|
||||||
|
the ``pwncat/privesc`` directory.
|
||||||
|
|
||||||
|
Methods vs Techniques
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Privelege escalation methods may implement multiple techniques. Techniques represent a single action
|
||||||
|
which a specific privilege escalation method can perform. Each technique is identified by it's method,
|
||||||
|
the user which the action can be performed as, a Capability and some method specific data.
|
||||||
|
|
||||||
|
Capabilities are one of ``READ``, ``WRITE`` or ``SHELL`` and are specified with the
|
||||||
|
``pwncat.gtfobins.Capability`` flags. Each technique must specify one and only one capability.
|
||||||
|
|
||||||
|
Privilege escalation is implemented by iterating over all known methods and enumerating all techniques.
|
||||||
|
After techniques are gathered, ``pwncat`` attempts to put the different file read, write or shell
|
||||||
|
techniques together to perform some action. For example, it might use a shell technique to.. well...
|
||||||
|
get a shell. However, ``pwncat`` may also attempt to read a file with a shell technique or gain a
|
||||||
|
shell with a file read technique. The individual privilege escalation methods do not need to worry
|
||||||
|
about this, though. They only need to enumerate all available techniques and implement the
|
||||||
|
associated execution methods for those techniques.
|
||||||
|
|
||||||
|
Implementing a Privilege Escalation Method
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
Privilege escalation methods normally take the form of common vulnerabilities or misconfigurations
|
||||||
|
in the target host. For example, there are built-in privesc methods for SUID binaries, sudo privileges
|
||||||
|
and a few common vulnerabilities. Each method implements up to five different class methods.
|
||||||
|
|
||||||
|
The first method is the ``check`` method. This is a ``classmethod`` which simply tests to make sure
|
||||||
|
that the dependencies of this privesc method are available. It should check that the required
|
||||||
|
binaries, packages or libraries associated with this escalation are available. By default, the base
|
||||||
|
class will check that all binaries specified in the class variable ``BINARIES`` are present on the
|
||||||
|
remote system. If anything is missing from the remote system rendering this method unusable, the check
|
||||||
|
method should raise a ``PrivescError`` exception with a description of what is missing.
|
||||||
|
|
||||||
|
The next method is the ``enumerate`` method. This function returns a list of ``pwncat.privesc.base.Technique``
|
||||||
|
objects, each describing a technique which this method is capable of performing on the remote host.
|
||||||
|
For example, the SUID method iterates over all known SUID binaries and checks for file write, file
|
||||||
|
read or shell capabilities with GTFObins. It returns techniques which overlap with the capabilities
|
||||||
|
requested:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def enumerate(self, caps: Capability = Capability.ALL) -> List[Technique]:
|
||||||
|
""" Find all techniques known at this time """
|
||||||
|
|
||||||
|
# Update the cache for the current user
|
||||||
|
self.find_suid()
|
||||||
|
|
||||||
|
known_techniques = []
|
||||||
|
for suid in pwncat.victim.host.suid:
|
||||||
|
try:
|
||||||
|
binary = pwncat.victim.gtfo.find_binary(suid.path, caps)
|
||||||
|
except BinaryNotFound:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for method in binary.iter_methods(suid.path, caps, Stream.ANY):
|
||||||
|
known_techniques.append(
|
||||||
|
Technique(suid.owner.name, self, method, method.cap)
|
||||||
|
)
|
||||||
|
|
||||||
|
return known_techniques
|
||||||
|
|
||||||
|
The last three methods all take a parameter of a ``Technique`` object. This ``Technique`` will
|
||||||
|
be one of the techniques returned from ``enumerate`` by this method. They implement the three
|
||||||
|
capabilities which are possible. The first is the ``execute`` method. This method is used to
|
||||||
|
escalate privileges and gain a shell as the user specified in the technique. This type of
|
||||||
|
technique is returned, for example, from a SUID ``/bin/bash``, because we are able to directly
|
||||||
|
gain a shell as the owning user. It should perform the escalation and return with the remote
|
||||||
|
host currently at a prompt for the new user. If there are any issues or errors, it will raise a
|
||||||
|
``PrivescError`` with the description of the problem. The return value of this function is a
|
||||||
|
bytes object which can exit the terminal and return to the previous user. In a simple case, this
|
||||||
|
could be just "exit". In a more complicated case, like getting a shell from within ``vim``, this
|
||||||
|
may include control sequences to exit the shell and the containing application.
|
||||||
|
|
||||||
|
Next, methods can implement the ``read_file`` function. This function returns a file-like object
|
||||||
|
used to read data from a remote file as the user specified in the technique. This is possible,
|
||||||
|
for example in situations where a binary such as ``cat`` is SUID. Again, if there is an issue,
|
||||||
|
a ``PrivescError`` is raised.
|
||||||
|
|
||||||
|
The last method which may be implemented is the ``write_file`` method. This method will write
|
||||||
|
the given data to a file as the user specified in the technique. The method does not return
|
||||||
|
any data and should simply write the requested data using the technique specified.
|
||||||
|
|
||||||
|
Privilege Escalation Method Class
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
.. autoclass:: pwncat.privesc.base.Method
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Technique Class
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. autoclass:: pwncat.privesc.base.Technique
|
||||||
|
:members:
|
@ -94,9 +94,84 @@ which required a length argument. This is important because transfer of raw bina
|
|||||||
the output length to be known. If the length is not passed, the data will be automatically encoded (for
|
the output length to be known. If the length is not passed, the data will be automatically encoded (for
|
||||||
example, with base64) before uploading, and decoded automatically on the receiving end.
|
example, with base64) before uploading, and decoded automatically on the receiving end.
|
||||||
|
|
||||||
|
Working with remote services
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
``pwncat`` will attempt to figure out what type of init system is being used on the target host and provide
|
||||||
|
an abstracted interface to system services. The abstractions are available under the ``pwncat/remote/service.py`` file.
|
||||||
|
Currently, ``pwncat`` only supports SystemD, but the interface is abstracted to support other init systems
|
||||||
|
such as SysVInit or Upstart if the interface is implemented.
|
||||||
|
|
||||||
|
The ``pwncat.remote.service.service_map`` maps names of init systems to their abstract ``RemoteService``
|
||||||
|
class implementation. This is how ``pwncat`` selects the appropriate remote service backend.
|
||||||
|
|
||||||
|
Regardless of the underlying init system, ``pwncat`` provides methods for querying known services, enabling
|
||||||
|
auto-start, starting, stopping and creation of remote services.
|
||||||
|
|
||||||
|
To query a list of remote services, you can use the ``pwncat.victim.services`` property. This is an iterator
|
||||||
|
yielding each abstracted service object. Each object contains a name, description, and state as well as
|
||||||
|
methods for starting, stopping, enabling or disabling the service. This functionality obviously depends
|
||||||
|
on you having the correct permission to manage the services, however retrieve the state and list of
|
||||||
|
services should work regardless of your permission level.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pwncat import victim
|
||||||
|
|
||||||
|
for service in victim.services:
|
||||||
|
print(f"{service.name} is {'running' if service.running else 'stopped'}")
|
||||||
|
|
||||||
|
To find a specific service by name, there is a ``find_service`` method which returns an individual
|
||||||
|
remote service object. If the service is not found, a ValueError is raised.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pwncat import victim
|
||||||
|
|
||||||
|
sshd = victim.find_service("sshd")
|
||||||
|
|
||||||
|
|
||||||
|
The interface for creating services is provided through the ``create_service`` method, which allows
|
||||||
|
you to specify a target binary name which serves as the entrypoint for your service as well as a name
|
||||||
|
description, and enabled state. A ``PermissionError`` is raised if you do not have permission to create
|
||||||
|
the specified service. This method also returns a wrapped ``RemoteService`` object for the newly
|
||||||
|
created service.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pwncat import victim
|
||||||
|
|
||||||
|
pwncat = victim.create_service(name="pwncat",
|
||||||
|
description="a malicious service",
|
||||||
|
target="/usr/bin/pwncat_service",
|
||||||
|
runas="root",
|
||||||
|
enable=True,
|
||||||
|
user=False)
|
||||||
|
pwncat.start()
|
||||||
|
|
||||||
|
Starting, stopping or enabling a service is as easy as calling a method or setting a property:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pwncat import victim
|
||||||
|
|
||||||
|
try:
|
||||||
|
sshd = victim.find_service("sshd")
|
||||||
|
sshd.enabled = False
|
||||||
|
sshd.stop()
|
||||||
|
except PermissionError:
|
||||||
|
print("you don't have permission to modify sshd :(")
|
||||||
|
except ValueError:
|
||||||
|
print("sshd doesn't exist!")
|
||||||
|
|
||||||
The Victim Object
|
The Victim Object
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
.. autoclass:: pwncat.remote.victim.Victim
|
.. autoclass:: pwncat.remote.victim.Victim
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
Remote Service Object
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. autoclass:: pwncat.remote.service.RemoteService
|
||||||
|
:members:
|
||||||
|
@ -19,15 +19,30 @@ class PrivescError(Exception):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Technique:
|
class Technique:
|
||||||
|
"""
|
||||||
|
An individual technique which was found to be possible by a privilege escalation
|
||||||
|
method.
|
||||||
|
|
||||||
|
:param user: the user this technique provides access as
|
||||||
|
:param method: the method this technique is associated with
|
||||||
|
:param ident: method-specific identifier
|
||||||
|
:param capabilities: a GTFObins capability this technique provides
|
||||||
|
"""
|
||||||
|
|
||||||
# The user that this technique will move to
|
# The user that this technique will move to
|
||||||
user: str
|
user: str
|
||||||
|
""" The user this technique provides access as """
|
||||||
# The method that will be used
|
# The method that will be used
|
||||||
method: "Method"
|
method: "Method"
|
||||||
|
""" The method which this technique is associated with """
|
||||||
# The unique identifier for this method (can be anything, specific to the
|
# The unique identifier for this method (can be anything, specific to the
|
||||||
# method)
|
# method)
|
||||||
ident: Any
|
ident: Any
|
||||||
|
""" Method specific identifier. This can be anything the method needs
|
||||||
|
to identify this specific technique. It can also be unused. """
|
||||||
# The GTFObins capabilities required for this technique to work
|
# The GTFObins capabilities required for this technique to work
|
||||||
capabilities: Capability
|
capabilities: Capability
|
||||||
|
""" The GTFOBins capabilities this technique provides. """
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
cap_names = {
|
cap_names = {
|
||||||
@ -42,10 +57,25 @@ class Technique:
|
|||||||
|
|
||||||
|
|
||||||
class Method:
|
class Method:
|
||||||
|
"""
|
||||||
|
Generic privilege escalation method. You must implement at a minimum the enumerate
|
||||||
|
method. Also, for any capabilities which you are capable of generating techniques for,
|
||||||
|
you must implement the corresponding methods:
|
||||||
|
|
||||||
|
* ``Capability.SHELL`` - ``execute``
|
||||||
|
* ``Capability.READ`` - ``read_file``
|
||||||
|
* ``Capability.WRITE`` - ``write_file``
|
||||||
|
|
||||||
|
Further, you can also implement the ``check`` class method to verify applicability of
|
||||||
|
this method to the remote victim and the ``get_name`` method to generate a printable
|
||||||
|
representation of a given technique for this method (as seen in ``privesc`` output).
|
||||||
|
"""
|
||||||
|
|
||||||
# Binaries which are needed on the remote host for this privesc
|
# Binaries which are needed on the remote host for this privesc
|
||||||
name = "unknown"
|
name = "unknown"
|
||||||
|
""" Name of this method """
|
||||||
BINARIES = []
|
BINARIES = []
|
||||||
|
""" List of binaries to verify presence in the default ``check`` method """
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def check(cls, pty: "pwncat.pty.PtyHandler") -> bool:
|
def check(cls, pty: "pwncat.pty.PtyHandler") -> bool:
|
||||||
@ -58,24 +88,63 @@ class Method:
|
|||||||
self.pty = pty
|
self.pty = pty
|
||||||
|
|
||||||
def enumerate(self, capability: int = Capability.ALL) -> List[Technique]:
|
def enumerate(self, capability: int = Capability.ALL) -> List[Technique]:
|
||||||
""" Enumerate all possible escalations to the given users """
|
"""
|
||||||
|
Enumerate all possible techniques known and possible on the remote host for
|
||||||
|
this method. This should only enumerate techniques with overlapping capabilities
|
||||||
|
as specified by the ``capability`` parameter.
|
||||||
|
|
||||||
|
:param capability: the requested capabilities to enumerate
|
||||||
|
:return: A list of potentially working techniques
|
||||||
|
"""
|
||||||
raise NotImplementedError("no enumerate method implemented")
|
raise NotImplementedError("no enumerate method implemented")
|
||||||
|
|
||||||
def execute(self, technique: Technique):
|
def execute(self, technique: Technique) -> bytes:
|
||||||
""" Execute the given technique to move laterally to the given user.
|
"""
|
||||||
Raise a PrivescError if there was a problem. """
|
Execute the given technique to gain a shell. This is only called for techniques
|
||||||
|
providing the Capability.SHELL capability. If there is a problem with escalation,
|
||||||
|
the shell should be returned to normal and a ``PrivescError`` should be raised.
|
||||||
|
|
||||||
|
:param technique: the technique to execute
|
||||||
|
:return: a bytes object which will exit the new shell
|
||||||
|
"""
|
||||||
raise NotImplementedError("no execute method implemented")
|
raise NotImplementedError("no execute method implemented")
|
||||||
|
|
||||||
def read_file(self, filename: str, technique: Technique) -> RemoteBinaryPipe:
|
def read_file(self, filename: str, technique: Technique) -> RemoteBinaryPipe:
|
||||||
""" Execute a read_file action with the given technique and return a
|
"""
|
||||||
remote file pipe which will yield the file contents. """
|
Open the given file for reading and return a file-like object, as the user
|
||||||
|
specified in the technique. This is only called for techniques providing the
|
||||||
|
Capability.READ capability. If an error occurs, a ``PrivescError`` should be
|
||||||
|
raised with a description of the problem.
|
||||||
|
|
||||||
|
:param filename: path to the remote file
|
||||||
|
:param technique: the technique to utilize
|
||||||
|
:return: Binary file-like object representing the remote file
|
||||||
|
"""
|
||||||
raise NotImplementedError("no read_file implementation")
|
raise NotImplementedError("no read_file implementation")
|
||||||
|
|
||||||
def write_file(self, filename: str, data: bytes, technique: Technique):
|
def write_file(self, filename: str, data: bytes, technique: Technique):
|
||||||
""" Execute a write_file action with the given technique. """
|
"""
|
||||||
|
Write the data to the given filename on the remote host as the user
|
||||||
|
specified in the technique. This is only called for techniques providing the
|
||||||
|
Capability.WRITE capability. If an error occurs, ``PrivescError`` should
|
||||||
|
be raised with a description of the problem.
|
||||||
|
|
||||||
|
This will overwrite the remote file if it exists!
|
||||||
|
|
||||||
|
:param filename: the remote file name to write
|
||||||
|
:param data: the data to write
|
||||||
|
:param technique: the technique to user
|
||||||
|
"""
|
||||||
raise NotImplementedError("no write_file implementation")
|
raise NotImplementedError("no write_file implementation")
|
||||||
|
|
||||||
def get_name(self, tech: Technique):
|
def get_name(self, tech: Technique) -> str:
|
||||||
|
"""
|
||||||
|
Generate a human-readable and formatted name for this method/technique
|
||||||
|
combination.
|
||||||
|
|
||||||
|
:param tech: a technique applicable to this object
|
||||||
|
:return: a formatted string
|
||||||
|
"""
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -16,6 +16,18 @@ class ServiceState(Enum):
|
|||||||
|
|
||||||
|
|
||||||
class RemoteService:
|
class RemoteService:
|
||||||
|
"""
|
||||||
|
Abstract service interface. Interfaces for specific init systems are implemented as
|
||||||
|
a subclass of the RemoteService class. The class methods defined here should be
|
||||||
|
redefined to access and enumerate the underlying init system.
|
||||||
|
|
||||||
|
:param name: the service name
|
||||||
|
:param user: whether this service is a user specific service
|
||||||
|
:param running: whether this service is currently running
|
||||||
|
:param description: a long description for this service
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, name: str, running: bool, description: str, user: bool = False):
|
def __init__(self, name: str, running: bool, description: str, user: bool = False):
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
self.user: bool = user
|
self.user: bool = user
|
||||||
@ -24,6 +36,13 @@ class RemoteService:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def enumerate(cls, user: bool = False) -> Iterator["RemoteService"]:
|
def enumerate(cls, user: bool = False) -> Iterator["RemoteService"]:
|
||||||
|
"""
|
||||||
|
Enumerate installed services on the remote host. This is overloaded for a
|
||||||
|
specific init system.
|
||||||
|
|
||||||
|
:param user: whether to enumerate user specific services
|
||||||
|
:return: An iterator for remote service objects
|
||||||
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
@ -39,13 +58,14 @@ class RemoteService:
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stopped(self):
|
def stopped(self) -> bool:
|
||||||
""" Check if the service is stopped """
|
""" Check if the service is stopped """
|
||||||
return not self.running
|
return not self.running
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enabled(self):
|
def enabled(self) -> bool:
|
||||||
""" Check if the service is enabled at boot """
|
""" Check if the service is enabled at boot. The setter will attempt to
|
||||||
|
enable or disable this service for auto-start. """
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user