1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-27 19:04:15 +01:00

Added initial privilege escalation api documentation

This commit is contained in:
Caleb Stewart 2020-05-20 23:00:17 -04:00
parent 21cddc0a05
commit fce965c0c8
5 changed files with 277 additions and 12 deletions

View File

@ -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
View 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:

View File

@ -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:

View File

@ -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):

View File

@ -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