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

Added certificate options for entrypoint

TODO: transfer entrypoint logic to `connect`
This commit is contained in:
Caleb Stewart 2021-06-14 08:35:07 -04:00
parent 0f00871abf
commit 74962f2b2d
7 changed files with 100 additions and 47 deletions

View File

@ -39,6 +39,12 @@ def main():
default=None, default=None,
help="Custom configuration file (default: ./pwncatrc)", help="Custom configuration file (default: ./pwncatrc)",
) )
parser.add_argument(
"--certificate",
"--cert",
default=None,
help="Certificate for SSL-encrypted listeners",
)
parser.add_argument( parser.add_argument(
"--identity", "--identity",
"-i", "-i",
@ -149,27 +155,47 @@ def main():
or args.listen or args.listen
or args.identity is not None or args.identity is not None
): ):
protocol = None query_args = {}
user = None query_args["protocol"] = None
password = None query_args["user"] = None
host = None query_args["password"] = None
port = 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: if args.connection_string:
m = connect.Command.CONNECTION_PATTERN.match(args.connection_string) m = connect.Command.CONNECTION_PATTERN.match(args.connection_string)
protocol = m.group("protocol") query_args["protocol"] = m.group("protocol")
user = m.group("user") query_args["user"] = m.group("user")
password = m.group("password") query_args["password"] = m.group("password")
host = m.group("host") query_args["host"] = m.group("host")
port = m.group("port") query_args["port"] = m.group("port")
querystring = m.group("querystring")
if protocol is not None: if query_args["protocol"] is not None:
protocol = protocol.removesuffix("://") query_args["protocol"] = query_args["protocol"].removesuffix("://")
if host is not None and host == "": if querystring is not None:
host = 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( console.log(
"[red]error[/red]: --listen is not compatible with an explicit connection string" "[red]error[/red]: --listen is not compatible with an explicit connection string"
) )
@ -178,7 +204,7 @@ def main():
if ( if (
sum( sum(
[ [
port is not None, query_args["port"] is not None,
args.port is not None, args.port is not None,
args.pos_port is not None, args.pos_port is not None,
] ]
@ -189,22 +215,24 @@ def main():
return return
if args.port is not None: if args.port is not None:
port = args.port query_args["port"] = args.port
if args.pos_port is not None: 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: try:
port = int(port.lstrip(":")) query_args["port"] = int(query_args["port"].lstrip(":"))
except ValueError: 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 return
# Attempt to reconnect via installed implants # Attempt to reconnect via installed implants
if ( if (
protocol is None query_args["protocol"] is None
and password is None and query_args["password"] is None
and port is None and query_args["port"] is None
and args.identity is None and args.identity is None
): ):
db = manager.db.open() db = manager.db.open()
@ -213,11 +241,14 @@ def main():
# Locate all installed implants # Locate all installed implants
for target in db.root.targets: 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 continue
# Collect users # Collect users
users = {} userss = {}
for fact in target.facts: for fact in target.facts:
if "user" in fact.types: if "user" in fact.types:
users[fact.id] = fact users[fact.id] = fact
@ -236,13 +267,13 @@ def main():
) as progress: ) as progress:
task = progress.add_task("", status="...") task = progress.add_task("", status="...")
for target, implant_user, implant in implants: for target, implant_user, implant in implants:
# Check correct user # Check correct query_args["user"]
if user is not None and implant_user.name != user: if query_args["user"] is not None and implant_user.name != user:
continue continue
# Check correct platform # Check correct platform
if ( if (
args.platform is not None query_args["platform"] is not None
and target.platform != args.platform and target.platform != query_args["platform"]
): ):
continue continue
@ -267,13 +298,7 @@ def main():
else: else:
try: try:
manager.create_session( manager.create_session(
platform=args.platform, **query_args,
protocol=protocol,
user=user,
password=password,
host=host,
port=port,
identity=args.identity,
) )
except (ChannelError, PlatformError) as exc: except (ChannelError, PlatformError) as exc:
manager.log(f"connection failed: {exc}") manager.log(f"connection failed: {exc}")

View File

@ -581,7 +581,10 @@ def create(protocol: Optional[str] = None, **kwargs) -> Channel:
or kwargs["host"] == "0.0.0.0" or kwargs["host"] == "0.0.0.0"
or kwargs["host"] is None 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") protocols.append("ssl-bind")
else: else:
protocols.append("bind") protocols.append("bind")

View File

@ -28,6 +28,12 @@ class Bind(Socket):
if not host or host == "": if not host or host == "":
host = "0.0.0.0" 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: if port is None:
raise ChannelError(self, "no port specified") raise ChannelError(self, "no port specified")

View File

@ -24,10 +24,16 @@ class Connect(Socket):
def __init__(self, host: str, port: int, **kwargs): def __init__(self, host: str, port: int, **kwargs):
if not host: if not host:
raise ChannelError("no host address provided") raise ChannelError(self, "no host address provided")
if port is None: 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( with Progress(
f"connecting to [blue]{host}[/blue]:[cyan]{port}[/cyan]", f"connecting to [blue]{host}[/blue]:[cyan]{port}[/cyan]",

View File

@ -49,6 +49,9 @@ class Socket(Channel):
def __init__(self, client: socket.socket = None, **kwargs): 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: if client is not None:
# Report host and port number to base channel # Report host and port number to base channel
host, port = client.getpeername() host, port = client.getpeername()

View File

@ -33,8 +33,14 @@ class Ssh(Channel):
if port is None: if port is None:
port = 22 port = 22
if isinstance(port, str):
try:
port = int(port)
except ValueError:
raise ChannelError(self, "invalid port")
if not user or user is None: 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: if password is None and identity is None:
password = prompt("Password: ", is_password=True) password = prompt("Password: ", is_password=True)
@ -51,7 +57,7 @@ class Ssh(Channel):
t.start_client() t.start_client()
except paramiko.SSHException: except paramiko.SSHException:
sock.close() sock.close()
raise ChannelError("ssh negotiation failed") raise ChannelError(self, "ssh negotiation failed")
if identity is not None: if identity is not None:
try: try:
@ -67,23 +73,23 @@ class Ssh(Channel):
try: try:
key = paramiko.RSAKey.from_private_key_file(identity, password) key = paramiko.RSAKey.from_private_key_file(identity, password)
except paramiko.ssh_exception.SSHException: except paramiko.ssh_exception.SSHException:
raise ChannelError("invalid private key or passphrase") raise ChannelError(self, "invalid private key or passphrase")
# Attempt authentication # Attempt authentication
try: try:
t.auth_publickey(user, key) t.auth_publickey(user, key)
except paramiko.ssh_exception.AuthenticationException as exc: except paramiko.ssh_exception.AuthenticationException as exc:
raise ChannelError(str(exc)) raise ChannelError(self, str(exc))
else: else:
try: try:
t.auth_password(user, password) t.auth_password(user, password)
except paramiko.ssh_exception.AuthenticationException as exc: except paramiko.ssh_exception.AuthenticationException as exc:
raise ChannelError(str(exc)) raise ChannelError(self, str(exc))
if not t.is_authenticated(): if not t.is_authenticated():
t.close() t.close()
sock.close() sock.close()
raise ChannelError("authentication failed") raise ChannelError(self, "authentication failed")
# Open an interactive session # Open an interactive session
chan = t.open_session() chan = t.open_session()

View File

@ -58,6 +58,10 @@ class Command(CommandDefinition):
action="store_true", action="store_true",
help="List installed implants with remote connection capability", help="List installed implants with remote connection capability",
), ),
"--connection,--conn": Parameter(
Complete.NONE,
help="Certificate for SSL-encrypted listeners",
),
"connection_string": Parameter( "connection_string": Parameter(
Complete.NONE, Complete.NONE,
metavar="[protocol://][user[:password]@][host][:port]", metavar="[protocol://][user[:password]@][host][:port]",
@ -73,7 +77,7 @@ class Command(CommandDefinition):
} }
LOCAL = True LOCAL = True
CONNECTION_PATTERN = re.compile( CONNECTION_PATTERN = re.compile(
r"""^(?P<protocol>[-a-zA-Z0-9_]*://)?((?P<user>[^:@]*)?(?P<password>:(\\@|[^@])*)?@)?(?P<host>[^:]*)?(?P<port>:[0-9]*)?$""" r"""^(?P<protocol>[-a-zA-Z0-9_]*://)?((?P<user>[^:@]*)?(?P<password>:(\\@|[^@])*)?@)?(?P<host>[^:]*)?(?P<port>:[0-9]*)?(\?(?P<querystring>.*))?$"""
) )
def run(self, manager: "pwncat.manager.Manager", args): def run(self, manager: "pwncat.manager.Manager", args):