1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-27 19:04:15 +01:00

Identified critical error in RawIOBase implementation

The `ChannelFile` implementation raised a BlockingIOError in certain
circumstances which is against the documented implementation for a
subclass of `RawIOBase`. This was causing odd behaviour like occasional
missing command output (e.g. empty environment variables)
This commit is contained in:
Caleb Stewart 2021-06-14 22:22:42 -04:00
parent 95a6ac98cb
commit 64dcae2f0b
3 changed files with 28 additions and 10 deletions

View File

@ -184,7 +184,7 @@ class ChannelFile(RawIOBase):
n = len(data)
if n == 0 and not self.blocking:
raise BlockingIOError
return None
obj = bytes(b[:n])

View File

@ -21,7 +21,6 @@ import socket
import functools
from typing import Optional
from pwncat.channel import Channel, ChannelError, ChannelClosed

View File

@ -140,9 +140,9 @@ class PopenLinux(pwncat.subprocess.Popen):
if result == b"" and self.stdout_raw.raw.eof:
self._receive_returncode()
return self.returncode
except ValueError:
self._receive_returncode()
return self.returncode
# except ValueError:
# self._receive_returncode()
# return self.returncode
except BlockingIOError:
return None
@ -203,6 +203,12 @@ class PopenLinux(pwncat.subprocess.Popen):
except BlockingIOError:
time.sleep(0.1)
# Check if there's any data left buffered
if self.stdout:
new_data = self.stdout.read()
if new_data is not None:
data += new_data
return (data, empty)
def kill(self):
@ -488,6 +494,7 @@ class Linux(Platform):
PATH_TYPE = pathlib.PurePosixPath
PROMPTS = {
"sh": """'$(command printf "(remote) $(whoami)@$(hostname):$PWD\\$ ")'""",
"dash": """'$(command printf "(remote) $(whoami)@$(hostname):$PWD\\$ ")'""",
"zsh": """'%B%F{red}(remote) %B%F{yellow}%n@%M%B%F{reset}:%B%F{cyan}%(6~.%-1~/…/%4~.%5~)%B%(#.%b%F{white}#.%b%F{white}$)%b%F{reset} '""",
"default": """'$(command printf "\\[\\033[01;31m\\](remote)\\[\\033[0m\\] \\[\\033[01;33m\\]$(whoami)@$(hostname)\\[\\033[0m\\]:\\[\\033[1;36m\\]$PWD\\[\\033[0m\\]\\$ ")'""",
}
@ -546,21 +553,20 @@ class Linux(Platform):
else:
self.has_pty = False
self.shell = self.getenv("SHELL")
if self.shell == "" or self.shell is None:
self.shell = "/bin/sh"
# This doesn't make sense, but happened for some people (see issue #116)
if os.path.basename(self.shell) == "nologin":
if os.path.basename(self.shell) in ["nologin", "false", "sync", "git-shell"]:
self.shell = "/bin/sh"
self.channel.sendline(b" export SHELL=/bin/sh")
if os.path.basename(self.shell) == "sh":
if os.path.basename(self.shell) in ["sh", "dash"]:
# Try to find a better shell
bash = self._do_which("bash")
if bash is not None:
self.session.log(f"upgrading from {self.shell} to {bash}")
self.shell = bash
self.session.log(f"upgrading from sh to {self.shell}")
self.channel.sendline(f"exec {self.shell}".encode("utf-8"))
time.sleep(0.5)
@ -1035,6 +1041,8 @@ class Linux(Platform):
command += f" 2>{stderr}"
elif stderr == pwncat.subprocess.DEVNULL:
command += " 2>/dev/null"
elif stderr == pwncat.subprocess.PIPE:
command += " 2>&1"
if isinstance(stdin, str):
command += f" 0<{stdin}"
@ -1518,6 +1526,17 @@ class Linux(Platform):
self.logger.info(command.rstrip("\n"))
self.channel.send(command.encode("utf-8"))
self.channel.drain()
self._interactive = False
# Update self.shell just in case the user changed shells
try:
# Get the PID of the running shell
pid = self.getenv("$")
# Grab the path to the executable representing the shell
self.shell = self.Path("/proc", pid, "exe").readlink()
except (FileNotFoundError, PermissionError):
# Fall back to SHELL even though it's not really trustworthy
self.shell = self.getenv("SHELL")
else:
# Going interactive requires a pty
@ -1556,7 +1575,7 @@ class Linux(Platform):
except pwncat.channel.ChannelTimeout:
pass
self._interactive = value
self._interactive = True
def whoami(self):
"""Get the name of the current user"""