diff --git a/CHANGELOG.md b/CHANGELOG.md index 405e16a..817bd49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and simply didn't have the time to go back and retroactively create one. ### Fixed - Possible exception due to _pre-registering_ of `session` with `manager` +- Covered edge case in sudo rule parsing for wildcards ([#183](https://github.com/calebstewart/pwncat/issue/183)) +- Added fallthrough cases for PTY methods in case of misbehaving binaries (looking at you: `screen`) - Fixed handling of `socket.getpeername` when `Socket` channel uses IPv6 ([#159](https://github.com/calebstewart/pwncat/issues/159)). ### Added - Added alternatives to `bash` to be used during _shell upgrade_ for a _better shell_ diff --git a/pwncat/gtfobins.py b/pwncat/gtfobins.py index 1c56919..603784b 100644 --- a/pwncat/gtfobins.py +++ b/pwncat/gtfobins.py @@ -121,6 +121,9 @@ class Method: elif spec[-1] == "*": has_wildcard = True command = spec.rstrip("*") + else: + has_wildcard = False + command = spec # The sudo command is just "/path/to/binary", we are allowed to add any # parameters we want. diff --git a/pwncat/platform/linux.py b/pwncat/platform/linux.py index c9c45e7..a5cc5f1 100644 --- a/pwncat/platform/linux.py +++ b/pwncat/platform/linux.py @@ -642,20 +642,9 @@ class Linux(Platform): """Spawn a PTY in the current shell. If a PTY is already running then this method does nothing.""" - # Check if we are currently in a PTY - if self.has_pty: - return - - pty_command = None - shell = self.shell - - if pty_command is None: - script_path = self.which("script") - if script_path is not None: - pty_command = f""" exec {script_path} -qc {shell} /dev/null 2>&1\n""" - - if pty_command is None: - python_path = self.which( + PTY_OPTIONS = [ + (["script"], " {binary_path} -qc {shell} /dev/null 2>&1"), + ( [ "python", "python2", @@ -664,38 +653,64 @@ class Linux(Platform): "python3.6", "python3.8", "python3.9", - ] - ) - if python_path is not None: - pty_command = f""" exec {python_path} -c "import pty; pty.spawn('{shell}')" 2>&1\n""" - - if pty_command is not None: - self.logger.info(pty_command.rstrip("\n")) - self.channel.send(pty_command.encode("utf-8")) - - self.has_pty = True - - # Preserve interactivity - if not self.interactive: - self._interactive = True - self.interactive = False - - # When starting a pty, history is sometimes re-enabled - self.disable_history() - - # Ensure that the TTY settings make sense - self.Popen( - [ - "stty", - "400:1:bf:8a33:3:1c:7f:15:4:0:1:0:11:13:1a:0:12:f:17:16:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0", ], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - ).wait() + """ {binary_path} -c "import pty; pty.spawn('{shell}')" 2>&1""", + ), + ] + # Check if we are currently in a PTY + if self.has_pty: return - raise PlatformError("no avialable pty methods") + # Grab the current shell PID + pid = self.getenv("$") + + for binaries, payload_format in PTY_OPTIONS: + for binary in binaries: + binary_path = self.which(binary) + if binary_path is None: + continue + + payload = payload_format.format( + binary_path=binary_path, shell=self.shell + ) + + # Send the payload + self.logger.info(payload) + self.channel.sendline(payload.encode("utf-8")) + + # Preserve interactivity. This has to happen for `getenv` to function + # It should do no harm if the pty method failed + self.has_pty = True + if not self.interactive: + self._interactive = True + self.interactive = False + + # Seemed to work + new_pid = self.getenv("$") + if new_pid != pid: + break + + # We failed, reset the flag + self.has_pty = False + else: + continue + break + else: + raise PlatformError("no avialable pty methods") + + # When starting a pty, history is sometimes re-enabled + self.disable_history() + + # Ensure that the TTY settings make sense + self.Popen( + [ + "stty", + "400:1:bf:8a33:3:1c:7f:15:4:0:1:0:11:13:1a:0:12:f:17:16:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0", + ], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + ).wait() def get_host_hash(self) -> str: """