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:
parent
0f00871abf
commit
74962f2b2d
@ -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}")
|
||||||
|
@ -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")
|
||||||
|
@ -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")
|
||||||
|
|
||||||
|
@ -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]",
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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):
|
||||||
|
Loading…
Reference in New Issue
Block a user