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:
parent
21cddc0a05
commit
fce965c0c8
@ -11,4 +11,5 @@ prompt commands or more complicated privilege escalation or persistence methods.
|
||||
:caption: Contents
|
||||
|
||||
commandparser.rst
|
||||
privesc.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
|
||||
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
|
||||
-----------------
|
||||
|
||||
.. autoclass:: pwncat.remote.victim.Victim
|
||||
:members:
|
||||
|
||||
Remote Service Object
|
||||
---------------------
|
||||
|
||||
.. autoclass:: pwncat.remote.service.RemoteService
|
||||
:members:
|
||||
|
@ -19,15 +19,30 @@ class PrivescError(Exception):
|
||||
|
||||
@dataclass
|
||||
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
|
||||
user: str
|
||||
""" The user this technique provides access as """
|
||||
# The method that will be used
|
||||
method: "Method"
|
||||
""" The method which this technique is associated with """
|
||||
# The unique identifier for this method (can be anything, specific to the
|
||||
# method)
|
||||
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
|
||||
capabilities: Capability
|
||||
""" The GTFOBins capabilities this technique provides. """
|
||||
|
||||
def __str__(self):
|
||||
cap_names = {
|
||||
@ -42,10 +57,25 @@ class Technique:
|
||||
|
||||
|
||||
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
|
||||
name = "unknown"
|
||||
""" Name of this method """
|
||||
BINARIES = []
|
||||
""" List of binaries to verify presence in the default ``check`` method """
|
||||
|
||||
@classmethod
|
||||
def check(cls, pty: "pwncat.pty.PtyHandler") -> bool:
|
||||
@ -58,24 +88,63 @@ class Method:
|
||||
self.pty = pty
|
||||
|
||||
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")
|
||||
|
||||
def execute(self, technique: Technique):
|
||||
""" Execute the given technique to move laterally to the given user.
|
||||
Raise a PrivescError if there was a problem. """
|
||||
def execute(self, technique: Technique) -> bytes:
|
||||
"""
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
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)
|
||||
|
||||
def __str__(self):
|
||||
|
@ -16,6 +16,18 @@ class ServiceState(Enum):
|
||||
|
||||
|
||||
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):
|
||||
self.name: str = name
|
||||
self.user: bool = user
|
||||
@ -24,6 +36,13 @@ class RemoteService:
|
||||
|
||||
@classmethod
|
||||
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
|
||||
|
||||
def start(self):
|
||||
@ -39,13 +58,14 @@ class RemoteService:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def stopped(self):
|
||||
def stopped(self) -> bool:
|
||||
""" Check if the service is stopped """
|
||||
return not self.running
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
""" Check if the service is enabled at boot """
|
||||
def enabled(self) -> bool:
|
||||
""" Check if the service is enabled at boot. The setter will attempt to
|
||||
enable or disable this service for auto-start. """
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user