Resolve merge conflicts
commit
a6c77294dc
|
@ -11,11 +11,16 @@ assignees: ''
|
|||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
Steps to reproduce the behavior i.e.:
|
||||
Command: `crackmapexec smb -u username -p password`
|
||||
Resulted in:
|
||||
```
|
||||
crackmapexec smb 10.10.10.10 -u username -p password -x "whoami"
|
||||
SMB 10.10.10.10 445 DC01 [*] Windows 10.0 Build 17763 x64 (name:DC01) (domain:domain) (signing:True) (SMBv1:False)
|
||||
SMB 10.10.10.10 445 DC01 [+] domain\username:password
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
```
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
@ -26,7 +31,7 @@ If applicable, add screenshots to help explain your problem.
|
|||
**Crackmapexec info**
|
||||
- OS: [e.g. Kali]
|
||||
- Version of CME [e.g. v5.0.2]
|
||||
- Installed from apt or using latest release ? Please try with latest release before openning an issue
|
||||
- Installed from: apt/github/pip/docker/...? Please try with latest release before openning an issue
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
|
58
cme/cmedb.py
58
cme/cmedb.py
|
@ -19,14 +19,6 @@ from cme.loaders.protocolloader import ProtocolLoader
|
|||
from cme.paths import CONFIG_PATH, WS_PATH, WORKSPACE_DIR
|
||||
|
||||
|
||||
# # The following disables the InsecureRequests warning and the 'Starting new HTTPS connection' log message
|
||||
# from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
# requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
#
|
||||
# # if there is an issue with SQLAlchemy and a connection cannot be cleaned up properly it spews out annoying warnings
|
||||
# warnings.filterwarnings("ignore", category=SAWarning)
|
||||
|
||||
|
||||
class UserExitedProto(Exception):
|
||||
pass
|
||||
|
||||
|
@ -84,7 +76,7 @@ def complete_export(text, line):
|
|||
"""
|
||||
Tab-complete 'creds' commands.
|
||||
"""
|
||||
commands = ["creds", "plaintext", "hashes", "shares", "local_admins", "signing"]
|
||||
commands = ["creds", "plaintext", "hashes", "shares", "local_admins", "signing", "keys"]
|
||||
mline = line.partition(' ')[2]
|
||||
offs = len(mline) - len(text)
|
||||
return [s[offs:] for s in commands if s.startswith(mline)]
|
||||
|
@ -121,11 +113,11 @@ class DatabaseNavigator(cmd.Cmd):
|
|||
print("[-] not enough arguments")
|
||||
return
|
||||
line = line.split()
|
||||
|
||||
command = line[0].lower()
|
||||
# Need to use if/elif/else to keep compatibility with py3.8/3.9
|
||||
# Reference DB Function cme/protocols/smb/database.py
|
||||
# Users
|
||||
if line[0].lower() == 'creds':
|
||||
if command == "creds":
|
||||
if len(line) < 3:
|
||||
print("[-] invalid arguments, export creds <simple|detailed> <filename>")
|
||||
return
|
||||
|
@ -154,11 +146,11 @@ class DatabaseNavigator(cmd.Cmd):
|
|||
formatted_creds.append(entry)
|
||||
write_csv(filename, csv_header, formatted_creds)
|
||||
else:
|
||||
print('[-] No such export option: %s' % line[1])
|
||||
print(f"[-] No such export option: {line[1]}")
|
||||
return
|
||||
print('[+] Creds exported')
|
||||
# Hosts
|
||||
elif line[0].lower() == 'hosts':
|
||||
elif command == "hosts":
|
||||
if len(line) < 3:
|
||||
print("[-] invalid arguments, export hosts <simple|detailed|signing> <filename>")
|
||||
return
|
||||
|
@ -180,11 +172,11 @@ class DatabaseNavigator(cmd.Cmd):
|
|||
signing_hosts = [host[1] for host in hosts]
|
||||
write_list(filename, signing_hosts)
|
||||
else:
|
||||
print('[-] No such export option: %s' % line[1])
|
||||
print(f"[-] No such export option: {line[1]}")
|
||||
return
|
||||
print('[+] Hosts exported')
|
||||
# Shares
|
||||
elif line[0].lower() == 'shares':
|
||||
elif command == "shares":
|
||||
if len(line) < 3:
|
||||
print("[-] invalid arguments, export shares <simple|detailed> <filename>")
|
||||
return
|
||||
|
@ -193,11 +185,11 @@ class DatabaseNavigator(cmd.Cmd):
|
|||
csv_header = ["id", "host", "userid", "name", "remark", "read", "write"]
|
||||
filename = line[2]
|
||||
|
||||
if line[1].lower() == 'simple':
|
||||
if line[1].lower() == "simple":
|
||||
write_csv(filename, csv_header, shares)
|
||||
print('[+] shares exported')
|
||||
print("[+] shares exported")
|
||||
# Detailed view gets hostname, usernames, and true false statement
|
||||
elif line[1].lower() == 'detailed':
|
||||
elif line[1].lower() == "detailed":
|
||||
formatted_shares = []
|
||||
for share in shares:
|
||||
user = self.db.get_users(share[2])[0]
|
||||
|
@ -214,11 +206,11 @@ class DatabaseNavigator(cmd.Cmd):
|
|||
formatted_shares.append(entry)
|
||||
write_csv(filename, csv_header, formatted_shares)
|
||||
else:
|
||||
print('[-] No such export option: %s' % line[1])
|
||||
print(f"[-] No such export option: {line[1]}")
|
||||
return
|
||||
print('[+] Shares exported')
|
||||
print("[+] Shares exported")
|
||||
# Local Admin
|
||||
elif line[0].lower() == 'local_admins':
|
||||
elif command == "local_admins":
|
||||
if len(line) < 3:
|
||||
print("[-] invalid arguments, export local_admins <simple|detailed> <filename>")
|
||||
return
|
||||
|
@ -228,9 +220,9 @@ class DatabaseNavigator(cmd.Cmd):
|
|||
csv_header = ["id", "userid", "host"]
|
||||
filename = line[2]
|
||||
|
||||
if line[1].lower() == 'simple':
|
||||
if line[1].lower() == "simple":
|
||||
write_csv(filename, csv_header, local_admins)
|
||||
elif line[1].lower() == 'detailed':
|
||||
elif line[1].lower() == "detailed":
|
||||
formatted_local_admins = []
|
||||
for entry in local_admins:
|
||||
user = self.db.get_users(filter_term=entry[1])[0]
|
||||
|
@ -244,10 +236,10 @@ class DatabaseNavigator(cmd.Cmd):
|
|||
formatted_local_admins.append(formatted_entry)
|
||||
write_csv(filename, csv_header, formatted_local_admins)
|
||||
else:
|
||||
print('[-] No such export option: %s' % line[1])
|
||||
print(f"[-] No such export option: {line[1]}")
|
||||
return
|
||||
print('[+] Local Admins exported')
|
||||
elif line[0].lower() == 'dpapi':
|
||||
elif command == "dpapi":
|
||||
if len(line) < 3:
|
||||
print("[-] invalid arguments, export dpapi <simple|detailed> <filename>")
|
||||
return
|
||||
|
@ -257,9 +249,9 @@ class DatabaseNavigator(cmd.Cmd):
|
|||
csv_header = ["id", "host", "dpapi_type", "windows_user", "username", "password", "url"]
|
||||
filename = line[2]
|
||||
|
||||
if line[1].lower() == 'simple':
|
||||
if line[1].lower() == "simple":
|
||||
write_csv(filename, csv_header, dpapi_secrets)
|
||||
elif line[1].lower() == 'detailed':
|
||||
elif line[1].lower() == "detailed":
|
||||
formatted_dpapi_secret = []
|
||||
for entry in dpapi_secrets:
|
||||
|
||||
|
@ -279,16 +271,26 @@ class DatabaseNavigator(cmd.Cmd):
|
|||
print('[-] No such export option: %s' % line[1])
|
||||
return
|
||||
print('[+] DPAPI secrets exported')
|
||||
elif command == "keys":
|
||||
if line[1].lower() == "all":
|
||||
keys = self.db.get_keys()
|
||||
else:
|
||||
keys = self.db.get_keys(key_id=int(line[1]))
|
||||
writable_keys = [key[2] for key in keys]
|
||||
filename = line[2]
|
||||
write_list(filename, writable_keys)
|
||||
else:
|
||||
print("[-] Invalid argument, specify creds, hosts, local_admins, shares or dpapi")
|
||||
|
||||
def help_export(self):
|
||||
help_string = """
|
||||
export [creds|hosts|local_admins|shares|signing] [simple|detailed|*] [filename]
|
||||
export [creds|hosts|local_admins|shares|signing|keys] [simple|detailed|*] [filename]
|
||||
Exports information to a specified file
|
||||
|
||||
* hosts has an additional third option from simple and detailed: signing - this simply writes a list of ips of
|
||||
hosts where signing is enabled
|
||||
* keys' third option is either "all" or an id of a key to export
|
||||
export keys [all|id] [filename]
|
||||
"""
|
||||
print_help(help_string)
|
||||
|
||||
|
|
|
@ -1,36 +1,74 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
|
||||
import paramiko
|
||||
|
||||
from cme.config import process_secret
|
||||
from cme.connection import *
|
||||
from cme.helpers.logger import highlight
|
||||
from cme.logger import CMEAdapter
|
||||
from paramiko.ssh_exception import AuthenticationException, NoValidConnectionsError, SSHException
|
||||
|
||||
|
||||
class ssh(connection):
|
||||
def __init__(self, args, db, host):
|
||||
super().__init__(args, db, host)
|
||||
self.remote_version = None
|
||||
self.server_os = None
|
||||
|
||||
|
||||
@staticmethod
|
||||
def proto_args(parser, std_parser, module_parser):
|
||||
ssh_parser = parser.add_parser('ssh', help="own stuff using SSH", parents=[std_parser, module_parser])
|
||||
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 = parser.add_parser(
|
||||
"ssh",
|
||||
help="own stuff using SSH",
|
||||
parents=[std_parser, module_parser]
|
||||
)
|
||||
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)"
|
||||
)
|
||||
|
||||
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")
|
||||
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"
|
||||
)
|
||||
cgroup.add_argument(
|
||||
"--remote-enum",
|
||||
action="store_true",
|
||||
help="executes remote commands for enumeration"
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def proto_logger(self):
|
||||
self.logger = CMEAdapter(
|
||||
extra={
|
||||
'protocol': 'SSH',
|
||||
'host': self.host,
|
||||
'port': self.args.port,
|
||||
'hostname': self.hostname
|
||||
"protocol": "SSH",
|
||||
"host": self.host,
|
||||
"port": self.args.port,
|
||||
"hostname": self.hostname
|
||||
}
|
||||
)
|
||||
logging.getLogger("paramiko").setLevel(logging.WARNING)
|
||||
|
||||
def print_host_info(self):
|
||||
self.logger.display(self.remote_version)
|
||||
|
@ -38,6 +76,13 @@ class ssh(connection):
|
|||
|
||||
def enum_host_info(self):
|
||||
self.remote_version = self.conn._transport.remote_version
|
||||
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)
|
||||
|
||||
def create_conn_obj(self):
|
||||
self.conn = paramiko.SSHClient()
|
||||
|
@ -58,25 +103,38 @@ class ssh(connection):
|
|||
self.conn.close()
|
||||
|
||||
def check_if_admin(self):
|
||||
# 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?
|
||||
stdin, stdout, stderr = self.conn.exec_command("id")
|
||||
if stdout.read().decode("utf-8").find("uid=0(root)") != -1:
|
||||
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")
|
||||
self.admin_privs = True
|
||||
return True
|
||||
|
||||
def plaintext_login(self, username, password):
|
||||
try:
|
||||
if self.args.key_file:
|
||||
passwd = password
|
||||
password = f"{passwd} (keyfile: {self.args.key_file})"
|
||||
self.logger.debug(f"Logging in with keyfile: {self.args.key_file}")
|
||||
with open(self.args.key_file, "r") as f:
|
||||
key_data = f.read()
|
||||
|
||||
self.conn.connect(
|
||||
self.host,
|
||||
port=self.args.port,
|
||||
username=username,
|
||||
passphrase=passwd,
|
||||
passphrase=password,
|
||||
key_filename=self.args.key_file,
|
||||
look_for_keys=False,
|
||||
allow_agent=False
|
||||
)
|
||||
cred_id = self.db.add_credential("key", username, password, key=key_data)
|
||||
else:
|
||||
self.logger.debug(f"Logging in with password")
|
||||
self.conn.connect(
|
||||
self.host,
|
||||
port=self.args.port,
|
||||
|
@ -85,30 +143,57 @@ class ssh(connection):
|
|||
look_for_keys=False,
|
||||
allow_agent=False
|
||||
)
|
||||
cred_id = self.db.add_credential("plaintext", username, password)
|
||||
|
||||
shell_access = False
|
||||
host_id = self.db.get_hosts(self.host)[0].id
|
||||
|
||||
if self.check_if_admin():
|
||||
shell_access = True
|
||||
self.logger.debug(f"User {username} logged in successfully and is root!")
|
||||
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)
|
||||
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
|
||||
|
||||
self.db.add_loggedin_relation(cred_id, host_id, shell=shell_access)
|
||||
|
||||
if self.args.key_file:
|
||||
password = f"{password} (keyfile: {self.args.key_file})"
|
||||
|
||||
display_shell_access = f" - shell access!" if shell_access else ""
|
||||
|
||||
self.check_if_admin()
|
||||
self.logger.success(
|
||||
u"{}:{} {}".format(
|
||||
username,
|
||||
password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
|
||||
highlight(f'({self.config.get("CME", "pwn3d_label")})' if self.admin_privs else '')
|
||||
)
|
||||
f"{username}:{process_secret(password)} {self.mark_pwned()}{highlight(display_shell_access)}"
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
except (AuthenticationException, NoValidConnectionsError, ConnectionResetError) as e:
|
||||
self.logger.fail(
|
||||
f"{username}:{password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8} {e}"
|
||||
f"{username}:{process_secret(password)} {e}"
|
||||
)
|
||||
self.client_close()
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.exception(e)
|
||||
self.client_close()
|
||||
return False
|
||||
|
||||
def execute(self, payload=None, get_output=False):
|
||||
def execute(self, payload=None, output=False):
|
||||
try:
|
||||
stdin, stdout, stderr = self.conn.exec_command(self.args.execute)
|
||||
command = payload if payload is not None else self.args.execute
|
||||
stdin, stdout, stderr = self.conn.exec_command(command)
|
||||
except AttributeError:
|
||||
return ""
|
||||
self.logger.success("Executed command")
|
||||
for line in stdout:
|
||||
self.logger.highlight(line.strip())
|
||||
|
||||
return stdout
|
||||
if output:
|
||||
self.logger.success("Executed command")
|
||||
for line in stdout:
|
||||
self.logger.highlight(line.strip())
|
||||
return stdout
|
||||
|
|
|
@ -1,16 +1,29 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from sqlalchemy.dialects.sqlite import Insert
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
from sqlalchemy import MetaData, Table
|
||||
from sqlalchemy import MetaData, Table, select, func, delete
|
||||
from sqlalchemy.exc import IllegalStateChangeError, NoInspectionAvailable, NoSuchTableError
|
||||
|
||||
import os
|
||||
import configparser
|
||||
|
||||
from cme.logger import cme_logger
|
||||
from cme.paths import CME_PATH
|
||||
|
||||
# we can't import config.py due to a circular dependency, so we have to create redundant code unfortunately
|
||||
cme_config = configparser.ConfigParser()
|
||||
cme_config.read(os.path.join(CME_PATH, "cme.conf"))
|
||||
cme_workspace = cme_config.get("CME", "workspace", fallback="default")
|
||||
|
||||
|
||||
class database:
|
||||
def __init__(self, db_engine):
|
||||
self.CredentialsTable = None
|
||||
self.HostsTable = None
|
||||
self.LoggedinRelationsTable = None
|
||||
self.AdminRelationsTable = None
|
||||
self.KeysTable = None
|
||||
|
||||
self.db_engine = db_engine
|
||||
self.metadata = MetaData()
|
||||
|
@ -21,41 +34,72 @@ class database:
|
|||
)
|
||||
|
||||
Session = scoped_session(session_factory)
|
||||
# this is still named "conn" when it is the session object; TODO: rename
|
||||
self.conn = Session()
|
||||
self.sess = Session()
|
||||
|
||||
@staticmethod
|
||||
def db_schema(db_conn):
|
||||
db_conn.execute('''CREATE TABLE "credentials" (
|
||||
db_conn.execute(
|
||||
'''CREATE TABLE "credentials" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"username" text,
|
||||
"password" text
|
||||
)''')
|
||||
|
||||
db_conn.execute('''CREATE TABLE "hosts" (
|
||||
"password" text,
|
||||
"credtype" text
|
||||
)''')
|
||||
db_conn.execute(
|
||||
'''CREATE TABLE "hosts" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"ip" text,
|
||||
"host" text,
|
||||
"port" integer,
|
||||
"server_banner" text
|
||||
)''')
|
||||
"banner" text,
|
||||
"os" text
|
||||
)''')
|
||||
db_conn.execute(
|
||||
'''CREATE TABLE "loggedin_relations" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"credid" integer,
|
||||
"hostid" integer,
|
||||
"shell" boolean,
|
||||
FOREIGN KEY(credid) REFERENCES credentials(id),
|
||||
FOREIGN KEY(hostid) REFERENCES hosts(id)
|
||||
)''')
|
||||
# "admin" access with SSH means we have root access, which implies shell access since we run commands to check
|
||||
db_conn.execute(
|
||||
'''CREATE TABLE "admin_relations" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"credid" integer,
|
||||
"hostid" integer,
|
||||
FOREIGN KEY(credid) REFERENCES credentials(id),
|
||||
FOREIGN KEY(hostid) REFERENCES hosts(id)
|
||||
)''')
|
||||
db_conn.execute(
|
||||
'''CREATE TABLE "keys" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"credid" integer,
|
||||
"data" text,
|
||||
FOREIGN KEY(credid) REFERENCES credentials(id)
|
||||
)''')
|
||||
|
||||
def reflect_tables(self):
|
||||
with self.db_engine.connect() as conn:
|
||||
with self.db_engine.connect():
|
||||
try:
|
||||
self.CredentialsTable = Table("credentials", self.metadata, autoload_with=self.db_engine)
|
||||
self.HostsTable = Table("hosts", self.metadata, autoload_with=self.db_engine)
|
||||
self.LoggedinRelationsTable = Table("loggedin_relations", self.metadata, autoload_with=self.db_engine)
|
||||
self.AdminRelationsTable = Table("admin_relations", self.metadata, autoload_with=self.db_engine)
|
||||
self.KeysTable = Table("keys", self.metadata, autoload_with=self.db_engine)
|
||||
except (NoInspectionAvailable, NoSuchTableError):
|
||||
ssh_workspace = f"~/.cme/workspaces/{cme_workspace}/ssh.db"
|
||||
print(
|
||||
"[-] Error reflecting tables - this means there is a DB schema mismatch \n"
|
||||
"[-] Error reflecting tables for SSH protocol - this means there is a DB schema mismatch \n"
|
||||
"[-] This is probably because a newer version of CME is being ran on an old DB schema\n"
|
||||
"[-] If you wish to save the old DB data, copy it to a new location (`cp -r ~/.cme/workspaces/ ~/old_cme_workspaces/`)\n"
|
||||
"[-] Then remove the CME DB folders (`rm -rf ~/.cme/workspaces/`) and rerun CME to initialize the new DB schema"
|
||||
f"[-] Optionally save the old DB data (`cp {ssh_workspace} ~/cme_ssh.bak`)\n"
|
||||
f"[-] Then remove the CME SSH DB (`rm -rf {ssh_workspace}`) and run CME to initialize the new DB"
|
||||
)
|
||||
exit()
|
||||
|
||||
def shutdown_db(self):
|
||||
try:
|
||||
self.conn.close()
|
||||
self.sess.close()
|
||||
# due to the async nature of CME, sometimes session state is a bit messy and this will throw:
|
||||
# Method 'close()' can't be called here; method '_connection_for_bind()' is already in progress and
|
||||
# this would cause an unexpected state change to <SessionTransactionState.CLOSED: 5>
|
||||
|
@ -64,4 +108,429 @@ class database:
|
|||
|
||||
def clear_database(self):
|
||||
for table in self.metadata.sorted_tables:
|
||||
self.conn.execute(table.delete())
|
||||
self.sess.execute(table.delete())
|
||||
|
||||
def add_host(self, host, port, banner, os=None):
|
||||
"""
|
||||
Check if this host has already been added to the database, if not, add it in.
|
||||
"""
|
||||
hosts = []
|
||||
updated_ids = []
|
||||
|
||||
q = select(self.HostsTable).filter(
|
||||
self.HostsTable.c.host == host
|
||||
)
|
||||
results = self.sess.execute(q).all()
|
||||
cme_logger.debug(f"add_host(): Initial hosts results: {results}")
|
||||
|
||||
# create new host
|
||||
if not results:
|
||||
new_host = {
|
||||
"host": host,
|
||||
"port": port,
|
||||
"banner": banner if banner is not None else '',
|
||||
"os": os if os is not None else ''
|
||||
}
|
||||
hosts = [new_host]
|
||||
# update existing hosts data
|
||||
else:
|
||||
for host_result in results:
|
||||
host_data = host_result._asdict()
|
||||
cme_logger.debug(f"host: {host_result}")
|
||||
cme_logger.debug(f"host_data: {host_data}")
|
||||
# only update column if it is being passed in
|
||||
if host is not None:
|
||||
host_data["host"] = host
|
||||
if port is not None:
|
||||
host_data["port"] = port
|
||||
if banner is not None:
|
||||
host_data["banner"] = banner
|
||||
if os is not None:
|
||||
host_data["os"] = os
|
||||
# only add host to be updated if it has changed
|
||||
if host_data not in hosts:
|
||||
hosts.append(host_data)
|
||||
updated_ids.append(host_data["id"])
|
||||
cme_logger.debug(f"Hosts: {hosts}")
|
||||
|
||||
# TODO: find a way to abstract this away to a single Upsert call
|
||||
q = Insert(self.HostsTable) # .returning(self.HostsTable.c.id)
|
||||
update_columns = {col.name: col for col in q.excluded if col.name not in 'id'}
|
||||
q = q.on_conflict_do_update(
|
||||
index_elements=self.HostsTable.primary_key,
|
||||
set_=update_columns
|
||||
)
|
||||
|
||||
self.sess.execute(
|
||||
q,
|
||||
hosts
|
||||
) # .scalar()
|
||||
# we only return updated IDs for now - when RETURNING clause is allowed we can return inserted
|
||||
if updated_ids:
|
||||
cme_logger.debug(f"add_host() - Host IDs Updated: {updated_ids}")
|
||||
return updated_ids
|
||||
|
||||
def add_credential(self, credtype, username, password, key=None):
|
||||
"""
|
||||
Check if this credential has already been added to the database, if not add it in.
|
||||
"""
|
||||
credentials = []
|
||||
|
||||
# a user can have multiple keys, all with passphrases, and a separate login password
|
||||
if key is not None:
|
||||
q = select(self.CredentialsTable).join(self.KeysTable).filter(
|
||||
func.lower(self.CredentialsTable.c.username) == func.lower(username),
|
||||
func.lower(self.CredentialsTable.c.credtype) == func.lower(credtype),
|
||||
self.KeysTable.c.data == key
|
||||
)
|
||||
results = self.sess.execute(q).all()
|
||||
else:
|
||||
q = select(self.CredentialsTable).filter(
|
||||
func.lower(self.CredentialsTable.c.username) == func.lower(username),
|
||||
func.lower(self.CredentialsTable.c.credtype) == func.lower(credtype)
|
||||
)
|
||||
results = self.sess.execute(q).all()
|
||||
|
||||
# add new credential
|
||||
if not results:
|
||||
new_cred = {
|
||||
"credtype": credtype,
|
||||
"username": username,
|
||||
"password": password,
|
||||
}
|
||||
credentials = [new_cred]
|
||||
# update existing cred data
|
||||
else:
|
||||
for creds in results:
|
||||
# this will include the id, so we don't touch it
|
||||
cred_data = creds._asdict()
|
||||
# only update column if it is being passed in
|
||||
if credtype is not None:
|
||||
cred_data["credtype"] = credtype
|
||||
if username is not None:
|
||||
cred_data["username"] = username
|
||||
if password is not None:
|
||||
cred_data["password"] = password
|
||||
# only add cred to be updated if it has changed
|
||||
if cred_data not in credentials:
|
||||
credentials.append(cred_data)
|
||||
|
||||
# TODO: find a way to abstract this away to a single Upsert call
|
||||
q_users = Insert(self.CredentialsTable) # .returning(self.CredentialsTable.c.id)
|
||||
update_columns_users = {col.name: col for col in q_users.excluded if col.name not in 'id'}
|
||||
q_users = q_users.on_conflict_do_update(
|
||||
index_elements=self.CredentialsTable.primary_key,
|
||||
set_=update_columns_users
|
||||
)
|
||||
cme_logger.debug(f"Adding credentials: {credentials}")
|
||||
|
||||
self.sess.execute(
|
||||
q_users,
|
||||
credentials
|
||||
) # .scalar()
|
||||
# return cred_ids
|
||||
|
||||
# hacky way to get cred_id since we can't use returning() yet
|
||||
if len(credentials) == 1:
|
||||
cred_id = self.get_credential(credtype, username, password)
|
||||
if key is not None:
|
||||
self.add_key(cred_id, key)
|
||||
return cred_id
|
||||
else:
|
||||
return credentials
|
||||
|
||||
def remove_credentials(self, creds_id):
|
||||
"""
|
||||
Removes a credential ID from the database
|
||||
"""
|
||||
del_hosts = []
|
||||
for cred_id in creds_id:
|
||||
q = delete(self.CredentialsTable).filter(
|
||||
self.CredentialsTable.c.id == cred_id
|
||||
)
|
||||
del_hosts.append(q)
|
||||
self.sess.execute(q)
|
||||
|
||||
def add_key(self, cred_id, key):
|
||||
# check if key relation already exists
|
||||
check_q = self.sess.execute(
|
||||
select(self.KeysTable).filter(
|
||||
self.KeysTable.c.credid == cred_id
|
||||
)
|
||||
).all()
|
||||
cme_logger.debug(f"check_q: {check_q}")
|
||||
if check_q:
|
||||
cme_logger.debug(f"Key already exists for cred_id {cred_id}")
|
||||
return
|
||||
|
||||
key_data = {
|
||||
"credid": cred_id,
|
||||
"data": key
|
||||
}
|
||||
self.sess.execute(
|
||||
Insert(self.KeysTable),
|
||||
key_data
|
||||
)
|
||||
key_id = self.sess.execute(
|
||||
select(self.KeysTable).filter(
|
||||
self.KeysTable.c.credid == cred_id
|
||||
)
|
||||
).all()[0].id
|
||||
cme_logger.debug(f"Key added: {key_id}")
|
||||
return key_id
|
||||
|
||||
def get_keys(self, key_id=None, cred_id=None):
|
||||
q = select(self.KeysTable)
|
||||
if key_id is not None:
|
||||
q = q.filter(
|
||||
self.KeysTable.c.id == key_id
|
||||
)
|
||||
elif cred_id is not None:
|
||||
q = q.filter(
|
||||
self.KeysTable.c.credid == cred_id
|
||||
)
|
||||
results = self.sess.execute(q)
|
||||
return results
|
||||
|
||||
def add_admin_user(self, credtype, username, secret, host_id=None, cred_id=None):
|
||||
add_links = []
|
||||
|
||||
creds_q = select(self.CredentialsTable)
|
||||
if cred_id:
|
||||
creds_q = creds_q.filter(
|
||||
self.CredentialsTable.c.id == cred_id
|
||||
)
|
||||
else:
|
||||
creds_q = creds_q.filter(
|
||||
func.lower(self.CredentialsTable.c.credtype) == func.lower(credtype),
|
||||
func.lower(self.CredentialsTable.c.username) == func.lower(username),
|
||||
self.CredentialsTable.c.password == secret
|
||||
)
|
||||
creds = self.sess.execute(creds_q)
|
||||
hosts = self.get_hosts(host_id)
|
||||
|
||||
if creds and hosts:
|
||||
for cred, host in zip(creds, hosts):
|
||||
cred_id = cred[0]
|
||||
host_id = host[0]
|
||||
link = {
|
||||
"credid": cred_id,
|
||||
"hostid": host_id
|
||||
}
|
||||
admin_relations_select = select(self.AdminRelationsTable).filter(
|
||||
self.AdminRelationsTable.c.credid == cred_id,
|
||||
self.AdminRelationsTable.c.hostid == host_id
|
||||
)
|
||||
links = self.sess.execute(admin_relations_select).all()
|
||||
|
||||
if not links:
|
||||
add_links.append(link)
|
||||
|
||||
admin_relations_insert = Insert(self.AdminRelationsTable)
|
||||
|
||||
if add_links:
|
||||
self.sess.execute(
|
||||
admin_relations_insert,
|
||||
add_links
|
||||
)
|
||||
|
||||
def get_admin_relations(self, cred_id=None, host_id=None):
|
||||
if cred_id:
|
||||
q = select(self.AdminRelationsTable).filter(
|
||||
self.AdminRelationsTable.c.credid == cred_id
|
||||
)
|
||||
elif host_id:
|
||||
q = select(self.AdminRelationsTable).filter(
|
||||
self.AdminRelationsTable.c.hostid == host_id
|
||||
)
|
||||
else:
|
||||
q = select(self.AdminRelationsTable)
|
||||
|
||||
results = self.sess.execute(q).all()
|
||||
return results
|
||||
|
||||
def remove_admin_relation(self, cred_ids=None, host_ids=None):
|
||||
q = delete(self.AdminRelationsTable)
|
||||
if cred_ids:
|
||||
for cred_id in cred_ids:
|
||||
q = q.filter(
|
||||
self.AdminRelationsTable.c.credid == cred_id
|
||||
)
|
||||
elif host_ids:
|
||||
for host_id in host_ids:
|
||||
q = q.filter(
|
||||
self.AdminRelationsTable.c.hostid == host_id
|
||||
)
|
||||
self.sess.execute(q)
|
||||
|
||||
def is_credential_valid(self, credential_id):
|
||||
"""
|
||||
Check if this credential ID is valid.
|
||||
"""
|
||||
q = select(self.CredentialsTable).filter(
|
||||
self.CredentialsTable.c.id == credential_id,
|
||||
self.CredentialsTable.c.password is not None
|
||||
)
|
||||
results = self.sess.execute(q).all()
|
||||
return len(results) > 0
|
||||
|
||||
def get_credentials(self, filter_term=None, cred_type=None):
|
||||
"""
|
||||
Return credentials from the database.
|
||||
"""
|
||||
# if we're returning a single credential by ID
|
||||
if self.is_credential_valid(filter_term):
|
||||
q = select(self.CredentialsTable).filter(
|
||||
self.CredentialsTable.c.id == filter_term
|
||||
)
|
||||
elif cred_type:
|
||||
q = select(self.CredentialsTable).filter(
|
||||
self.CredentialsTable.c.credtype == cred_type
|
||||
)
|
||||
# if we're filtering by username
|
||||
elif filter_term and filter_term != '':
|
||||
like_term = func.lower(f"%{filter_term}%")
|
||||
q = select(self.CredentialsTable).filter(
|
||||
func.lower(self.CredentialsTable.c.username).like(like_term)
|
||||
)
|
||||
# otherwise return all credentials
|
||||
else:
|
||||
q = select(self.CredentialsTable)
|
||||
|
||||
results = self.sess.execute(q).all()
|
||||
return results
|
||||
|
||||
def get_credential(self, cred_type, username, password):
|
||||
|
||||
q = select(self.CredentialsTable).filter(
|
||||
self.CredentialsTable.c.username == username,
|
||||
self.CredentialsTable.c.password == password,
|
||||
self.CredentialsTable.c.credtype == cred_type
|
||||
)
|
||||
results = self.sess.execute(q).first()
|
||||
return results.id
|
||||
|
||||
def is_host_valid(self, host_id):
|
||||
"""
|
||||
Check if this host ID is valid.
|
||||
"""
|
||||
q = select(self.HostsTable).filter(
|
||||
self.HostsTable.c.id == host_id
|
||||
)
|
||||
results = self.sess.execute(q).all()
|
||||
return len(results) > 0
|
||||
|
||||
def get_hosts(self, filter_term=None):
|
||||
"""
|
||||
Return hosts from the database.
|
||||
"""
|
||||
q = select(self.HostsTable)
|
||||
|
||||
# if we're returning a single host by ID
|
||||
if self.is_host_valid(filter_term):
|
||||
q = q.filter(
|
||||
self.HostsTable.c.id == filter_term
|
||||
)
|
||||
results = self.sess.execute(q).first()
|
||||
# all() returns a list, so we keep the return format the same so consumers don't have to guess
|
||||
return [results]
|
||||
# if we're filtering by host
|
||||
elif filter_term and filter_term != "":
|
||||
like_term = func.lower(f"%{filter_term}%")
|
||||
q = q.filter(
|
||||
self.HostsTable.c.host.like(like_term)
|
||||
)
|
||||
results = self.sess.execute(q).all()
|
||||
cme_logger.debug(f"SSH get_hosts() - results: {results}")
|
||||
return results
|
||||
|
||||
def is_user_valid(self, cred_id):
|
||||
"""
|
||||
Check if this User ID is valid.
|
||||
"""
|
||||
q = select(self.CredentialsTable).filter(
|
||||
self.CredentialsTable.c.id == cred_id
|
||||
)
|
||||
results = self.sess.execute(q).all()
|
||||
return len(results) > 0
|
||||
|
||||
def get_users(self, filter_term=None):
|
||||
q = select(self.CredentialsTable)
|
||||
|
||||
if self.is_user_valid(filter_term):
|
||||
q = q.filter(
|
||||
self.CredentialsTable.c.id == filter_term
|
||||
)
|
||||
# if we're filtering by username
|
||||
elif filter_term and filter_term != '':
|
||||
like_term = func.lower(f"%{filter_term}%")
|
||||
q = q.filter(
|
||||
func.lower(self.CredentialsTable.c.username).like(like_term)
|
||||
)
|
||||
results = self.sess.execute(q).all()
|
||||
return results
|
||||
|
||||
def get_user(self, domain, username):
|
||||
q = select(self.CredentialsTable).filter(
|
||||
func.lower(self.CredentialsTable.c.username) == func.lower(username)
|
||||
)
|
||||
results = self.sess.execute(q).all()
|
||||
return results
|
||||
|
||||
def add_loggedin_relation(self, cred_id, host_id, shell=False):
|
||||
relation_query = select(self.LoggedinRelationsTable).filter(
|
||||
self.LoggedinRelationsTable.c.credid == cred_id,
|
||||
self.LoggedinRelationsTable.c.hostid == host_id
|
||||
)
|
||||
results = self.sess.execute(relation_query).all()
|
||||
|
||||
# only add one if one doesn't already exist
|
||||
if not results:
|
||||
relation = {
|
||||
"credid": cred_id,
|
||||
"hostid": host_id,
|
||||
"shell": shell
|
||||
}
|
||||
try:
|
||||
cme_logger.debug(f"Inserting loggedin_relations: {relation}")
|
||||
# TODO: find a way to abstract this away to a single Upsert call
|
||||
q = Insert(self.LoggedinRelationsTable) # .returning(self.LoggedinRelationsTable.c.id)
|
||||
|
||||
self.sess.execute(
|
||||
q,
|
||||
[relation]
|
||||
) # .scalar()
|
||||
inserted_id_results = self.get_loggedin_relations(cred_id, host_id)
|
||||
cme_logger.debug(f"Checking if relation was added: {inserted_id_results}")
|
||||
return inserted_id_results[0].id
|
||||
except Exception as e:
|
||||
cme_logger.debug(f"Error inserting LoggedinRelation: {e}")
|
||||
|
||||
def get_loggedin_relations(self, cred_id=None, host_id=None, shell=None):
|
||||
q = select(self.LoggedinRelationsTable) # .returning(self.LoggedinRelationsTable.c.id)
|
||||
if cred_id:
|
||||
q = q.filter(
|
||||
self.LoggedinRelationsTable.c.credid == cred_id
|
||||
)
|
||||
if host_id:
|
||||
q = q.filter(
|
||||
self.LoggedinRelationsTable.c.hostid == host_id
|
||||
)
|
||||
if shell:
|
||||
q = q.filter(
|
||||
self.LoggedinRelationsTable.c.shell == shell
|
||||
)
|
||||
results = self.sess.execute(q).all()
|
||||
return results
|
||||
|
||||
def remove_loggedin_relations(self, cred_id=None, host_id=None):
|
||||
q = delete(self.LoggedinRelationsTable)
|
||||
if cred_id:
|
||||
q = q.filter(
|
||||
self.LoggedinRelationsTable.c.credid == cred_id
|
||||
)
|
||||
elif host_id:
|
||||
q = q.filter(
|
||||
self.LoggedinRelationsTable.c.hostid == host_id
|
||||
)
|
||||
self.sess.execute(q)
|
||||
|
|
|
@ -1,12 +1,307 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from cme.cmedb import DatabaseNavigator, print_help
|
||||
from cme.helpers.misc import validate_ntlm
|
||||
from cme.cmedb import DatabaseNavigator, print_table, print_help
|
||||
|
||||
|
||||
class navigator(DatabaseNavigator):
|
||||
def display_creds(self, creds):
|
||||
data = [["CredID", "Admin On", "Total Login", "Total Shell", "Username", "Password", "CredType"]]
|
||||
|
||||
for cred in creds:
|
||||
cred_id = cred[0]
|
||||
username = cred[1]
|
||||
password = cred[2]
|
||||
credtype = cred[3]
|
||||
|
||||
admin_links = self.db.get_admin_relations(cred_id=cred_id)
|
||||
total_users = self.db.get_loggedin_relations(cred_id=cred_id)
|
||||
total_shell = total_users = self.db.get_loggedin_relations(cred_id=cred_id, shell=True)
|
||||
|
||||
data.append([
|
||||
cred_id,
|
||||
str(len(admin_links)) + " Host(s)",
|
||||
str(len(total_users)) + " Host(s)",
|
||||
str(len(total_shell)) + " Shells(s)",
|
||||
username,
|
||||
password,
|
||||
credtype
|
||||
])
|
||||
print_table(data, title="Credentials")
|
||||
|
||||
# pull/545
|
||||
def display_hosts(self, hosts):
|
||||
data = [[
|
||||
"HostID",
|
||||
"Admins",
|
||||
"Total Users",
|
||||
"Host",
|
||||
"Port",
|
||||
'Banner',
|
||||
'OS'
|
||||
]]
|
||||
|
||||
for h in hosts:
|
||||
host_id = h[0]
|
||||
host = h[1]
|
||||
port = h[2]
|
||||
banner = h[3]
|
||||
os = h[4]
|
||||
|
||||
admin_users = self.db.get_admin_relations(host_id=host_id)
|
||||
total_users = self.db.get_loggedin_relations(host_id=host_id)
|
||||
data.append(
|
||||
[
|
||||
host_id,
|
||||
str(len(admin_users)) + " Cred(s)",
|
||||
str(len(total_users)) + " User(s)",
|
||||
host,
|
||||
port,
|
||||
banner,
|
||||
os
|
||||
]
|
||||
)
|
||||
print_table(data, title="Hosts")
|
||||
|
||||
def do_hosts(self, line):
|
||||
filter_term = line.strip()
|
||||
|
||||
if filter_term == "":
|
||||
hosts = self.db.get_hosts()
|
||||
self.display_hosts(hosts)
|
||||
else:
|
||||
hosts = self.db.get_hosts(filter_term=filter_term)
|
||||
|
||||
if len(hosts) > 1:
|
||||
self.display_hosts(hosts)
|
||||
elif len(hosts) == 1:
|
||||
data = [[
|
||||
"HostID",
|
||||
"Host",
|
||||
"Port",
|
||||
"Banner",
|
||||
"OS"
|
||||
]]
|
||||
host_id_list = []
|
||||
|
||||
for h in hosts:
|
||||
host_id = h[0]
|
||||
host_id_list.append(host_id)
|
||||
host = h[1]
|
||||
port = h[2]
|
||||
banner = h[3]
|
||||
os = h[4]
|
||||
|
||||
data.append(
|
||||
[
|
||||
host_id,
|
||||
host,
|
||||
port,
|
||||
banner,
|
||||
os
|
||||
]
|
||||
)
|
||||
print_table(data, title="Host")
|
||||
|
||||
admin_access_data = [["CredID", "CredType", "UserName", "Password", "Shell"]]
|
||||
nonadmin_access_data = [["CredID", "CredType", "UserName", "Password", "Shell"]]
|
||||
for host_id in host_id_list:
|
||||
admin_links = self.db.get_admin_relations(host_id=host_id)
|
||||
nonadmin_links = self.db.get_loggedin_relations(host_id=host_id)
|
||||
|
||||
for link in admin_links:
|
||||
link_id, cred_id, host_id = link
|
||||
creds = self.db.get_credentials(filter_term=cred_id)
|
||||
|
||||
for cred in creds:
|
||||
cred_id = cred[0]
|
||||
username = cred[1]
|
||||
password = cred[2]
|
||||
credtype = cred[3]
|
||||
shell = True
|
||||
|
||||
admin_access_data.append([cred_id, credtype, username, password, shell])
|
||||
|
||||
# probably a better way to do this without looping through and requesting them all again,
|
||||
# but I just want to get this working for now
|
||||
for link in nonadmin_links:
|
||||
link_id, cred_id, host_id, shell = link
|
||||
creds = self.db.get_credentials(filter_term=cred_id)
|
||||
for cred in creds:
|
||||
cred_id = cred[0]
|
||||
username = cred[1]
|
||||
password = cred[2]
|
||||
credtype = cred[3]
|
||||
shell = shell
|
||||
|
||||
cred_data = [cred_id, credtype, username, password, shell]
|
||||
|
||||
if cred_data not in admin_access_data:
|
||||
nonadmin_access_data.append(cred_data)
|
||||
|
||||
if len(nonadmin_access_data) > 1:
|
||||
print_table(nonadmin_access_data, title="Credential(s) with Non Admin Access")
|
||||
if len(admin_access_data) > 1:
|
||||
print_table(admin_access_data, title="Credential(s) with Admin Access")
|
||||
|
||||
def help_hosts(self):
|
||||
help_string = """
|
||||
hosts [filter_term]
|
||||
By default prints all hosts
|
||||
Table format:
|
||||
| 'HostID', 'Host', 'Port', 'Banner', 'OS' |
|
||||
"""
|
||||
print_help(help_string)
|
||||
|
||||
def do_creds(self, line):
|
||||
filter_term = line.strip()
|
||||
|
||||
if filter_term == "":
|
||||
creds = self.db.get_credentials()
|
||||
self.display_creds(creds)
|
||||
# TODO
|
||||
# elif filter_term.split()[0].lower() == "add":
|
||||
# # add format: "domain username password <notes> <credType> <sid>
|
||||
# args = filter_term.split()[1:]
|
||||
#
|
||||
# if len(args) == 3:
|
||||
# domain, username, password = args
|
||||
# if validate_ntlm(password):
|
||||
# self.db.add_credential("hash", domain, username, password)
|
||||
# else:
|
||||
# self.db.add_credential("plaintext", domain, username, password)
|
||||
# else:
|
||||
# print("[!] Format is 'add username password")
|
||||
# return
|
||||
elif filter_term.split()[0].lower() == "remove":
|
||||
args = filter_term.split()[1:]
|
||||
if len(args) != 1:
|
||||
print("[!] Format is 'remove <credID>'")
|
||||
return
|
||||
else:
|
||||
self.db.remove_credentials(args)
|
||||
self.db.remove_admin_relation(user_ids=args)
|
||||
elif filter_term.split()[0].lower() == "plaintext":
|
||||
creds = self.db.get_credentials(cred_type="plaintext")
|
||||
self.display_creds(creds)
|
||||
elif filter_term.split()[0].lower() == "key":
|
||||
creds = self.db.get_credentials(cred_type="key")
|
||||
self.display_creds(creds)
|
||||
else:
|
||||
creds = self.db.get_credentials(filter_term=filter_term)
|
||||
if len(creds) != 1:
|
||||
self.display_creds(creds)
|
||||
elif len(creds) == 1:
|
||||
cred_data = [["CredID", "UserName", "Password", "CredType"]]
|
||||
cred_id_list = []
|
||||
|
||||
for cred in creds:
|
||||
cred_id = cred[0]
|
||||
cred_id_list.append(cred_id)
|
||||
username = cred[1]
|
||||
password = cred[2]
|
||||
credtype = cred[3]
|
||||
|
||||
cred_data.append([cred_id, username, password, credtype])
|
||||
print_table(cred_data, title='Credential(s)')
|
||||
|
||||
admin_access_data = [["HostID", "Host", "Port", "Banner", "OS", "Shell"]]
|
||||
nonadmin_access_data = [["HostID", "Host", "Port", "Banner", "OS", "Shell"]]
|
||||
|
||||
for cred_id in cred_id_list:
|
||||
admin_links = self.db.get_admin_relations(cred_id=cred_id)
|
||||
nonadmin_links = self.db.get_loggedin_relations(cred_id=cred_id)
|
||||
|
||||
for link in admin_links:
|
||||
link_id, cred_id, host_id = link
|
||||
hosts = self.db.get_hosts(host_id)
|
||||
for h in hosts:
|
||||
host_id = h[0]
|
||||
host = h[1]
|
||||
port = h[2]
|
||||
banner = h[3]
|
||||
os = h[4]
|
||||
shell = True # if we have root via SSH, we know it's a shell
|
||||
|
||||
admin_access_data.append([host_id, host, port, banner, os, shell])
|
||||
|
||||
# probably a better way to do this without looping through and requesting them all again,
|
||||
# but I just want to get this working for now
|
||||
for link in nonadmin_links:
|
||||
link_id, cred_id, host_id, shell = link
|
||||
hosts = self.db.get_hosts(host_id)
|
||||
for h in hosts:
|
||||
host_id = h[0]
|
||||
host = h[1]
|
||||
port = h[2]
|
||||
banner = h[3]
|
||||
os = h[4]
|
||||
host_data = [host_id, host, port, banner, os, shell]
|
||||
if host_data not in admin_access_data:
|
||||
nonadmin_access_data.append(host_data)
|
||||
|
||||
# we look if it's greater than one because the header row always exists
|
||||
if len(nonadmin_access_data) > 1:
|
||||
print_table(nonadmin_access_data, title="Non-Admin Access to Host(s)")
|
||||
if len(admin_access_data) > 1:
|
||||
print_table(admin_access_data, title="Admin Access to Host(s)")
|
||||
|
||||
def help_creds(self):
|
||||
help_string = """
|
||||
creds [add|remove|plaintext|key|filter_term]
|
||||
By default prints all creds
|
||||
Table format:
|
||||
| 'CredID', 'Admin On', 'CredType', 'UserName', 'Password', 'Key' (if key type) |
|
||||
Subcommands:
|
||||
add - format: "add username password <notes> <credType>"
|
||||
remove - format: "remove <credID>"
|
||||
plaintext - prints plaintext creds
|
||||
key - prints ssh key creds
|
||||
filter_term - filters creds with filter_term
|
||||
If a single credential is returned (e.g. `creds 15`, it prints the following tables:
|
||||
Credential(s) | 'CredID', 'CredType', 'UserName', 'Password', 'Key' |
|
||||
Admin Access to Host(s) | 'HostID', 'Host', 'OS', 'Banner'
|
||||
Otherwise, it prints the default credential table from a `like` query on the `username` column
|
||||
"""
|
||||
print_help(help_string)
|
||||
|
||||
def display_keys(self, keys):
|
||||
data = [[
|
||||
"Key ID",
|
||||
"Cred ID",
|
||||
"Key Data"
|
||||
]]
|
||||
for key in keys:
|
||||
data.append([key[0], key[1], key[2]])
|
||||
print_table(data, "Keys")
|
||||
|
||||
def do_keys(self, line):
|
||||
filter_term = line.strip()
|
||||
|
||||
if filter_term == "":
|
||||
keys = self.db.get_keys()
|
||||
self.display_keys(keys)
|
||||
elif filter_term == "cred_id":
|
||||
cred_id = filter_term.split()[1]
|
||||
keys = self.db.get_keys(cred_id=cred_id)
|
||||
self.display_keys(keys)
|
||||
else:
|
||||
key_id = filter_term
|
||||
keys = self.db.get_keys(key_id=key_id)
|
||||
self.display_keys(keys)
|
||||
|
||||
def help_keys(self):
|
||||
help_string = """
|
||||
list SSH keys
|
||||
keys [id]
|
||||
"""
|
||||
print_help(help_string)
|
||||
|
||||
def do_clear_database(self, line):
|
||||
if input("This will destroy all data in the current database, are you SURE you want to run this? (y/n): ") == "y":
|
||||
if input(
|
||||
"This will destroy all data in the current database, are you SURE you want to run this? (y/n): "
|
||||
) == "y":
|
||||
self.db.clear_database()
|
||||
|
||||
def help_clear_database(self):
|
||||
|
@ -16,3 +311,24 @@ class navigator(DatabaseNavigator):
|
|||
YOU CANNOT UNDO THIS COMMAND
|
||||
"""
|
||||
print_help(help_string)
|
||||
|
||||
@staticmethod
|
||||
def complete_hosts(self, text, line):
|
||||
"""
|
||||
Tab-complete 'hosts' commands.
|
||||
"""
|
||||
commands = ["add", "remove"]
|
||||
|
||||
mline = line.partition(' ')[2]
|
||||
offs = len(mline) - len(text)
|
||||
return [s[offs:] for s in commands if s.startswith(mline)]
|
||||
|
||||
def complete_creds(self, text, line):
|
||||
"""
|
||||
Tab-complete 'creds' commands.
|
||||
"""
|
||||
commands = ["add", "remove", "key", "plaintext"]
|
||||
|
||||
mline = line.partition(' ')[2]
|
||||
offs = len(mline) - len(text)
|
||||
return [s[offs:] for s in commands if s.startswith(mline)]
|
||||
|
|
Loading…
Reference in New Issue