diff --git a/pwncat/__main__.py b/pwncat/__main__.py index 53590ec..08f106b 100644 --- a/pwncat/__main__.py +++ b/pwncat/__main__.py @@ -39,6 +39,12 @@ def main(): default=None, help="Custom configuration file (default: ./pwncatrc)", ) + parser.add_argument( + "--certificate", + "--cert", + default=None, + help="Certificate for SSL-encrypted listeners", + ) parser.add_argument( "--identity", "-i", @@ -149,27 +155,47 @@ def main(): or args.listen or args.identity is not None ): - protocol = None - user = None - password = None - host = None - port = None + query_args = {} + query_args["protocol"] = None + query_args["user"] = None + query_args["password"] = None + query_args["host"] = None + query_args["port"] = None + query_args["platform"] = args.platform + query_args["identity"] = args.identity + query_args["certfile"] = args.certificate + query_args["keyfile"] = args.certificate + querystring = None if args.connection_string: m = connect.Command.CONNECTION_PATTERN.match(args.connection_string) - protocol = m.group("protocol") - user = m.group("user") - password = m.group("password") - host = m.group("host") - port = m.group("port") + query_args["protocol"] = m.group("protocol") + query_args["user"] = m.group("user") + query_args["password"] = m.group("password") + query_args["host"] = m.group("host") + query_args["port"] = m.group("port") + querystring = m.group("querystring") - if protocol is not None: - protocol = protocol.removesuffix("://") + if query_args["protocol"] is not None: + query_args["protocol"] = query_args["protocol"].removesuffix("://") - if host is not None and host == "": - host = None + if querystring is not None: + for arg in querystring.split("&"): + if arg.find("=") == -1: + continue - if protocol is not None and args.listen: + key, *value = arg.split("=") + + if key in query_args and query_args[key] is not None: + console.log(f"[red]error[/red]: multiple values for {key}") + return + + query_args[key] = "=".join(value) + + if query_args["host"] is not None and query_args["host"] == "": + query_args["host"] = None + + if query_args["protocol"] is not None and args.listen: console.log( "[red]error[/red]: --listen is not compatible with an explicit connection string" ) @@ -178,7 +204,7 @@ def main(): if ( sum( [ - port is not None, + query_args["port"] is not None, args.port is not None, args.pos_port is not None, ] @@ -189,22 +215,24 @@ def main(): return if args.port is not None: - port = args.port + query_args["port"] = args.port if args.pos_port is not None: - port = args.pos_port + query_args["port"] = args.pos_port - if port is not None: + if query_args["port"] is not None: try: - port = int(port.lstrip(":")) + query_args["port"] = int(query_args["port"].lstrip(":")) except ValueError: - console.log(f"[red]error[/red]: {port}: invalid port number") + console.log( + f"[red]error[/red]: {query_args['port'].lstrip(':')}: invalid port number" + ) return # Attempt to reconnect via installed implants if ( - protocol is None - and password is None - and port is None + query_args["protocol"] is None + and query_args["password"] is None + and query_args["port"] is None and args.identity is None ): db = manager.db.open() @@ -213,11 +241,14 @@ def main(): # Locate all installed implants for target in db.root.targets: - if target.guid != host and target.public_address[0] != host: + if ( + target.guid != query_args["host"] + and target.public_address[0] != query_args["host"] + ): continue # Collect users - users = {} + userss = {} for fact in target.facts: if "user" in fact.types: users[fact.id] = fact @@ -236,13 +267,13 @@ def main(): ) as progress: task = progress.add_task("", status="...") for target, implant_user, implant in implants: - # Check correct user - if user is not None and implant_user.name != user: + # Check correct query_args["user"] + if query_args["user"] is not None and implant_user.name != user: continue # Check correct platform if ( - args.platform is not None - and target.platform != args.platform + query_args["platform"] is not None + and target.platform != query_args["platform"] ): continue @@ -267,13 +298,7 @@ def main(): else: try: manager.create_session( - platform=args.platform, - protocol=protocol, - user=user, - password=password, - host=host, - port=port, - identity=args.identity, + **query_args, ) except (ChannelError, PlatformError) as exc: manager.log(f"connection failed: {exc}") diff --git a/pwncat/channel/__init__.py b/pwncat/channel/__init__.py index 188d939..7db3b16 100644 --- a/pwncat/channel/__init__.py +++ b/pwncat/channel/__init__.py @@ -581,7 +581,10 @@ def create(protocol: Optional[str] = None, **kwargs) -> Channel: or kwargs["host"] == "0.0.0.0" or kwargs["host"] is None ): - if "certfile" in kwargs or "keyfile" in kwargs: + if ( + kwargs.get("certfile") is not None + or kwargs.get("keyfile") is not None + ): protocols.append("ssl-bind") else: protocols.append("bind") diff --git a/pwncat/channel/bind.py b/pwncat/channel/bind.py index 1a6c678..33921fe 100644 --- a/pwncat/channel/bind.py +++ b/pwncat/channel/bind.py @@ -28,6 +28,12 @@ class Bind(Socket): if not host or host == "": host = "0.0.0.0" + if isinstance(port, str): + try: + port = int(port) + except ValueError: + raise ChannelError(self, "invalid port number") + if port is None: raise ChannelError(self, "no port specified") diff --git a/pwncat/channel/connect.py b/pwncat/channel/connect.py index a40214d..e34b534 100644 --- a/pwncat/channel/connect.py +++ b/pwncat/channel/connect.py @@ -24,10 +24,16 @@ class Connect(Socket): def __init__(self, host: str, port: int, **kwargs): if not host: - raise ChannelError("no host address provided") + raise ChannelError(self, "no host address provided") if port is None: - raise ChannelError("no port provided") + raise ChannelError(self, "no port provided") + + if isinstance(port, str): + try: + port = int(port) + except ValueError: + raise ChannelError(self, "invalid port") with Progress( f"connecting to [blue]{host}[/blue]:[cyan]{port}[/cyan]", diff --git a/pwncat/channel/socket.py b/pwncat/channel/socket.py index 5239bd1..ffa51a2 100644 --- a/pwncat/channel/socket.py +++ b/pwncat/channel/socket.py @@ -49,6 +49,9 @@ class Socket(Channel): def __init__(self, client: socket.socket = None, **kwargs): + if isinstance(client, str): + raise ChannelError(self, f"expected socket object not {repr(type(client))}") + if client is not None: # Report host and port number to base channel host, port = client.getpeername() diff --git a/pwncat/channel/ssh.py b/pwncat/channel/ssh.py index 0937571..4ffb363 100644 --- a/pwncat/channel/ssh.py +++ b/pwncat/channel/ssh.py @@ -33,8 +33,14 @@ class Ssh(Channel): if port is None: port = 22 + if isinstance(port, str): + try: + port = int(port) + except ValueError: + raise ChannelError(self, "invalid port") + if not user or user is None: - raise ChannelError("you must specify a user") + raise ChannelError(self, "you must specify a user") if password is None and identity is None: password = prompt("Password: ", is_password=True) @@ -51,7 +57,7 @@ class Ssh(Channel): t.start_client() except paramiko.SSHException: sock.close() - raise ChannelError("ssh negotiation failed") + raise ChannelError(self, "ssh negotiation failed") if identity is not None: try: @@ -67,23 +73,23 @@ class Ssh(Channel): try: key = paramiko.RSAKey.from_private_key_file(identity, password) except paramiko.ssh_exception.SSHException: - raise ChannelError("invalid private key or passphrase") + raise ChannelError(self, "invalid private key or passphrase") # Attempt authentication try: t.auth_publickey(user, key) except paramiko.ssh_exception.AuthenticationException as exc: - raise ChannelError(str(exc)) + raise ChannelError(self, str(exc)) else: try: t.auth_password(user, password) except paramiko.ssh_exception.AuthenticationException as exc: - raise ChannelError(str(exc)) + raise ChannelError(self, str(exc)) if not t.is_authenticated(): t.close() sock.close() - raise ChannelError("authentication failed") + raise ChannelError(self, "authentication failed") # Open an interactive session chan = t.open_session() diff --git a/pwncat/commands/connect.py b/pwncat/commands/connect.py index cd77bf7..ab7cd84 100644 --- a/pwncat/commands/connect.py +++ b/pwncat/commands/connect.py @@ -58,6 +58,10 @@ class Command(CommandDefinition): action="store_true", help="List installed implants with remote connection capability", ), + "--connection,--conn": Parameter( + Complete.NONE, + help="Certificate for SSL-encrypted listeners", + ), "connection_string": Parameter( Complete.NONE, metavar="[protocol://][user[:password]@][host][:port]", @@ -73,7 +77,7 @@ class Command(CommandDefinition): } LOCAL = True CONNECTION_PATTERN = re.compile( - r"""^(?P[-a-zA-Z0-9_]*://)?((?P[^:@]*)?(?P:(\\@|[^@])*)?@)?(?P[^:]*)?(?P:[0-9]*)?$""" + r"""^(?P[-a-zA-Z0-9_]*://)?((?P[^:@]*)?(?P:(\\@|[^@])*)?@)?(?P[^:]*)?(?P:[0-9]*)?(\?(?P.*))?$""" ) def run(self, manager: "pwncat.manager.Manager", args):