2022-07-18 23:59:14 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
2023-04-26 15:43:49 +00:00
|
|
|
import logging
|
2022-07-18 23:59:14 +00:00
|
|
|
|
2023-05-01 01:49:45 +00:00
|
|
|
import sys
|
2023-05-01 13:56:03 +00:00
|
|
|
from io import StringIO
|
|
|
|
|
|
|
|
import paramiko
|
2023-04-25 23:45:56 +00:00
|
|
|
|
|
|
|
from cme.config import process_secret
|
2017-07-10 05:44:58 +00:00
|
|
|
from cme.connection import *
|
|
|
|
from cme.logger import CMEAdapter
|
|
|
|
from paramiko.ssh_exception import AuthenticationException, NoValidConnectionsError, SSHException
|
|
|
|
|
2017-10-25 02:08:19 +00:00
|
|
|
|
2017-07-10 05:44:58 +00:00
|
|
|
class ssh(connection):
|
2023-04-26 15:43:49 +00:00
|
|
|
def __init__(self, args, db, host):
|
2023-04-30 21:23:29 +00:00
|
|
|
self.protocol = "SSH"
|
2023-04-26 15:43:49 +00:00
|
|
|
self.remote_version = None
|
|
|
|
self.server_os = None
|
2023-04-30 21:23:29 +00:00
|
|
|
super().__init__(args, db, host)
|
2023-04-26 15:43:49 +00:00
|
|
|
|
|
|
|
|
2017-07-10 05:44:58 +00:00
|
|
|
@staticmethod
|
|
|
|
def proto_args(parser, std_parser, module_parser):
|
2023-04-25 23:45:56 +00:00
|
|
|
ssh_parser = parser.add_parser(
|
|
|
|
"ssh",
|
|
|
|
help="own stuff using SSH",
|
|
|
|
parents=[std_parser, module_parser]
|
|
|
|
)
|
|
|
|
ssh_parser.add_argument(
|
|
|
|
"--no-bruteforce",
|
|
|
|
action='store_true',
|
|
|
|
help="No spray when using file for username and password (user1 => password1, user2 => password2"
|
|
|
|
)
|
|
|
|
ssh_parser.add_argument(
|
|
|
|
"--key-file",
|
|
|
|
type=str,
|
|
|
|
help="Authenticate using the specified private key. Treats the password parameter as the key's passphrase."
|
|
|
|
)
|
|
|
|
ssh_parser.add_argument(
|
|
|
|
"--port",
|
|
|
|
type=int,
|
|
|
|
default=22,
|
|
|
|
help="SSH port (default: 22)"
|
|
|
|
)
|
|
|
|
ssh_parser.add_argument(
|
|
|
|
"--continue-on-success",
|
|
|
|
action="store_true",
|
|
|
|
help="continues authentication attempts even after successes"
|
|
|
|
)
|
2017-07-10 05:44:58 +00:00
|
|
|
|
2023-04-25 23:45:56 +00:00
|
|
|
cgroup = ssh_parser.add_argument_group(
|
|
|
|
"Command Execution",
|
|
|
|
"Options for executing commands"
|
|
|
|
)
|
|
|
|
cgroup.add_argument(
|
|
|
|
"--no-output",
|
|
|
|
action="store_true",
|
|
|
|
help="do not retrieve command output"
|
|
|
|
)
|
|
|
|
cgroup.add_argument(
|
|
|
|
"-x",
|
|
|
|
metavar="COMMAND",
|
|
|
|
dest="execute",
|
|
|
|
help="execute the specified command"
|
|
|
|
)
|
2023-04-26 15:43:49 +00:00
|
|
|
cgroup.add_argument(
|
|
|
|
"--remote-enum",
|
|
|
|
action="store_true",
|
|
|
|
help="executes remote commands for enumeration"
|
|
|
|
)
|
2017-07-10 05:44:58 +00:00
|
|
|
|
|
|
|
return parser
|
|
|
|
|
|
|
|
def proto_logger(self):
|
2023-03-30 03:59:22 +00:00
|
|
|
self.logger = CMEAdapter(
|
|
|
|
extra={
|
2023-04-25 23:45:56 +00:00
|
|
|
"protocol": "SSH",
|
|
|
|
"host": self.host,
|
|
|
|
"port": self.args.port,
|
|
|
|
"hostname": self.hostname
|
2023-03-30 03:59:22 +00:00
|
|
|
}
|
|
|
|
)
|
2023-04-26 15:43:49 +00:00
|
|
|
logging.getLogger("paramiko").setLevel(logging.WARNING)
|
2017-07-10 05:44:58 +00:00
|
|
|
|
|
|
|
def print_host_info(self):
|
2023-03-30 03:59:22 +00:00
|
|
|
self.logger.display(self.remote_version)
|
2021-11-17 12:37:14 +00:00
|
|
|
return True
|
2017-07-10 05:44:58 +00:00
|
|
|
|
|
|
|
def enum_host_info(self):
|
|
|
|
self.remote_version = self.conn._transport.remote_version
|
2023-04-26 15:43:49 +00:00
|
|
|
self.logger.debug(f"Remote version: {self.remote_version}")
|
|
|
|
self.server_os = ""
|
|
|
|
if self.args.remote_enum:
|
|
|
|
stdin, stdout, stderr = self.conn.exec_command("uname -r")
|
|
|
|
self.server_os = stdout.read().decode("utf-8")
|
|
|
|
self.logger.debug(f"OS retrieved: {self.server_os}")
|
|
|
|
self.db.add_host(self.host, self.args.port, self.remote_version, os=self.server_os)
|
2017-07-10 05:44:58 +00:00
|
|
|
|
|
|
|
def create_conn_obj(self):
|
|
|
|
self.conn = paramiko.SSHClient()
|
|
|
|
self.conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.conn.connect(self.host, port=self.args.port)
|
|
|
|
except AuthenticationException:
|
|
|
|
return True
|
|
|
|
except SSHException:
|
|
|
|
return True
|
|
|
|
except NoValidConnectionsError:
|
|
|
|
return False
|
2017-10-21 23:24:09 +00:00
|
|
|
except socket.error:
|
|
|
|
return False
|
2017-07-10 05:44:58 +00:00
|
|
|
|
2020-04-28 10:11:16 +00:00
|
|
|
def client_close(self):
|
|
|
|
self.conn.close()
|
|
|
|
|
2017-07-10 05:44:58 +00:00
|
|
|
def check_if_admin(self):
|
2023-04-25 23:45:56 +00:00
|
|
|
# we could add in another method to check by piping in the password to sudo
|
|
|
|
# but that might be too much of an opsec concern - maybe add in a flag to do more checks?
|
2023-04-12 04:25:38 +00:00
|
|
|
stdin, stdout, stderr = self.conn.exec_command("id")
|
|
|
|
if stdout.read().decode("utf-8").find("uid=0(root)") != -1:
|
2023-04-25 23:45:56 +00:00
|
|
|
self.logger.info(f"Determined user is root via `id` command")
|
|
|
|
self.admin_privs = True
|
|
|
|
return True
|
|
|
|
stdin, stdout, stderr = self.conn.exec_command("sudo -ln | grep 'NOPASSWD: ALL'")
|
|
|
|
if stdout.read().decode("utf-8").find("NOPASSWD: ALL")!= -1:
|
|
|
|
self.logger.info(f"Determined user is root via `sudo -ln` command")
|
2017-07-10 05:44:58 +00:00
|
|
|
self.admin_privs = True
|
2023-04-25 23:45:56 +00:00
|
|
|
return True
|
2017-07-10 05:44:58 +00:00
|
|
|
|
2023-04-30 21:23:29 +00:00
|
|
|
def plaintext_login(self, username, password, private_key=None):
|
2017-07-10 05:44:58 +00:00
|
|
|
try:
|
2023-05-01 01:49:45 +00:00
|
|
|
if self.args.key_file or private_key:
|
|
|
|
if private_key:
|
|
|
|
pkey = paramiko.RSAKey.from_private_key(StringIO(private_key))
|
|
|
|
else:
|
|
|
|
pkey = paramiko.RSAKey.from_private_key_file(self.args.key_file)
|
2023-04-26 15:43:49 +00:00
|
|
|
|
2023-05-01 01:49:45 +00:00
|
|
|
self.logger.debug(f"Logging in with key")
|
2023-03-30 03:59:22 +00:00
|
|
|
self.conn.connect(
|
|
|
|
self.host,
|
|
|
|
port=self.args.port,
|
|
|
|
username=username,
|
2023-04-30 21:23:29 +00:00
|
|
|
passphrase=password if password != "" else None,
|
2023-05-01 01:49:45 +00:00
|
|
|
pkey=pkey,
|
2023-03-30 03:59:22 +00:00
|
|
|
look_for_keys=False,
|
|
|
|
allow_agent=False
|
|
|
|
)
|
2023-05-01 01:49:45 +00:00
|
|
|
if private_key:
|
|
|
|
cred_id = self.db.add_credential("key", username, password if password != "" else "", key=private_key)
|
|
|
|
else:
|
|
|
|
with open(self.args.key_file, "r") as f:
|
|
|
|
key_data = f.read()
|
|
|
|
cred_id = self.db.add_credential("key", username, password if password != "" else "", key=key_data)
|
2019-11-18 17:39:17 +00:00
|
|
|
else:
|
2023-04-25 23:45:56 +00:00
|
|
|
self.logger.debug(f"Logging in with password")
|
2023-03-30 03:59:22 +00:00
|
|
|
self.conn.connect(
|
|
|
|
self.host,
|
|
|
|
port=self.args.port,
|
|
|
|
username=username,
|
|
|
|
password=password,
|
|
|
|
look_for_keys=False,
|
|
|
|
allow_agent=False
|
|
|
|
)
|
2023-04-26 15:43:49 +00:00
|
|
|
cred_id = self.db.add_credential("plaintext", username, password)
|
|
|
|
|
2023-04-27 00:36:36 +00:00
|
|
|
shell_access = False
|
2023-04-26 15:43:49 +00:00
|
|
|
host_id = self.db.get_hosts(self.host)[0].id
|
2017-07-10 05:44:58 +00:00
|
|
|
|
2023-04-25 23:45:56 +00:00
|
|
|
if self.check_if_admin():
|
2023-04-27 00:36:36 +00:00
|
|
|
shell_access = True
|
2023-04-25 23:45:56 +00:00
|
|
|
self.logger.debug(f"User {username} logged in successfully and is root!")
|
2023-04-26 15:43:49 +00:00
|
|
|
if self.args.key_file:
|
|
|
|
self.db.add_admin_user("key", username, password, host_id=host_id, cred_id=cred_id)
|
|
|
|
else:
|
|
|
|
self.db.add_admin_user("plaintext", username, password, host_id=host_id, cred_id=cred_id)
|
2023-04-27 00:36:36 +00:00
|
|
|
else:
|
|
|
|
stdin, stdout, stderr = self.conn.exec_command("id")
|
|
|
|
output = stdout.read().decode("utf-8")
|
|
|
|
if not output:
|
|
|
|
self.logger.debug(f"User cannot get a shell")
|
|
|
|
shell_access = False
|
|
|
|
else:
|
|
|
|
shell_access = True
|
2023-04-26 15:43:49 +00:00
|
|
|
|
2023-04-29 20:33:16 +00:00
|
|
|
self.db.add_loggedin_relation(cred_id, host_id, shell=shell_access)
|
|
|
|
|
2023-04-26 15:43:49 +00:00
|
|
|
if self.args.key_file:
|
|
|
|
password = f"{password} (keyfile: {self.args.key_file})"
|
|
|
|
|
2023-04-27 00:36:36 +00:00
|
|
|
display_shell_access = f" - shell access!" if shell_access else ""
|
|
|
|
|
2023-03-30 03:59:22 +00:00
|
|
|
self.logger.success(
|
2023-04-27 00:36:36 +00:00
|
|
|
f"{username}:{process_secret(password)} {self.mark_pwned()}{highlight(display_shell_access)}"
|
2023-03-30 03:59:22 +00:00
|
|
|
)
|
2023-04-26 15:43:49 +00:00
|
|
|
|
2020-06-20 10:10:05 +00:00
|
|
|
if not self.args.continue_on_success:
|
|
|
|
return True
|
2023-04-26 16:50:00 +00:00
|
|
|
except (AuthenticationException, NoValidConnectionsError, ConnectionResetError) as e:
|
2023-04-21 10:20:47 +00:00
|
|
|
self.logger.fail(
|
2023-04-25 23:45:56 +00:00
|
|
|
f"{username}:{process_secret(password)} {e}"
|
2023-03-30 03:59:22 +00:00
|
|
|
)
|
2020-04-28 10:11:16 +00:00
|
|
|
self.client_close()
|
2017-07-10 05:44:58 +00:00
|
|
|
return False
|
2023-04-25 23:45:56 +00:00
|
|
|
except Exception as e:
|
|
|
|
self.logger.exception(e)
|
|
|
|
self.client_close()
|
|
|
|
return False
|
2017-07-10 05:44:58 +00:00
|
|
|
|
2023-04-26 15:43:49 +00:00
|
|
|
def execute(self, payload=None, output=False):
|
2020-04-28 10:11:16 +00:00
|
|
|
try:
|
2023-04-26 15:43:49 +00:00
|
|
|
command = payload if payload is not None else self.args.execute
|
|
|
|
stdin, stdout, stderr = self.conn.exec_command(command)
|
2020-04-28 10:11:16 +00:00
|
|
|
except AttributeError:
|
2023-04-12 04:25:38 +00:00
|
|
|
return ""
|
2023-04-26 15:43:49 +00:00
|
|
|
if output:
|
|
|
|
self.logger.success("Executed command")
|
|
|
|
for line in stdout:
|
|
|
|
self.logger.highlight(line.strip())
|
|
|
|
return stdout
|