From 593b9090c904b0547edbf7f9a556752594cd8bb7 Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Wed, 3 May 2023 16:31:54 -0400 Subject: [PATCH 01/12] refactor(linting): initial perflint linting --- cme/cmedb.py | 147 ++++++++++++++++++++++--------------------- cme/connection.py | 27 ++++---- cme/crackmapexec.py | 98 ++++++++++++++--------------- cme/first_run.py | 29 +++++---- cme/logger.py | 13 ++-- cme/protocols/smb.py | 79 +++++++++-------------- cme/servers/smb.py | 7 +-- 7 files changed, 193 insertions(+), 207 deletions(-) diff --git a/cme/cmedb.py b/cme/cmedb.py index 49c0a55a..eaaae14e 100644 --- a/cme/cmedb.py +++ b/cme/cmedb.py @@ -5,8 +5,11 @@ import cmd import configparser import csv import os +from os import listdir +from os.path import exists +from os.path import join as path_join import shutil -import sqlite3 +from sqlite3 import connect import sys from textwrap import dedent @@ -70,7 +73,10 @@ def complete_import(text, line): """ Tab-complete 'import' commands """ - commands = ["empire", "metasploit"] + commands = ( + "empire", + "metasploit" + ) mline = line.partition(" ")[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)] @@ -80,7 +86,7 @@ def complete_export(text, line): """ Tab-complete 'creds' commands. """ - commands = [ + commands = ( "creds", "plaintext", "hashes", @@ -88,7 +94,7 @@ def complete_export(text, line): "local_admins", "signing", "keys", - ] + ) mline = line.partition(" ")[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)] @@ -111,7 +117,8 @@ class DatabaseNavigator(cmd.Cmd): self.db.shutdown_db() sys.exit() - def help_exit(self): + @staticmethod + def help_exit(): help_string = """ Exits """ @@ -131,21 +138,19 @@ class DatabaseNavigator(cmd.Cmd): # Users if command == "creds": if len(line) < 3: - print( - "[-] invalid arguments, export creds " - ) + print("[-] invalid arguments, export creds ") return filename = line[2] creds = self.db.get_credentials() - csv_header = [ + csv_header = ( "id", "domain", "username", "password", "credtype", "pillaged_from", - ] + ) if line[1].lower() == "simple": write_csv(filename, csv_header, creds) @@ -173,12 +178,10 @@ class DatabaseNavigator(cmd.Cmd): # Hosts elif command == "hosts": if len(line) < 3: - print( - "[-] invalid arguments, export hosts " - ) + print("[-] invalid arguments, export hosts ") return - csv_header_simple = [ + csv_header_simple = ( "id", "ip", "hostname", @@ -187,8 +190,8 @@ class DatabaseNavigator(cmd.Cmd): "dc", "smbv1", "signing", - ] - csv_header_detailed = [ + ) + csv_header_detailed = ( "id", "ip", "hostname", @@ -200,7 +203,7 @@ class DatabaseNavigator(cmd.Cmd): "spooler", "zerologon", "petitpotam", - ] + ) filename = line[2] if line[1].lower() == "simple": @@ -222,13 +225,19 @@ class DatabaseNavigator(cmd.Cmd): # Shares elif command == "shares": if len(line) < 3: - print( - "[-] invalid arguments, export shares " - ) + print("[-] invalid arguments, export shares ") return shares = self.db.get_shares() - csv_header = ["id", "host", "userid", "name", "remark", "read", "write"] + csv_header = ( + "id", + "host", + "userid", + "name", + "remark", + "read", + "write" + ) filename = line[2] if line[1].lower() == "simple": @@ -240,7 +249,7 @@ class DatabaseNavigator(cmd.Cmd): for share in shares: user = self.db.get_users(share[2])[0] - entry = [ + entry = ( share[0], # shareID self.db.get_hosts(share[1])[0][2], # hosts f"{user[1]}\{user[2]}", # userID @@ -248,7 +257,7 @@ class DatabaseNavigator(cmd.Cmd): share[4], # remark bool(share[5]), # read bool(share[6]), # write - ] + ) formatted_shares.append(entry) write_csv(filename, csv_header, formatted_shares) else: @@ -258,14 +267,16 @@ class DatabaseNavigator(cmd.Cmd): # Local Admin elif command == "local_admins": if len(line) < 3: - print( - "[-] invalid arguments, export local_admins " - ) + print("[-] invalid arguments, export local_admins ") return # These values don't change between simple and detailed local_admins = self.db.get_admin_relations() - csv_header = ["id", "userid", "host"] + csv_header = ( + "id", + "userid", + "host" + ) filename = line[2] if line[1].lower() == "simple": @@ -275,11 +286,11 @@ class DatabaseNavigator(cmd.Cmd): for entry in local_admins: user = self.db.get_users(filter_term=entry[1])[0] - formatted_entry = [ + formatted_entry = ( entry[0], # Entry ID f"{user[1]}/{user[2]}", # DOMAIN/Username self.db.get_hosts(filter_term=entry[2])[0][2], # Hostname - ] + ) # Can't modify a tuple which is what self.db.get_admin_relations() returns formatted_local_admins.append(formatted_entry) write_csv(filename, csv_header, formatted_local_admins) @@ -289,14 +300,12 @@ class DatabaseNavigator(cmd.Cmd): print("[+] Local Admins exported") elif command == "dpapi": if len(line) < 3: - print( - "[-] invalid arguments, export dpapi " - ) + print("[-] invalid arguments, export dpapi ") return # These values don't change between simple and detailed dpapi_secrets = self.db.get_dpapi_secrets() - csv_header = [ + csv_header = ( "id", "host", "dpapi_type", @@ -304,7 +313,7 @@ class DatabaseNavigator(cmd.Cmd): "username", "password", "url", - ] + ) filename = line[2] if line[1].lower() == "simple": @@ -312,7 +321,7 @@ class DatabaseNavigator(cmd.Cmd): elif line[1].lower() == "detailed": formatted_dpapi_secret = [] for entry in dpapi_secrets: - formatted_entry = [ + formatted_entry = ( entry[0], # Entry ID self.db.get_hosts(filter_term=entry[1])[0][2], # Hostname entry[2], # DPAPI type @@ -320,12 +329,12 @@ class DatabaseNavigator(cmd.Cmd): entry[4], # Username entry[5], # Password entry[6], # URL - ] + ) # Can't modify a tuple which is what self.db.get_admin_relations() returns formatted_dpapi_secret.append(formatted_entry) write_csv(filename, csv_header, formatted_dpapi_secret) else: - print("[-] No such export option: %s" % line[1]) + print(f"[-] No such export option: {line[1]}") return print("[+] DPAPI secrets exported") elif command == "keys": @@ -337,11 +346,10 @@ class DatabaseNavigator(cmd.Cmd): filename = line[2] write_list(filename, writable_keys) else: - print( - "[-] Invalid argument, specify creds, hosts, local_admins, shares or dpapi" - ) + print("[-] Invalid argument, specify creds, hosts, local_admins, shares or dpapi") - def help_export(self): + @staticmethod + def help_export(): help_string = """ export [creds|hosts|local_admins|shares|signing|keys] [simple|detailed|*] [filename] Exports information to a specified file @@ -358,7 +366,9 @@ class DatabaseNavigator(cmd.Cmd): return if line == "empire": - headers = {"Content-Type": "application/json"} + headers = { + "Content-Type": "application/json" + } # Pull the username and password from the config file payload = { "username": self.config.get("Empire", "username"), @@ -436,8 +446,8 @@ class CMEDBMenu(cmd.Cmd): if not proto: return - proto_db_path = os.path.join(WORKSPACE_DIR, self.workspace, proto + ".db") - if os.path.exists(proto_db_path): + proto_db_path = path_join(WORKSPACE_DIR, self.workspace, f"{proto}.db") + if exists(proto_db_path): self.conn = create_db_engine(proto_db_path) db_nav_object = self.p_loader.load_protocol(self.protocols[proto]["nvpath"]) db_object = self.p_loader.load_protocol(self.protocols[proto]["dbpath"]) @@ -451,7 +461,8 @@ class CMEDBMenu(cmd.Cmd): except UserExitedProto: pass - def help_proto(self): + @staticmethod + def help_proto(): help_string = """ proto [smb|mssql|winrm] *unimplemented protocols: ftp, rdp, ldap, ssh @@ -474,27 +485,30 @@ class CMEDBMenu(cmd.Cmd): self.do_workspace(new_workspace) elif subcommand == "list": print("[*] Enumerating Workspaces") - for workspace in os.listdir(os.path.join(WORKSPACE_DIR)): + for workspace in listdir(path_join(WORKSPACE_DIR)): if workspace == self.workspace: print("==> " + workspace) else: print(workspace) - elif os.path.exists(os.path.join(WORKSPACE_DIR, line)): + elif exists(path_join(WORKSPACE_DIR, line)): self.config.set("CME", "workspace", line) self.write_configfile() self.workspace = line self.prompt = f"cmedb ({line}) > " - def help_workspace(self): + @staticmethod + def help_workspace(): help_string = """ workspace [create | workspace list | workspace ] """ print_help(help_string) - def do_exit(self, line): + @staticmethod + def do_exit(line): sys.exit() - def help_exit(self): + @staticmethod + def help_exit(): help_string = """ Exits """ @@ -502,18 +516,15 @@ class CMEDBMenu(cmd.Cmd): def create_workspace(workspace_name, p_loader, protocols): - os.mkdir(os.path.join(WORKSPACE_DIR, workspace_name)) + os.mkdir(path_join(WORKSPACE_DIR, workspace_name)) for protocol in protocols.keys(): - try: - protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"]) - except KeyError: - continue - proto_db_path = os.path.join(WORKSPACE_DIR, workspace_name, protocol + ".db") + protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"]) + proto_db_path = path_join(WORKSPACE_DIR, workspace_name, f"{protocol}.db") - if not os.path.exists(proto_db_path): + if not exists(proto_db_path): print(f"[*] Initializing {protocol.upper()} protocol database") - conn = sqlite3.connect(proto_db_path) + conn = connect(proto_db_path) c = conn.cursor() # try to prevent some weird sqlite I/O errors @@ -528,27 +539,23 @@ def create_workspace(workspace_name, p_loader, protocols): def delete_workspace(workspace_name): - shutil.rmtree(os.path.join(WORKSPACE_DIR, workspace_name)) + shutil.rmtree(path_join(WORKSPACE_DIR, workspace_name)) def initialize_db(logger): - if not os.path.exists(os.path.join(WS_PATH, "default")): + if not exists(path_join(WS_PATH, "default")): logger.debug("Creating default workspace") - os.mkdir(os.path.join(WS_PATH, "default")) + os.mkdir(path_join(WS_PATH, "default")) p_loader = ProtocolLoader() protocols = p_loader.get_protocols() for protocol in protocols.keys(): - try: - protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"]) - except KeyError: - continue + protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"]) + proto_db_path = path_join(WS_PATH, "default", f"{protocol}.db") - proto_db_path = os.path.join(WS_PATH, "default", protocol + ".db") - - if not os.path.exists(proto_db_path): + if not exists(proto_db_path): logger.debug(f"Initializing {protocol.upper()} protocol database") - conn = sqlite3.connect(proto_db_path) + conn = connect(proto_db_path) c = conn.cursor() # try to prevent some weird sqlite I/O errors c.execute( @@ -564,7 +571,7 @@ def initialize_db(logger): def main(): - if not os.path.exists(CONFIG_PATH): + if not exists(CONFIG_PATH): print("[-] Unable to find config file") sys.exit(1) try: diff --git a/cme/connection.py b/cme/connection.py index 7e87b585..218cb110 100755 --- a/cme/connection.py +++ b/cme/connection.py @@ -3,7 +3,8 @@ import random import socket -import sys +from socket import AF_INET, AF_INET6, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME +from socket import getaddrinfo from os.path import isfile from threading import BoundedSemaphore from functools import wraps @@ -21,23 +22,23 @@ user_failed_logins = {} def gethost_addrinfo(hostname): try: - for res in socket.getaddrinfo( + for res in getaddrinfo( hostname, None, - socket.AF_INET6, - socket.SOCK_DGRAM, - socket.IPPROTO_IP, - socket.AI_CANONNAME, + AF_INET6, + SOCK_DGRAM, + IPPROTO_IP, + AI_CANONNAME, ): af, socktype, proto, canonname, sa = res except socket.gaierror: - for res in socket.getaddrinfo( + for res in getaddrinfo( hostname, None, - socket.AF_INET, - socket.SOCK_DGRAM, - socket.IPPROTO_IP, - socket.AI_CANONNAME, + AF_INET, + SOCK_DGRAM, + IPPROTO_IP, + AI_CANONNAME, ): af, socktype, proto, canonname, sa = res if canonname == "": @@ -277,9 +278,7 @@ class connection(object): return True elif self.hash_login(domain, username, password): return True - elif cred_type == "plaintext" and not self.over_fail_limit( - username - ): + elif cred_type == "plaintext" and not self.over_fail_limit(username): if self.args.kerberos: if self.kerberos_login( domain, diff --git a/cme/crackmapexec.py b/cme/crackmapexec.py index 15b0aa31..c2ecb86a 100755 --- a/cme/crackmapexec.py +++ b/cme/crackmapexec.py @@ -15,16 +15,17 @@ from cme.paths import CME_PATH, DATA_PATH from cme.console import cme_console from cme.logger import cme_logger from cme.config import cme_config, cme_workspace, config_log, ignore_opsec -from concurrent.futures import ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor, as_completed import asyncio import cme.helpers.powershell as powershell import shutil import webbrowser import random import os -import sys +from os.path import exists +from os.path import join as path_join +from sys import exit import logging -import concurrent.futures import sqlalchemy from rich.progress import Progress @@ -34,7 +35,7 @@ except: print( "Incompatible python version, try with another python version or another binary 3.8 / 3.9 / 3.10 / 3.11 that match your python version (python -V)" ) - sys.exit(1) + exit(1) def create_db_engine(db_path): @@ -64,7 +65,7 @@ async def start_run(protocol_obj, args, db, targets): executor.submit(protocol_obj, args, db, target) for target in targets ] - for future in concurrent.futures.as_completed(futures): + for future in as_completed(futures): current += 1 progress.update(tasks, completed=current) @@ -84,7 +85,8 @@ def main(): cme_logger.logger.setLevel(logging.ERROR) root_logger.setLevel(logging.ERROR) - # if these are the same, it might double log to file (two FileHandlers will be added), but this should never happen by accident + # if these are the same, it might double log to file (two FileHandlers will be added) + # but this should never happen by accident if config_log: cme_logger.add_file_log() if hasattr(args, "log") and args.log: @@ -94,16 +96,16 @@ def main(): if args.darrell: links = ( - open(os.path.join(DATA_PATH, "videos_for_darrell.harambe")) + open(path_join(DATA_PATH, "videos_for_darrell.harambe")) .read() .splitlines() ) try: webbrowser.open(random.choice(links)) - sys.exit(1) + exit(1) except Exception as e: cme_logger.error(f"Error opening le dank meme: {e}") - sys.exit(1) + exit(1) if args.protocol == "ssh": if args.key_file: @@ -111,15 +113,19 @@ def main(): cme_logger.fail( f"Password is required, even if a key file is used - if no passphrase for key, use `-p ''`" ) - sys.exit(1) + exit(1) if args.use_kcache and not os.environ.get("KRB5CCNAME"): cme_logger.error("KRB5CCNAME environment variable is not set") - sys.exit(1) + exit(1) module_server = None targets = [] - server_port_dict = {"http": 80, "https": 443, "smb": 445} + server_port_dict = { + "http": 80, + "https": 443, + "smb": 445 + } if hasattr(args, "cred_id") and args.cred_id: for cred_id in args.cred_id: @@ -131,11 +137,11 @@ def main(): args.cred_id.remove(cred_id) except Exception as e: cme_logger.error(f"Error parsing database credential id: {e}") - sys.exit(1) + exit(1) if hasattr(args, "target") and args.target: for target in args.target: - if os.path.exists(target): + if exists(target): target_file_type = identify_target_file(target) if target_file_type == "nmap": targets.extend(parse_nmap_xml(target, args.protocol)) @@ -169,7 +175,7 @@ def main(): protocol_db_object = getattr(p_loader.load_protocol(protocol_db_path), "database") cme_logger.debug(f"Protocol DB Object: {protocol_db_object}") - db_path = os.path.join(CME_PATH, "workspaces", cme_workspace, args.protocol + ".db") + db_path = path_join(CME_PATH, "workspaces", cme_workspace, f"{args.protocol}.db") cme_logger.debug(f"DB Path: {db_path}") db_engine = create_db_engine(db_path) @@ -186,13 +192,13 @@ def main(): for name, props in sorted(modules.items()): if args.protocol in props["supported_protocols"]: cme_logger.display(f"{name:<25} {props['description']}") - sys.exit(0) + exit(0) elif args.module and args.show_module_options: for module in args.module: cme_logger.display( f"{module} module options:\n{modules[module]['options']}" ) - sys.exit(0) + exit(0) elif args.module: cme_logger.debug(f"Modules to be Loaded: {args.module}, {type(args.module)}") for m in map(str.lower, args.module): @@ -205,31 +211,23 @@ def main(): if not module.opsec_safe: if ignore_opsec: - cme_logger.debug( - f"ignore_opsec is set in the configuration, skipping prompt" - ) - cme_logger.display( - f"Ignore OPSEC in configuration is set and OPSEC unsafe module loaded" - ) + cme_logger.debug(f"ignore_opsec is set in the configuration, skipping prompt") + cme_logger.display(f"Ignore OPSEC in configuration is set and OPSEC unsafe module loaded") else: - ans = input( - highlight( - "[!] Module is not opsec safe, are you sure you want to run this? [Y/n] ", - "red", - ) - ) + ans = input(highlight( + "[!] Module is not opsec safe, are you sure you want to run this? [Y/n] ", + "red", + )) if ans.lower() not in ["y", "yes", ""]: - sys.exit(1) + exit(1) if not module.multiple_hosts and len(targets) > 1: - ans = input( - highlight( - "[!] Running this module on multiple hosts doesn't really make any sense, are you sure you want to continue? [Y/n] ", - "red", - ) - ) + ans = input(highlight( + "[!] Running this module on multiple hosts doesn't really make any sense, are you sure you want to continue? [Y/n] ", + "red", + )) if ans.lower() not in ["y", "yes", ""]: - sys.exit(1) + exit(1) if hasattr(module, "on_request") or hasattr(module, "has_response"): if hasattr(module, "required_server"): @@ -240,7 +238,11 @@ def main(): # loading a module server multiple times will obviously fail try: - context = Context(db, cme_logger, args) + context = Context( + db, + cme_logger, + args + ) module_server = CMEServer( module, context, @@ -254,27 +256,21 @@ def main(): except Exception as e: cme_logger.error(f"Error loading module server for {module}: {e}") - cme_logger.debug( - f"proto_object: {protocol_object}, type: {type(protocol_object)}" - ) + cme_logger.debug(f"proto_object: {protocol_object}, type: {type(protocol_object)}") cme_logger.debug(f"proto object dir: {dir(protocol_object)}") # get currently set modules, otherwise default to empty list current_modules = getattr(protocol_object, "module", []) current_modules.append(module) setattr(protocol_object, "module", current_modules) - cme_logger.debug( - f"proto object module after adding: {protocol_object.module}" - ) + cme_logger.debug(f"proto object module after adding: {protocol_object.module}") if hasattr(args, "ntds") and args.ntds and not args.userntds: - ans = input( - highlight( - "[!] Dumping the ntds can crash the DC on Windows Server 2019. Use the option --user to dump a specific user safely or the module -M ntdsutil [Y/n] ", - "red", - ) - ) + ans = input(highlight( + "[!] Dumping the ntds can crash the DC on Windows Server 2019. Use the option --user to dump a specific user safely or the module -M ntdsutil [Y/n] ", + "red", + )) if ans.lower() not in ["y", "yes", ""]: - sys.exit(1) + exit(1) try: asyncio.run(start_run(protocol_object, args, db, targets)) diff --git a/cme/first_run.py b/cme/first_run.py index 8159ef7e..d9b7d8b3 100755 --- a/cme/first_run.py +++ b/cme/first_run.py @@ -2,6 +2,9 @@ # -*- coding: utf-8 -*- import os +from os import mkdir +from os.path import exists +from os.path import join as path_join import shutil import configparser from configparser import NoSectionError, NoOptionError @@ -11,32 +14,32 @@ from cme.logger import cme_logger def first_run_setup(logger=cme_logger): - if not os.path.exists(TMP_PATH): - os.mkdir(TMP_PATH) + if not exists(TMP_PATH): + mkdir(TMP_PATH) - if not os.path.exists(CME_PATH): + if not exists(CME_PATH): logger.display("First time use detected") logger.display("Creating home directory structure") - os.mkdir(CME_PATH) + mkdir(CME_PATH) - folders = [ + folders = ( "logs", "modules", "protocols", "workspaces", "obfuscated_scripts", "screenshots", - ] + ) for folder in folders: - if not os.path.exists(os.path.join(CME_PATH, folder)): + if not exists(path_join(CME_PATH, folder)): logger.display(f"Creating missing folder {folder}") - os.mkdir(os.path.join(CME_PATH, folder)) + mkdir(path_join(CME_PATH, folder)) initialize_db(logger) - if not os.path.exists(CONFIG_PATH): + if not exists(CONFIG_PATH): logger.display("Copying default configuration file") - default_path = os.path.join(DATA_PATH, "cme.conf") + default_path = path_join(DATA_PATH, "cme.conf") shutil.copy(default_path, CME_PATH) else: # This is just a quick check to make sure the config file isn't the old 3.x format @@ -52,10 +55,10 @@ def first_run_setup(logger=cme_logger): logger.display( "Old configuration file detected, replacing with new version" ) - default_path = os.path.join(DATA_PATH, "cme.conf") + default_path = path_join(DATA_PATH, "cme.conf") shutil.copy(default_path, CME_PATH) - # if not os.path.exists(CERT_PATH): + # if not exists(CERT_PATH): # logger.display('Generating SSL certificate') # try: # check_output(['openssl', 'help'], stderr=PIPE) @@ -66,7 +69,7 @@ def first_run_setup(logger=cme_logger): # except OSError as e: # if e.errno == errno.ENOENT: # logger.error('OpenSSL command line utility is not installed, could not generate certificate, using default certificate') - # default_path = os.path.join(DATA_PATH, 'default.pem') + # default_path = path_join(DATA_PATH, 'default.pem') # shutil.copy(default_path, CERT_PATH) # else: # logger.error('Error while generating SSL certificate: {}'.format(e)) diff --git a/cme/logger.py b/cme/logger.py index f4953bd6..7c1d0023 100755 --- a/cme/logger.py +++ b/cme/logger.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- import logging +from logging import LogRecord from logging.handlers import RotatingFileHandler import os.path import sys @@ -148,10 +149,10 @@ class CMEAdapter(logging.LoggerAdapter): if self.logger.getEffectiveLevel() >= logging.INFO: # will be 0 if it's just the console output, so only do this if we actually have file loggers if len(self.logger.handlers): - for handler in self.logger.handlers: - try: + try: + for handler in self.logger.handlers: handler.handle( - logging.LogRecord( + LogRecord( "cme", 20, "", @@ -161,10 +162,8 @@ class CMEAdapter(logging.LoggerAdapter): exc_info=None, ) ) - except Exception as e: - self.logger.fail( - f"Issue while trying to custom print handler: {e}" - ) + except Exception as e: + self.logger.fail(f"Issue while trying to custom print handler: {e}") else: self.logger.info(text) diff --git a/cme/protocols/smb.py b/cme/protocols/smb.py index d025feae..db443133 100755 --- a/cme/protocols/smb.py +++ b/cme/protocols/smb.py @@ -60,6 +60,7 @@ from datetime import datetime from functools import wraps from traceback import format_exc import logging +from json import loads smb_share_name = gen_random_string(5).upper() smb_server = None @@ -675,33 +676,25 @@ class smb(connection): from impacket.ldap import ldapasn1 as ldapasn1_impacket - results = [ - r for r in results if isinstance(r, ldapasn1_impacket.SearchResultEntry) - ] + results = [r for r in results if isinstance(r, ldapasn1_impacket.SearchResultEntry)] if len(results) != 0: for host in results: values = { - str(attr["type"]).lower(): str(attr["vals"][0]) - for attr in host["attributes"] + str(attr["type"]).lower(): str(attr["vals"][0]) for attr in host["attributes"] } if "mslaps-encryptedpassword" in values: self.logger.fail( "LAPS password is encrypted and currently CrackMapExec doesn't support the decryption..." ) - return False elif "mslaps-password" in values: - from json import loads - r = loads(values["mslaps-password"]) msMCSAdmPwd = r["p"] username = r["n"] elif "ms-mcs-admpwd" in values: msMCSAdmPwd = values["ms-mcs-admpwd"] else: - self.logger.fail( - "No result found with attribute ms-MCS-AdmPwd or msLAPS-Password" - ) + self.logger.fail("No result found with attribute ms-MCS-AdmPwd or msLAPS-Password") logging.debug( f"Host: {sAMAccountName:<20} Password: {msMCSAdmPwd} {self.hostname}" ) @@ -1775,9 +1768,7 @@ class smb(connection): policy_handle, lsad.POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation, ) - domain_sid = resp["PolicyInformation"]["PolicyAccountDomainInfo"][ - "DomainSid" - ].formatCanonical() + domain_sid = resp["PolicyInformation"]["PolicyAccountDomainInfo"]["DomainSid"].formatCanonical() so_far = 0 simultaneous = 1000 @@ -1792,10 +1783,13 @@ class smb(connection): sids = list() for i in range(so_far, so_far + sids_to_check): - sids.append(domain_sid + "-%d" % i) + sids.append(f"{domain_sid}-{i:d}") try: lsat.hLsarLookupSids( - dce, policy_handle, sids, lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta + dce, + policy_handle, + sids, + lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta ) except DCERPCException as e: if str(e).find("STATUS_NONE_MAPPED") >= 0: @@ -1809,28 +1803,22 @@ class smb(connection): for n, item in enumerate(resp["TranslatedNames"]["Names"]): if item["Use"] != SID_NAME_USE.SidTypeUnknown: rid = so_far + n - domain = resp["ReferencedDomains"]["Domains"][item["DomainIndex"]][ - "Name" - ] + domain = resp["ReferencedDomains"]["Domains"][item["DomainIndex"]]["Name"] user = item["Name"] sid_type = SID_NAME_USE.enumItems(item["Use"]).name self.logger.highlight(f"{rid}: {domain}\\{user} ({sid_type})") - entries.append( - { - "rid": rid, - "domain": domain, - "username": user, - "sidtype": sid_type, - } - ) + entries.append({ + "rid": rid, + "domain": domain, + "username": user, + "sidtype": sid_type, + }) so_far += simultaneous dce.disconnect() return entries def put_file(self): - self.logger.display( - f"Copying {self.args.put_file[0]} to {self.args.put_file[1]}" - ) + self.logger.display(f"Copying {self.args.put_file[0]} to {self.args.put_file[1]}") with open(self.args.put_file[0], "rb") as file: try: self.conn.putFile(self.args.share, self.args.put_file[1], file.read) @@ -1841,9 +1829,7 @@ class smb(connection): self.logger.fail(f"Error writing file to share {self.args.share}: {e}") def get_file(self): - self.logger.display( - f"Copying {self.args.get_file[0]} to {self.args.get_file[1]}" - ) + self.logger.display(f"Copying {self.args.get_file[0]} to {self.args.get_file[1]}") file_handle = self.args.get_file[1] if self.args.append_host: file_handle = f"{self.hostname}-{self.args.get_file[1]}" @@ -1958,11 +1944,10 @@ class smb(connection): dc_conn = DPLootSMBConnection(dc_target) dc_conn.connect() # Connect to DC if dc_conn.is_admin(): - self.logger.success( - "User is Domain Administrator, exporting domain backupkey..." - ) + self.logger.success("User is Domain Administrator, exporting domain backupkey...") backupkey_triage = BackupkeyTriage( - target=dc_target, conn=dc_conn + target=dc_target, + conn=dc_conn ) backupkey = backupkey_triage.triage_backupkey() self.pvkbytes = backupkey.backupkey_v2 @@ -1995,9 +1980,7 @@ class smb(connection): plaintexts = { username: password - for _, _, username, password, _, _ in self.db.get_credentials( - cred_type="plaintext" - ) + for _, _, username, password, _, _ in self.db.get_credentials(cred_type="plaintext") } nthashes = { username: nt.split(":")[1] if ":" in nt else nt @@ -2010,9 +1993,7 @@ class smb(connection): # Collect User and Machine masterkeys try: - self.logger.display( - "Collecting User and Machine masterkeys, grab a coffee and be patient..." - ) + self.logger.display("Collecting User and Machine masterkeys, grab a coffee and be patient...") masterkeys_triage = MasterkeysTriage( target=target, conn=conn, @@ -2030,9 +2011,7 @@ class smb(connection): logging.fail("No masterkeys looted") return - self.logger.success( - f"Got {highlight(len(masterkeys))} decrypted masterkeys. Looting secrets..." - ) + self.logger.success(f"Got {highlight(len(masterkeys))} decrypted masterkeys. Looting secrets...") try: # Collect User and Machine Credentials Manager secrets @@ -2076,7 +2055,9 @@ class smb(connection): # Collect Chrome Based Browser stored secrets dump_cookies = True if self.args.dpapi == "cookies" else False browser_triage = BrowserTriage( - target=target, conn=conn, masterkeys=masterkeys + target=target, + conn=conn, + masterkeys=masterkeys ) browser_credentials, cookies = browser_triage.triage_browsers( gather_cookies=dump_cookies @@ -2108,7 +2089,9 @@ class smb(connection): try: # Collect User Internet Explorer stored secrets vaults_triage = VaultsTriage( - target=target, conn=conn, masterkeys=masterkeys + target=target, + conn=conn, + masterkeys=masterkeys ) vaults = vaults_triage.triage_vaults() except Exception as e: diff --git a/cme/servers/smb.py b/cme/servers/smb.py index 57da9355..b89333a5 100755 --- a/cme/servers/smb.py +++ b/cme/servers/smb.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- import threading +from threading import enumerate from sys import exit from impacket import smbserver @@ -27,9 +28,7 @@ class CMESMBServer(threading.Thread): except Exception as e: errno, message = e.args if errno == 98 and message == "Address already in use": - logger.error( - "Error starting SMB server on port 445: the port is already in use" - ) + logger.error("Error starting SMB server on port 445: the port is already in use") else: logger.error(f"Error starting SMB server on port 445: {message}") exit(1) @@ -46,7 +45,7 @@ class CMESMBServer(threading.Thread): def shutdown(self): # TODO: should fine the proper way # make sure all the threads are killed - for thread in threading.enumerate(): + for thread in enumerate(): if thread.is_alive(): try: self._stop() From 8e274534787bb70276cc384ab31e796cda556362 Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Wed, 3 May 2023 16:36:13 -0400 Subject: [PATCH 02/12] refactor(linting): move rdp_error_status to be a local object variable since global variable lookups are slower --- cme/protocols/rdp.py | 50 ++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/cme/protocols/rdp.py b/cme/protocols/rdp.py index 631a3c7e..d88067d9 100644 --- a/cme/protocols/rdp.py +++ b/cme/protocols/rdp.py @@ -20,23 +20,6 @@ from asyauth.common.credentials.kerberos import KerberosCredential from asyauth.common.constants import asyauthSecret from asysocks.unicomm.common.target import UniTarget, UniProto -rdp_error_status = { - "0xc0000071": "STATUS_PASSWORD_EXPIRED", - "0xc0000234": "STATUS_ACCOUNT_LOCKED_OUT", - "0xc0000072": "STATUS_ACCOUNT_DISABLED", - "0xc0000193": "STATUS_ACCOUNT_EXPIRED", - "0xc000006E": "STATUS_ACCOUNT_RESTRICTION", - "0xc000006F": "STATUS_INVALID_LOGON_HOURS", - "0xc0000070": "STATUS_INVALID_WORKSTATION", - "0xc000015B": "STATUS_LOGON_TYPE_NOT_GRANTED", - "0xc0000224": "STATUS_PASSWORD_MUST_CHANGE", - "0xc0000022": "STATUS_ACCESS_DENIED", - "0xc000006d": "STATUS_LOGON_FAILURE", - "0xc000006a": "STATUS_WRONG_PASSWORD ", - "KDC_ERR_CLIENT_REVOKED": "KDC_ERR_CLIENT_REVOKED", - "KDC_ERR_PREAUTH_FAILED": "KDC_ERR_PREAUTH_FAILED", -} - class rdp(connection): def __init__(self, args, db, host): @@ -77,6 +60,23 @@ class rdp(connection): self.target = None self.auth = None + self.rdp_error_status = { + "0xc0000071": "STATUS_PASSWORD_EXPIRED", + "0xc0000234": "STATUS_ACCOUNT_LOCKED_OUT", + "0xc0000072": "STATUS_ACCOUNT_DISABLED", + "0xc0000193": "STATUS_ACCOUNT_EXPIRED", + "0xc000006E": "STATUS_ACCOUNT_RESTRICTION", + "0xc000006F": "STATUS_INVALID_LOGON_HOURS", + "0xc0000070": "STATUS_INVALID_WORKSTATION", + "0xc000015B": "STATUS_LOGON_TYPE_NOT_GRANTED", + "0xc0000224": "STATUS_PASSWORD_MUST_CHANGE", + "0xc0000022": "STATUS_ACCESS_DENIED", + "0xc000006d": "STATUS_LOGON_FAILURE", + "0xc000006a": "STATUS_WRONG_PASSWORD ", + "KDC_ERR_CLIENT_REVOKED": "KDC_ERR_CLIENT_REVOKED", + "KDC_ERR_PREAUTH_FAILED": "KDC_ERR_PREAUTH_FAILED", + } + connection.__init__(self, args, db, host) @staticmethod @@ -364,9 +364,9 @@ class rdp(connection): except Exception as e: if "KDC_ERR" in str(e): reason = None - for word in rdp_error_status.keys(): + for word in self.rdp_error_status.keys(): if word in str(e): - reason = rdp_error_status[word] + reason = self.rdp_error_status[word] self.logger.fail( f"{domain}\\{username}{' from ccache' if useCache else ':%s' % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8)} {f'({reason})' if reason else str(e)}", color="magenta" @@ -393,9 +393,9 @@ class rdp(connection): self.logger.fail(e) else: reason = None - for word in rdp_error_status.keys(): + for word in self.rdp_error_status.keys(): if word in str(e): - reason = rdp_error_status[word] + reason = self.rdp_error_status[word] if "cannot unpack non-iterable NoneType object" == str(e): reason = "User valid but cannot connect" self.logger.fail( @@ -455,9 +455,9 @@ class rdp(connection): ) else: reason = None - for word in rdp_error_status.keys(): + for word in self.rdp_error_status.keys(): if word in str(e): - reason = rdp_error_status[word] + reason = self.rdp_error_status[word] if "cannot unpack non-iterable NoneType object" == str(e): reason = "User valid but cannot connect" self.logger.fail( @@ -517,9 +517,9 @@ class rdp(connection): ) else: reason = None - for word in rdp_error_status.keys(): + for word in self.rdp_error_status.keys(): if word in str(e): - reason = rdp_error_status[word] + reason = self.rdp_error_status[word] if "cannot unpack non-iterable NoneType object" == str(e): reason = "User valid but cannot connect" From 9428a6e85cf6b7734216611849aa955ff042f88d Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Wed, 3 May 2023 16:38:46 -0400 Subject: [PATCH 03/12] fix formatting that was missed in previous revamp --- cme/protocols/rdp.py | 86 ++++++++++---------------------------------- 1 file changed, 19 insertions(+), 67 deletions(-) diff --git a/cme/protocols/rdp.py b/cme/protocols/rdp.py index d88067d9..9a4a5084 100644 --- a/cme/protocols/rdp.py +++ b/cme/protocols/rdp.py @@ -174,21 +174,22 @@ class rdp(connection): def print_host_info(self): if self.domain is None: - self.logger.display( - f"Probably old, doesn't not support HYBRID or HYBRID_EX (nla:{self.nla})" - ) + self.logger.display(f"Probably old, doesn't not support HYBRID or HYBRID_EX (nla:{self.nla})") else: - self.logger.display( - f"{self.server_os} (name:{self.hostname}) (domain:{self.domain}) (nla:{self.nla})" - ) + self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain}) (nla:{self.nla})") return True def create_conn_obj(self): self.target = RDPTarget( - ip=self.host, domain="FAKE", timeout=self.args.rdp_timeout + ip=self.host, + domain="FAKE", + timeout=self.args.rdp_timeout ) self.auth = NTLMCredential( - secret="pass", username="user", domain="FAKE", stype=asyauthSecret.PASS + secret="pass", + username="user", + domain="FAKE", + stype=asyauthSecret.PASS ) self.check_nla() @@ -303,9 +304,7 @@ class rdp(connection): if not password: password = getenv("KRB5CCNAME") if not password else password if "/" in password: - self.logger.fail( - "Kerberos ticket need to be on the local directory" - ) + self.logger.fail("Kerberos ticket need to be on the local directory") return False ccache = CCache.loadFile(getenv("KRB5CCNAME")) ticketCreds = ccache.credentials[0] @@ -349,11 +348,7 @@ class rdp(connection): 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 "" - ), + self.mark_pwned(), ) ) if not self.args.local_auth: @@ -378,16 +373,7 @@ class rdp(connection): ) elif "Authentication failed!" in str(e): self.logger.success( - "{}\\{}:{} {}".format( - domain, - username, - password, - highlight( - f'({self.config.get("CME", "pwn3d_label")})' - if self.admin_privs - else "" - ), - ) + f"{domain}\\{username}:{password} {self.mark_pwned()}" ) elif "No such file" in str(e): self.logger.fail(e) @@ -424,16 +410,7 @@ class rdp(connection): self.admin_privs = True self.logger.success( - "{}\\{}:{} {}".format( - domain, - username, - password, - highlight( - f'({self.config.get("CME", "pwn3d_label")})' - if self.admin_privs - else "" - ), - ) + f"{domain}\\{username}:{password} {self.mark_pwned()}" ) if not self.args.local_auth: add_user_bh(username, domain, self.logger, self.config) @@ -442,16 +419,7 @@ class rdp(connection): except Exception as e: if "Authentication failed!" in str(e): self.logger.success( - "{}\\{}:{} {}".format( - domain, - username, - password, - highlight( - f'({self.config.get("CME", "pwn3d_label")})' - if self.admin_privs - else "" - ), - ) + f"{domain}\\{username}:{password} {self.mark_pwned()}" ) else: reason = None @@ -486,16 +454,7 @@ class rdp(connection): self.admin_privs = True self.logger.success( - "{}\\{}:{} {}".format( - self.domain, - username, - ntlm_hash, - highlight( - f'({self.config.get("CME", "pwn3d_label")})' - if self.admin_privs - else "" - ), - ) + f"{self.domain}\\{username}:{ntlm_hash} {self.mark_pwned()}" ) if not self.args.local_auth: add_user_bh(username, domain, self.logger, self.config) @@ -504,16 +463,7 @@ class rdp(connection): except Exception as e: if "Authentication failed!" in str(e): self.logger.success( - "{}\\{}:{} {}".format( - domain, - username, - ntlm_hash, - highlight( - f'({self.config.get("CME", "pwn3d_label")})' - if self.admin_privs - else "" - ), - ) + f"{domain}\\{username}:{ntlm_hash} {self.mark_pwned()}" ) else: reason = None @@ -537,7 +487,9 @@ class rdp(connection): async def screen(self): try: self.conn = RDPConnection( - iosettings=self.iosettings, target=self.target, credentials=self.auth + iosettings=self.iosettings, + target=self.target, + credentials=self.auth ) await self.connect_rdp() except Exception as e: From d6608ab018c1e6b06c69758e3120f4dd01e0a45b Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Wed, 3 May 2023 16:43:44 -0400 Subject: [PATCH 04/12] mssql db_navigator format fix --- cme/protocols/mssql/db_navigator.py | 147 ++++++++++++++++++++-------- 1 file changed, 105 insertions(+), 42 deletions(-) diff --git a/cme/protocols/mssql/db_navigator.py b/cme/protocols/mssql/db_navigator.py index 814d937c..5fda5a8e 100644 --- a/cme/protocols/mssql/db_navigator.py +++ b/cme/protocols/mssql/db_navigator.py @@ -7,7 +7,14 @@ from cme.cmedb import DatabaseNavigator, print_table, print_help class navigator(DatabaseNavigator): def display_creds(self, creds): - data = [["CredID", "Admin On", "CredType", "Domain", "UserName", "Password"]] + data = [[ + "CredID", + "Admin On", + "CredType", + "Domain", + "UserName", + "Password" + ]] for cred in creds: cred_id = cred[0] @@ -18,20 +25,26 @@ class navigator(DatabaseNavigator): # pillaged_from = cred[5] links = self.db.get_admin_relations(user_id=cred_id) - data.append( - [ - cred_id, - str(len(links)) + " Host(s)", - credtype, - domain, - username, - password, - ] - ) + data.append([ + cred_id, + str(len(links)) + " Host(s)", + credtype, + domain, + username, + password, + ]) print_table(data, title="Credentials") def display_hosts(self, hosts): - data = [["HostID", "Admins", "IP", "Hostname", "Domain", "OS", "DB Instances"]] + data = [[ + "HostID", + "Admins", + "IP", + "Hostname", + "Domain", + "OS", + "DB Instances" + ]] for host in hosts: host_id = host[0] ip = host[1] @@ -42,17 +55,15 @@ class navigator(DatabaseNavigator): links = self.db.get_admin_relations(host_id=host_id) - data.append( - [ - host_id, - str(len(links)) + " Cred(s)", - ip, - hostname, - domain, - os, - instances, - ] - ) + data.append([ + host_id, + str(len(links)) + " Cred(s)", + ip, + hostname, + domain, + os, + instances, + ]) print_table(data, title="Hosts") def do_hosts(self, line): @@ -67,7 +78,13 @@ class navigator(DatabaseNavigator): if len(hosts) > 1: self.display_hosts(hosts) elif len(hosts) == 1: - data = [["HostID", "IP", "Hostname", "Domain", "OS"]] + data = [[ + "HostID", + "IP", + "Hostname", + "Domain", + "OS" + ]] host_id_list = [] for host in hosts: @@ -79,11 +96,23 @@ class navigator(DatabaseNavigator): domain = host[3] os = host[4] - data.append([host_id, ip, hostname, domain, os]) + data.append([ + host_id, + ip, + hostname, + domain, + os + ]) print_table(data, title="Host(s)") - data = [["CredID", "CredType", "Domain", "UserName", "Password"]] + data = [[ + "CredID", + "CredType", + "Domain", + "UserName", + "Password" + ]] for host_id in host_id_list: links = self.db.get_admin_relations(host_id=host_id) @@ -99,7 +128,13 @@ class navigator(DatabaseNavigator): credtype = cred[4] # pillaged_from = cred[5] - data.append([cred_id, credtype, domain, username, password]) + data.append([ + cred_id, + credtype, + domain, + username, + password + ]) print_table(data, title="Credential(s) with Admin Access") def do_creds(self, line): @@ -137,7 +172,13 @@ class navigator(DatabaseNavigator): self.display_creds(creds) else: creds = self.db.get_credentials(filter_term=filter_term) - data = [["CredID", "CredType", "Domain", "UserName", "Password"]] + data = [[ + "CredID", + "CredType", + "Domain", + "UserName", + "Password" + ]] cred_id_list = [] for cred in creds: @@ -149,11 +190,23 @@ class navigator(DatabaseNavigator): username = cred[3] password = cred[4] - data.append([cred_id, credType, domain, username, password]) + data.append([ + cred_id, + credType, + domain, + username, + password + ]) print_table(data, title="Credential(s)") - data = [["HostID", "IP", "Hostname", "Domain", "OS"]] + data = [[ + "HostID", + "IP", + "Hostname", + "Domain", + "OS" + ]] for cred_id in cred_id_list: links = self.db.get_admin_relations(user_id=cred_id) @@ -168,19 +221,23 @@ class navigator(DatabaseNavigator): domain = host[3] os = host[4] - data.append([host_id, ip, hostname, domain, os]) + data.append([ + host_id, + ip, + hostname, + domain, + os + ]) print_table(data, title="Admin Access to Host(s)") 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): + @staticmethod + def help_clear_database(): help_string = """ clear_database THIS COMPLETELY DESTROYS ALL DATA IN THE CURRENTLY CONNECTED DATABASE @@ -192,8 +249,10 @@ class navigator(DatabaseNavigator): """ Tab-complete 'creds' commands """ - commands = ["add", "remove"] - + commands = ( + "add", + "remove" + ) mline = line.partition(" ")[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)] @@ -202,8 +261,12 @@ class navigator(DatabaseNavigator): """ Tab-complete 'creds' commands """ - commands = ["add", "remove", "hash", "plaintext"] - + commands = ( + "add", + "remove", + "hash", + "plaintext" + ) mline = line.partition(" ")[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)] From f4401182bd96cffed4728b07ce09c1666f79a936 Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Wed, 3 May 2023 16:48:05 -0400 Subject: [PATCH 05/12] refactor: remove unnecessary variable declaration --- cme/protocols/mssql/db_navigator.py | 112 +++++++++------------------- 1 file changed, 35 insertions(+), 77 deletions(-) diff --git a/cme/protocols/mssql/db_navigator.py b/cme/protocols/mssql/db_navigator.py index 5fda5a8e..546f6abb 100644 --- a/cme/protocols/mssql/db_navigator.py +++ b/cme/protocols/mssql/db_navigator.py @@ -17,21 +17,14 @@ class navigator(DatabaseNavigator): ]] for cred in creds: - cred_id = cred[0] - credtype = cred[1] - domain = cred[2] - username = cred[3] - password = cred[4] - # pillaged_from = cred[5] - - links = self.db.get_admin_relations(user_id=cred_id) + links = self.db.get_admin_relations(user_id=cred[0]) data.append([ - cred_id, + cred[0], # cred_id str(len(links)) + " Host(s)", - credtype, - domain, - username, - password, + cred[1], # cred_type + cred[2], # domain + cred[3], # username + cred[4], # password ]) print_table(data, title="Credentials") @@ -46,23 +39,15 @@ class navigator(DatabaseNavigator): "DB Instances" ]] for host in hosts: - host_id = host[0] - ip = host[1] - hostname = host[2] - domain = host[3] - os = host[4] - instances = host[5] - - links = self.db.get_admin_relations(host_id=host_id) - + links = self.db.get_admin_relations(host_id=host[0]) data.append([ - host_id, + host[0], str(len(links)) + " Cred(s)", - ip, - hostname, - domain, - os, - instances, + host[1], + host[2], + host[3], + host[4], + host[5], ]) print_table(data, title="Hosts") @@ -88,20 +73,13 @@ class navigator(DatabaseNavigator): host_id_list = [] for host in hosts: - host_id = host[0] - host_id_list.append(host_id) - - ip = host[1] - hostname = host[2] - domain = host[3] - os = host[4] - + host_id_list.append(host[0]) data.append([ - host_id, - ip, - hostname, - domain, - os + host[0], + host[1], + host[2], + host[3], + host[4] ]) print_table(data, title="Host(s)") @@ -121,19 +99,12 @@ class navigator(DatabaseNavigator): creds = self.db.get_credentials(filter_term=cred_id) for cred in creds: - cred_id = cred[0] - domain = cred[1] - username = cred[2] - password = cred[3] - credtype = cred[4] - # pillaged_from = cred[5] - data.append([ - cred_id, - credtype, - domain, - username, - password + cred[0], + cred[4], + cred[1], + cred[2], + cred[3] ]) print_table(data, title="Credential(s) with Admin Access") @@ -182,20 +153,13 @@ class navigator(DatabaseNavigator): cred_id_list = [] for cred in creds: - cred_id = cred[0] - cred_id_list.append(cred_id) - - credType = cred[1] - domain = cred[2] - username = cred[3] - password = cred[4] - + cred_id_list.append(cred[0]) data.append([ - cred_id, - credType, - domain, - username, - password + cred[0], + cred[1], + cred[2], + cred[3], + cred[4] ]) print_table(data, title="Credential(s)") @@ -215,18 +179,12 @@ class navigator(DatabaseNavigator): hosts = self.db.get_hosts(host_id) for host in hosts: - host_id = host[0] - ip = host[1] - hostname = host[2] - domain = host[3] - os = host[4] - data.append([ - host_id, - ip, - hostname, - domain, - os + host[0], + host[1], + host[2], + host[3], + host[4] ]) print_table(data, title="Admin Access to Host(s)") From a4b18d261bec6f997577ff5eaa3fe1804ebf43e3 Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Thu, 4 May 2023 00:23:51 -0400 Subject: [PATCH 06/12] refactor(firefox): perflint and formatting refactors --- cme/protocols/smb/firefox.py | 66 ++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/cme/protocols/smb/firefox.py b/cme/protocols/smb/firefox.py index fde19504..0f356867 100644 --- a/cme/protocols/smb/firefox.py +++ b/cme/protocols/smb/firefox.py @@ -31,7 +31,7 @@ class FirefoxTriage: firefox_generic_path = "Users\\{}\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles" share = "C$" - false_positive = [ + false_positive = ( ".", "..", "desktop.ini", @@ -39,7 +39,7 @@ class FirefoxTriage: "Default", "Default User", "All Users", - ] + ) def __init__(self, target, logger, conn: DPLootSMBConnection = None): self.target = target @@ -63,7 +63,8 @@ class FirefoxTriage: for user in users: try: directories = self.conn.remote_list_dir( - share=self.share, path=self.firefox_generic_path.format(user) + share=self.share, + path=self.firefox_generic_path.format(user) ) except Exception as e: if "STATUS_OBJECT_PATH_NOT_FOUND" in str(e): @@ -71,11 +72,7 @@ class FirefoxTriage: self.logger.debug(e) if directories is None: continue - for d in [ - d - for d in directories - if d.get_longname() not in self.false_positive and d.is_directory() > 0 - ]: + for d in [d for d in directories if d.get_longname() not in self.false_positive and d.is_directory() > 0]: try: logins_path = ( self.firefox_generic_path.format(user) @@ -96,7 +93,9 @@ class FirefoxTriage: + "\\key4.db" ) key4_data = self.conn.readFile( - self.share, key4_path, bypass_shared_violation=True + self.share, + key4_path, + bypass_shared_violation=True ) if key4_data is None: continue @@ -110,10 +109,14 @@ class FirefoxTriage: continue for username, pwd, host in logins: decoded_username = self.decrypt( - key=key, iv=username[1], ciphertext=username[2] + key=key, + iv=username[1], + ciphertext=username[2] ).decode("utf-8") password = self.decrypt( - key=key, iv=pwd[1], ciphertext=pwd[2] + key=key, + iv=pwd[1], + ciphertext=pwd[2] ).decode("utf-8") if password is not None and decoded_username is not None: firefox_data.append( @@ -127,23 +130,18 @@ class FirefoxTriage: except Exception as e: if "STATUS_OBJECT_PATH_NOT_FOUND" in str(e): continue - print(e) - self.logger.debug(e) + self.logger.exception(e) return firefox_data def get_login_data(self, logins_data): - logins = [] json_logins = json.loads(logins_data) if "logins" not in json_logins: - return logins # No logins key in logins.json file - for row in json_logins["logins"]: - logins.append( - ( - self.decode_login_data(row["encryptedUsername"]), - self.decode_login_data(row["encryptedPassword"]), - row["hostname"], - ) - ) + return [] # No logins key in logins.json file + logins = [( + self.decode_login_data(row["encryptedUsername"]), + self.decode_login_data(row["encryptedPassword"]), + row["hostname"] + ) for row in json_logins["logins"]] return logins def get_key(self, key4_data, master_password=b""): @@ -157,7 +155,8 @@ class FirefoxTriage: if row: global_salt, master_password, _ = self.is_master_password_correct( - key_data=row, master_password=master_password + key_data=row, + master_password=master_password ) if global_salt: try: @@ -170,7 +169,9 @@ class FirefoxTriage: if a102 == CKA_ID: decoded_a11 = decoder.decode(a11) key = self.decrypt_3des( - decoded_a11, master_password, global_salt + decoded_a11, + master_password, + global_salt ) if key is not None: fh.close() @@ -188,7 +189,9 @@ class FirefoxTriage: item2 = key_data[1] decoded_item2 = decoder.decode(item2) cleartext_data = self.decrypt_3des( - decoded_item2, master_password, global_salt + decoded_item2, + master_password, + global_salt ) if cleartext_data != "password-check\x02\x02".encode(): return "", "", "" @@ -202,12 +205,13 @@ class FirefoxTriage: users_dir_path = "Users\\*" directories = self.conn.listPath( - shareName=self.share, path=ntpath.normpath(users_dir_path) + shareName=self.share, + path=ntpath.normpath(users_dir_path) ) + for d in directories: if d.get_longname() not in self.false_positive and d.is_directory() > 0: users.append(d.get_longname()) - return users @staticmethod @@ -267,7 +271,11 @@ class FirefoxTriage: k = sha1(global_salt + master_password).digest() key = pbkdf2_hmac( - "sha256", k, entry_salt, iteration_count, dklen=key_length + "sha256", + k, + entry_salt, + iteration_count, + dklen=key_length ) # https://hg.mozilla.org/projects/nss/rev/fc636973ad06392d11597620b602779b4af312f6#l6.49 From 1f1ddedf34211bae71c702a445a3b0009c2e7170 Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Thu, 4 May 2023 09:21:17 -0400 Subject: [PATCH 07/12] redo black --- cme/cmedb.py | 49 ++++---- cme/connection.py | 4 +- cme/crackmapexec.py | 62 +++++----- cme/protocols/mssql/db_navigator.py | 142 +++++++---------------- cme/protocols/rdp.py | 29 +++-- cme/protocols/smb.py | 169 ++++++++++++---------------- cme/servers/smb.py | 4 +- 7 files changed, 182 insertions(+), 277 deletions(-) diff --git a/cme/cmedb.py b/cme/cmedb.py index eaaae14e..59287af4 100644 --- a/cme/cmedb.py +++ b/cme/cmedb.py @@ -73,10 +73,7 @@ def complete_import(text, line): """ Tab-complete 'import' commands """ - commands = ( - "empire", - "metasploit" - ) + commands = ("empire", "metasploit") mline = line.partition(" ")[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)] @@ -138,7 +135,9 @@ class DatabaseNavigator(cmd.Cmd): # Users if command == "creds": if len(line) < 3: - print("[-] invalid arguments, export creds ") + print( + "[-] invalid arguments, export creds " + ) return filename = line[2] @@ -178,7 +177,9 @@ class DatabaseNavigator(cmd.Cmd): # Hosts elif command == "hosts": if len(line) < 3: - print("[-] invalid arguments, export hosts ") + print( + "[-] invalid arguments, export hosts " + ) return csv_header_simple = ( @@ -225,19 +226,13 @@ class DatabaseNavigator(cmd.Cmd): # Shares elif command == "shares": if len(line) < 3: - print("[-] invalid arguments, export shares ") + print( + "[-] invalid arguments, export shares " + ) return shares = self.db.get_shares() - csv_header = ( - "id", - "host", - "userid", - "name", - "remark", - "read", - "write" - ) + csv_header = ("id", "host", "userid", "name", "remark", "read", "write") filename = line[2] if line[1].lower() == "simple": @@ -267,16 +262,14 @@ class DatabaseNavigator(cmd.Cmd): # Local Admin elif command == "local_admins": if len(line) < 3: - print("[-] invalid arguments, export local_admins ") + print( + "[-] invalid arguments, export local_admins " + ) return # These values don't change between simple and detailed local_admins = self.db.get_admin_relations() - csv_header = ( - "id", - "userid", - "host" - ) + csv_header = ("id", "userid", "host") filename = line[2] if line[1].lower() == "simple": @@ -300,7 +293,9 @@ class DatabaseNavigator(cmd.Cmd): print("[+] Local Admins exported") elif command == "dpapi": if len(line) < 3: - print("[-] invalid arguments, export dpapi ") + print( + "[-] invalid arguments, export dpapi " + ) return # These values don't change between simple and detailed @@ -346,7 +341,9 @@ class DatabaseNavigator(cmd.Cmd): filename = line[2] write_list(filename, writable_keys) else: - print("[-] Invalid argument, specify creds, hosts, local_admins, shares or dpapi") + print( + "[-] Invalid argument, specify creds, hosts, local_admins, shares or dpapi" + ) @staticmethod def help_export(): @@ -366,9 +363,7 @@ class DatabaseNavigator(cmd.Cmd): return if line == "empire": - headers = { - "Content-Type": "application/json" - } + headers = {"Content-Type": "application/json"} # Pull the username and password from the config file payload = { "username": self.config.get("Empire", "username"), diff --git a/cme/connection.py b/cme/connection.py index 218cb110..74c67088 100755 --- a/cme/connection.py +++ b/cme/connection.py @@ -278,7 +278,9 @@ class connection(object): return True elif self.hash_login(domain, username, password): return True - elif cred_type == "plaintext" and not self.over_fail_limit(username): + elif cred_type == "plaintext" and not self.over_fail_limit( + username + ): if self.args.kerberos: if self.kerberos_login( domain, diff --git a/cme/crackmapexec.py b/cme/crackmapexec.py index c2ecb86a..a939d448 100755 --- a/cme/crackmapexec.py +++ b/cme/crackmapexec.py @@ -96,9 +96,7 @@ def main(): if args.darrell: links = ( - open(path_join(DATA_PATH, "videos_for_darrell.harambe")) - .read() - .splitlines() + open(path_join(DATA_PATH, "videos_for_darrell.harambe")).read().splitlines() ) try: webbrowser.open(random.choice(links)) @@ -121,11 +119,7 @@ def main(): module_server = None targets = [] - server_port_dict = { - "http": 80, - "https": 443, - "smb": 445 - } + server_port_dict = {"http": 80, "https": 443, "smb": 445} if hasattr(args, "cred_id") and args.cred_id: for cred_id in args.cred_id: @@ -211,21 +205,29 @@ def main(): if not module.opsec_safe: if ignore_opsec: - cme_logger.debug(f"ignore_opsec is set in the configuration, skipping prompt") - cme_logger.display(f"Ignore OPSEC in configuration is set and OPSEC unsafe module loaded") + cme_logger.debug( + f"ignore_opsec is set in the configuration, skipping prompt" + ) + cme_logger.display( + f"Ignore OPSEC in configuration is set and OPSEC unsafe module loaded" + ) else: - ans = input(highlight( - "[!] Module is not opsec safe, are you sure you want to run this? [Y/n] ", - "red", - )) + ans = input( + highlight( + "[!] Module is not opsec safe, are you sure you want to run this? [Y/n] ", + "red", + ) + ) if ans.lower() not in ["y", "yes", ""]: exit(1) if not module.multiple_hosts and len(targets) > 1: - ans = input(highlight( - "[!] Running this module on multiple hosts doesn't really make any sense, are you sure you want to continue? [Y/n] ", - "red", - )) + ans = input( + highlight( + "[!] Running this module on multiple hosts doesn't really make any sense, are you sure you want to continue? [Y/n] ", + "red", + ) + ) if ans.lower() not in ["y", "yes", ""]: exit(1) @@ -238,11 +240,7 @@ def main(): # loading a module server multiple times will obviously fail try: - context = Context( - db, - cme_logger, - args - ) + context = Context(db, cme_logger, args) module_server = CMEServer( module, context, @@ -256,19 +254,25 @@ def main(): except Exception as e: cme_logger.error(f"Error loading module server for {module}: {e}") - cme_logger.debug(f"proto_object: {protocol_object}, type: {type(protocol_object)}") + cme_logger.debug( + f"proto_object: {protocol_object}, type: {type(protocol_object)}" + ) cme_logger.debug(f"proto object dir: {dir(protocol_object)}") # get currently set modules, otherwise default to empty list current_modules = getattr(protocol_object, "module", []) current_modules.append(module) setattr(protocol_object, "module", current_modules) - cme_logger.debug(f"proto object module after adding: {protocol_object.module}") + cme_logger.debug( + f"proto object module after adding: {protocol_object.module}" + ) if hasattr(args, "ntds") and args.ntds and not args.userntds: - ans = input(highlight( - "[!] Dumping the ntds can crash the DC on Windows Server 2019. Use the option --user to dump a specific user safely or the module -M ntdsutil [Y/n] ", - "red", - )) + ans = input( + highlight( + "[!] Dumping the ntds can crash the DC on Windows Server 2019. Use the option --user to dump a specific user safely or the module -M ntdsutil [Y/n] ", + "red", + ) + ) if ans.lower() not in ["y", "yes", ""]: exit(1) diff --git a/cme/protocols/mssql/db_navigator.py b/cme/protocols/mssql/db_navigator.py index 546f6abb..ae7bd361 100644 --- a/cme/protocols/mssql/db_navigator.py +++ b/cme/protocols/mssql/db_navigator.py @@ -7,48 +7,37 @@ from cme.cmedb import DatabaseNavigator, print_table, print_help class navigator(DatabaseNavigator): def display_creds(self, creds): - data = [[ - "CredID", - "Admin On", - "CredType", - "Domain", - "UserName", - "Password" - ]] + data = [["CredID", "Admin On", "CredType", "Domain", "UserName", "Password"]] for cred in creds: links = self.db.get_admin_relations(user_id=cred[0]) - data.append([ - cred[0], # cred_id - str(len(links)) + " Host(s)", - cred[1], # cred_type - cred[2], # domain - cred[3], # username - cred[4], # password - ]) + data.append( + [ + cred[0], # cred_id + str(len(links)) + " Host(s)", + cred[1], # cred_type + cred[2], # domain + cred[3], # username + cred[4], # password + ] + ) print_table(data, title="Credentials") def display_hosts(self, hosts): - data = [[ - "HostID", - "Admins", - "IP", - "Hostname", - "Domain", - "OS", - "DB Instances" - ]] + data = [["HostID", "Admins", "IP", "Hostname", "Domain", "OS", "DB Instances"]] for host in hosts: links = self.db.get_admin_relations(host_id=host[0]) - data.append([ - host[0], - str(len(links)) + " Cred(s)", - host[1], - host[2], - host[3], - host[4], - host[5], - ]) + data.append( + [ + host[0], + str(len(links)) + " Cred(s)", + host[1], + host[2], + host[3], + host[4], + host[5], + ] + ) print_table(data, title="Hosts") def do_hosts(self, line): @@ -63,34 +52,16 @@ class navigator(DatabaseNavigator): if len(hosts) > 1: self.display_hosts(hosts) elif len(hosts) == 1: - data = [[ - "HostID", - "IP", - "Hostname", - "Domain", - "OS" - ]] + data = [["HostID", "IP", "Hostname", "Domain", "OS"]] host_id_list = [] for host in hosts: host_id_list.append(host[0]) - data.append([ - host[0], - host[1], - host[2], - host[3], - host[4] - ]) + data.append([host[0], host[1], host[2], host[3], host[4]]) print_table(data, title="Host(s)") - data = [[ - "CredID", - "CredType", - "Domain", - "UserName", - "Password" - ]] + data = [["CredID", "CredType", "Domain", "UserName", "Password"]] for host_id in host_id_list: links = self.db.get_admin_relations(host_id=host_id) @@ -99,13 +70,7 @@ class navigator(DatabaseNavigator): creds = self.db.get_credentials(filter_term=cred_id) for cred in creds: - data.append([ - cred[0], - cred[4], - cred[1], - cred[2], - cred[3] - ]) + data.append([cred[0], cred[4], cred[1], cred[2], cred[3]]) print_table(data, title="Credential(s) with Admin Access") def do_creds(self, line): @@ -143,34 +108,16 @@ class navigator(DatabaseNavigator): self.display_creds(creds) else: creds = self.db.get_credentials(filter_term=filter_term) - data = [[ - "CredID", - "CredType", - "Domain", - "UserName", - "Password" - ]] + data = [["CredID", "CredType", "Domain", "UserName", "Password"]] cred_id_list = [] for cred in creds: cred_id_list.append(cred[0]) - data.append([ - cred[0], - cred[1], - cred[2], - cred[3], - cred[4] - ]) + data.append([cred[0], cred[1], cred[2], cred[3], cred[4]]) print_table(data, title="Credential(s)") - data = [[ - "HostID", - "IP", - "Hostname", - "Domain", - "OS" - ]] + data = [["HostID", "IP", "Hostname", "Domain", "OS"]] for cred_id in cred_id_list: links = self.db.get_admin_relations(user_id=cred_id) @@ -179,19 +126,16 @@ class navigator(DatabaseNavigator): hosts = self.db.get_hosts(host_id) for host in hosts: - data.append([ - host[0], - host[1], - host[2], - host[3], - host[4] - ]) + data.append([host[0], host[1], host[2], host[3], host[4]]) print_table(data, title="Admin Access to Host(s)") 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() @staticmethod @@ -207,10 +151,7 @@ class navigator(DatabaseNavigator): """ Tab-complete 'creds' commands """ - commands = ( - "add", - "remove" - ) + commands = ("add", "remove") mline = line.partition(" ")[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)] @@ -219,12 +160,7 @@ class navigator(DatabaseNavigator): """ Tab-complete 'creds' commands """ - commands = ( - "add", - "remove", - "hash", - "plaintext" - ) + commands = ("add", "remove", "hash", "plaintext") mline = line.partition(" ")[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)] diff --git a/cme/protocols/rdp.py b/cme/protocols/rdp.py index 9a4a5084..2cf4769f 100644 --- a/cme/protocols/rdp.py +++ b/cme/protocols/rdp.py @@ -174,22 +174,21 @@ class rdp(connection): def print_host_info(self): if self.domain is None: - self.logger.display(f"Probably old, doesn't not support HYBRID or HYBRID_EX (nla:{self.nla})") + self.logger.display( + f"Probably old, doesn't not support HYBRID or HYBRID_EX (nla:{self.nla})" + ) else: - self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain}) (nla:{self.nla})") + self.logger.display( + f"{self.server_os} (name:{self.hostname}) (domain:{self.domain}) (nla:{self.nla})" + ) return True def create_conn_obj(self): self.target = RDPTarget( - ip=self.host, - domain="FAKE", - timeout=self.args.rdp_timeout + ip=self.host, domain="FAKE", timeout=self.args.rdp_timeout ) self.auth = NTLMCredential( - secret="pass", - username="user", - domain="FAKE", - stype=asyauthSecret.PASS + secret="pass", username="user", domain="FAKE", stype=asyauthSecret.PASS ) self.check_nla() @@ -304,7 +303,9 @@ class rdp(connection): if not password: password = getenv("KRB5CCNAME") if not password else password if "/" in password: - self.logger.fail("Kerberos ticket need to be on the local directory") + self.logger.fail( + "Kerberos ticket need to be on the local directory" + ) return False ccache = CCache.loadFile(getenv("KRB5CCNAME")) ticketCreds = ccache.credentials[0] @@ -409,9 +410,7 @@ class rdp(connection): asyncio.run(self.connect_rdp()) self.admin_privs = True - self.logger.success( - f"{domain}\\{username}:{password} {self.mark_pwned()}" - ) + self.logger.success(f"{domain}\\{username}:{password} {self.mark_pwned()}") if not self.args.local_auth: add_user_bh(username, domain, self.logger, self.config) if not self.args.continue_on_success: @@ -487,9 +486,7 @@ class rdp(connection): async def screen(self): try: self.conn = RDPConnection( - iosettings=self.iosettings, - target=self.target, - credentials=self.auth + iosettings=self.iosettings, target=self.target, credentials=self.auth ) await self.connect_rdp() except Exception as e: diff --git a/cme/protocols/smb.py b/cme/protocols/smb.py index db443133..8e04bc4e 100755 --- a/cme/protocols/smb.py +++ b/cme/protocols/smb.py @@ -168,9 +168,7 @@ class smb(connection): @staticmethod def proto_args(parser, std_parser, module_parser): smb_parser = parser.add_parser( - "smb", - help="own stuff using SMB", - parents=[std_parser, module_parser] + "smb", help="own stuff using SMB", parents=[std_parser, module_parser] ) smb_parser.add_argument( "-H", @@ -245,19 +243,14 @@ class smb(connection): ) cgroup = smb_parser.add_argument_group( - "Credential Gathering", - "Options for gathering credentials" + "Credential Gathering", "Options for gathering credentials" ) cegroup = cgroup.add_mutually_exclusive_group() cegroup.add_argument( - "--sam", - action="store_true", - help="dump SAM hashes from target systems" + "--sam", action="store_true", help="dump SAM hashes from target systems" ) cegroup.add_argument( - "--lsa", - action="store_true", - help="dump LSA secrets from target systems" + "--lsa", action="store_true", help="dump LSA secrets from target systems" ) cegroup.add_argument( "--ntds", @@ -271,14 +264,13 @@ class smb(connection): choices={"password", "cookies"}, nargs="?", const="password", - help="dump DPAPI secrets from target systems, can dump cookies if you add \"cookies\"\n(default: password)", + help='dump DPAPI secrets from target systems, can dump cookies if you add "cookies"\n(default: password)', ) # cgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history') # cgroup.add_argument("--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account') ngroup = smb_parser.add_argument_group( - "Credential Gathering", - "Options for gathering credentials" + "Credential Gathering", "Options for gathering credentials" ) ngroup.add_argument( "--mkfile", @@ -286,30 +278,20 @@ class smb(connection): help="DPAPI option. File with masterkeys in form of {GUID}:SHA1", ) ngroup.add_argument( - "--pvk", - action="store", - help="DPAPI option. File with domain backupkey" + "--pvk", action="store", help="DPAPI option. File with domain backupkey" ) ngroup.add_argument( - "--enabled", - action="store_true", - help="Only dump enabled targets from DC" + "--enabled", action="store_true", help="Only dump enabled targets from DC" ) ngroup.add_argument( - "--user", - dest="userntds", - type=str, - help="Dump selected user from DC" + "--user", dest="userntds", type=str, help="Dump selected user from DC" ) egroup = smb_parser.add_argument_group( - "Mapping/Enumeration", - "Options for Mapping/Enumerating" + "Mapping/Enumeration", "Options for Mapping/Enumerating" ) egroup.add_argument( - "--shares", - action="store_true", - help="enumerate shares and access" + "--shares", action="store_true", help="enumerate shares and access" ) egroup.add_argument( "--filter-shares", @@ -317,24 +299,16 @@ class smb(connection): help="Filter share by access, option 'read' 'write' or 'read,write'", ) egroup.add_argument( - "--sessions", - action="store_true", - help="enumerate active sessions" - ) - egroup.add_argument( - "--disks", - action="store_true", - help="enumerate disks" + "--sessions", action="store_true", help="enumerate active sessions" ) + egroup.add_argument("--disks", action="store_true", help="enumerate disks") egroup.add_argument( "--loggedon-users-filter", action="store", help="only search for specific user, works with regex", ) egroup.add_argument( - "--loggedon-users", - action="store_true", - help="enumerate logged on users" + "--loggedon-users", action="store_true", help="enumerate logged on users" ) egroup.add_argument( "--users", @@ -365,9 +339,7 @@ class smb(connection): help="enumerate local groups, if a group is specified then its members are enumerated", ) egroup.add_argument( - "--pass-pol", - action="store_true", - help="dump password policy" + "--pass-pol", action="store_true", help="dump password policy" ) egroup.add_argument( "--rid-brute", @@ -378,10 +350,7 @@ class smb(connection): help="enumerate users by bruteforcing RID's (default: 4000)", ) egroup.add_argument( - "--wmi", - metavar="QUERY", - type=str, - help="issues the specified WMI query" + "--wmi", metavar="QUERY", type=str, help="issues the specified WMI query" ) egroup.add_argument( "--wmi-namespace", @@ -391,14 +360,10 @@ class smb(connection): ) sgroup = smb_parser.add_argument_group( - "Spidering", - "Options for spidering shares" + "Spidering", "Options for spidering shares" ) sgroup.add_argument( - "--spider", - metavar="SHARE", - type=str, - help="share to spider" + "--spider", metavar="SHARE", type=str, help="share to spider" ) sgroup.add_argument( "--spider-folder", @@ -408,9 +373,7 @@ class smb(connection): help="folder to spider (default: root share directory)", ) sgroup.add_argument( - "--content", - action="store_true", - help="enable file content searching" + "--content", action="store_true", help="enable file content searching" ) sgroup.add_argument( "--exclude-dirs", @@ -437,14 +400,11 @@ class smb(connection): help="max spider recursion depth (default: infinity & beyond)", ) sgroup.add_argument( - "--only-files", - action="store_true", - help="only spider files" + "--only-files", action="store_true", help="only spider files" ) tgroup = smb_parser.add_argument_group( - "Files", - "Options for put and get remote files" + "Files", "Options for put and get remote files" ) tgroup.add_argument( "--put-file", @@ -465,8 +425,7 @@ class smb(connection): ) cgroup = smb_parser.add_argument_group( - "Command Execution", - "Options for executing commands" + "Command Execution", "Options for executing commands" ) cgroup.add_argument( "--exec-method", @@ -489,9 +448,7 @@ class smb(connection): help="force the PowerShell command to run in a 32-bit process", ) cgroup.add_argument( - "--no-output", - action="store_true", - help="do not retrieve command output" + "--no-output", action="store_true", help="do not retrieve command output" ) cegroup = cgroup.add_mutually_exclusive_group() cegroup.add_argument( @@ -507,13 +464,10 @@ class smb(connection): help="execute the specified PowerShell command", ) psgroup = smb_parser.add_argument_group( - "Powershell Obfuscation", - "Options for PowerShell script obfuscation" + "Powershell Obfuscation", "Options for PowerShell script obfuscation" ) psgroup.add_argument( - "--obfs", - action="store_true", - help="Obfuscate PowerShell scripts" + "--obfs", action="store_true", help="Obfuscate PowerShell scripts" ) psgroup.add_argument( "--amsi-bypass", @@ -676,11 +630,14 @@ class smb(connection): from impacket.ldap import ldapasn1 as ldapasn1_impacket - results = [r for r in results if isinstance(r, ldapasn1_impacket.SearchResultEntry)] + results = [ + r for r in results if isinstance(r, ldapasn1_impacket.SearchResultEntry) + ] if len(results) != 0: for host in results: values = { - str(attr["type"]).lower(): str(attr["vals"][0]) for attr in host["attributes"] + str(attr["type"]).lower(): str(attr["vals"][0]) + for attr in host["attributes"] } if "mslaps-encryptedpassword" in values: self.logger.fail( @@ -694,7 +651,9 @@ class smb(connection): elif "ms-mcs-admpwd" in values: msMCSAdmPwd = values["ms-mcs-admpwd"] else: - self.logger.fail("No result found with attribute ms-MCS-AdmPwd or msLAPS-Password") + self.logger.fail( + "No result found with attribute ms-MCS-AdmPwd or msLAPS-Password" + ) logging.debug( f"Host: {sAMAccountName:<20} Password: {msMCSAdmPwd} {self.hostname}" ) @@ -1768,7 +1727,9 @@ class smb(connection): policy_handle, lsad.POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation, ) - domain_sid = resp["PolicyInformation"]["PolicyAccountDomainInfo"]["DomainSid"].formatCanonical() + domain_sid = resp["PolicyInformation"]["PolicyAccountDomainInfo"][ + "DomainSid" + ].formatCanonical() so_far = 0 simultaneous = 1000 @@ -1786,10 +1747,7 @@ class smb(connection): sids.append(f"{domain_sid}-{i:d}") try: lsat.hLsarLookupSids( - dce, - policy_handle, - sids, - lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta + dce, policy_handle, sids, lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta ) except DCERPCException as e: if str(e).find("STATUS_NONE_MAPPED") >= 0: @@ -1803,22 +1761,28 @@ class smb(connection): for n, item in enumerate(resp["TranslatedNames"]["Names"]): if item["Use"] != SID_NAME_USE.SidTypeUnknown: rid = so_far + n - domain = resp["ReferencedDomains"]["Domains"][item["DomainIndex"]]["Name"] + domain = resp["ReferencedDomains"]["Domains"][item["DomainIndex"]][ + "Name" + ] user = item["Name"] sid_type = SID_NAME_USE.enumItems(item["Use"]).name self.logger.highlight(f"{rid}: {domain}\\{user} ({sid_type})") - entries.append({ - "rid": rid, - "domain": domain, - "username": user, - "sidtype": sid_type, - }) + entries.append( + { + "rid": rid, + "domain": domain, + "username": user, + "sidtype": sid_type, + } + ) so_far += simultaneous dce.disconnect() return entries def put_file(self): - self.logger.display(f"Copying {self.args.put_file[0]} to {self.args.put_file[1]}") + self.logger.display( + f"Copying {self.args.put_file[0]} to {self.args.put_file[1]}" + ) with open(self.args.put_file[0], "rb") as file: try: self.conn.putFile(self.args.share, self.args.put_file[1], file.read) @@ -1829,7 +1793,9 @@ class smb(connection): self.logger.fail(f"Error writing file to share {self.args.share}: {e}") def get_file(self): - self.logger.display(f"Copying {self.args.get_file[0]} to {self.args.get_file[1]}") + self.logger.display( + f"Copying {self.args.get_file[0]} to {self.args.get_file[1]}" + ) file_handle = self.args.get_file[1] if self.args.append_host: file_handle = f"{self.hostname}-{self.args.get_file[1]}" @@ -1944,10 +1910,11 @@ class smb(connection): dc_conn = DPLootSMBConnection(dc_target) dc_conn.connect() # Connect to DC if dc_conn.is_admin(): - self.logger.success("User is Domain Administrator, exporting domain backupkey...") + self.logger.success( + "User is Domain Administrator, exporting domain backupkey..." + ) backupkey_triage = BackupkeyTriage( - target=dc_target, - conn=dc_conn + target=dc_target, conn=dc_conn ) backupkey = backupkey_triage.triage_backupkey() self.pvkbytes = backupkey.backupkey_v2 @@ -1980,7 +1947,9 @@ class smb(connection): plaintexts = { username: password - for _, _, username, password, _, _ in self.db.get_credentials(cred_type="plaintext") + for _, _, username, password, _, _ in self.db.get_credentials( + cred_type="plaintext" + ) } nthashes = { username: nt.split(":")[1] if ":" in nt else nt @@ -1993,7 +1962,9 @@ class smb(connection): # Collect User and Machine masterkeys try: - self.logger.display("Collecting User and Machine masterkeys, grab a coffee and be patient...") + self.logger.display( + "Collecting User and Machine masterkeys, grab a coffee and be patient..." + ) masterkeys_triage = MasterkeysTriage( target=target, conn=conn, @@ -2011,7 +1982,9 @@ class smb(connection): logging.fail("No masterkeys looted") return - self.logger.success(f"Got {highlight(len(masterkeys))} decrypted masterkeys. Looting secrets...") + self.logger.success( + f"Got {highlight(len(masterkeys))} decrypted masterkeys. Looting secrets..." + ) try: # Collect User and Machine Credentials Manager secrets @@ -2055,9 +2028,7 @@ class smb(connection): # Collect Chrome Based Browser stored secrets dump_cookies = True if self.args.dpapi == "cookies" else False browser_triage = BrowserTriage( - target=target, - conn=conn, - masterkeys=masterkeys + target=target, conn=conn, masterkeys=masterkeys ) browser_credentials, cookies = browser_triage.triage_browsers( gather_cookies=dump_cookies @@ -2089,9 +2060,7 @@ class smb(connection): try: # Collect User Internet Explorer stored secrets vaults_triage = VaultsTriage( - target=target, - conn=conn, - masterkeys=masterkeys + target=target, conn=conn, masterkeys=masterkeys ) vaults = vaults_triage.triage_vaults() except Exception as e: diff --git a/cme/servers/smb.py b/cme/servers/smb.py index b89333a5..2befa078 100755 --- a/cme/servers/smb.py +++ b/cme/servers/smb.py @@ -28,7 +28,9 @@ class CMESMBServer(threading.Thread): except Exception as e: errno, message = e.args if errno == 98 and message == "Address already in use": - logger.error("Error starting SMB server on port 445: the port is already in use") + logger.error( + "Error starting SMB server on port 445: the port is already in use" + ) else: logger.error(f"Error starting SMB server on port 445: {message}") exit(1) From 7f73740e501bcc5808f268377d0553ab576d85ff Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Thu, 4 May 2023 09:22:31 -0400 Subject: [PATCH 08/12] firefox: redo black --- cme/protocols/smb/firefox.py | 54 +++++++++++++++--------------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/cme/protocols/smb/firefox.py b/cme/protocols/smb/firefox.py index 0f356867..9942ef97 100644 --- a/cme/protocols/smb/firefox.py +++ b/cme/protocols/smb/firefox.py @@ -63,8 +63,7 @@ class FirefoxTriage: for user in users: try: directories = self.conn.remote_list_dir( - share=self.share, - path=self.firefox_generic_path.format(user) + share=self.share, path=self.firefox_generic_path.format(user) ) except Exception as e: if "STATUS_OBJECT_PATH_NOT_FOUND" in str(e): @@ -72,7 +71,11 @@ class FirefoxTriage: self.logger.debug(e) if directories is None: continue - for d in [d for d in directories if d.get_longname() not in self.false_positive and d.is_directory() > 0]: + for d in [ + d + for d in directories + if d.get_longname() not in self.false_positive and d.is_directory() > 0 + ]: try: logins_path = ( self.firefox_generic_path.format(user) @@ -93,9 +96,7 @@ class FirefoxTriage: + "\\key4.db" ) key4_data = self.conn.readFile( - self.share, - key4_path, - bypass_shared_violation=True + self.share, key4_path, bypass_shared_violation=True ) if key4_data is None: continue @@ -109,14 +110,10 @@ class FirefoxTriage: continue for username, pwd, host in logins: decoded_username = self.decrypt( - key=key, - iv=username[1], - ciphertext=username[2] + key=key, iv=username[1], ciphertext=username[2] ).decode("utf-8") password = self.decrypt( - key=key, - iv=pwd[1], - ciphertext=pwd[2] + key=key, iv=pwd[1], ciphertext=pwd[2] ).decode("utf-8") if password is not None and decoded_username is not None: firefox_data.append( @@ -137,11 +134,14 @@ class FirefoxTriage: json_logins = json.loads(logins_data) if "logins" not in json_logins: return [] # No logins key in logins.json file - logins = [( - self.decode_login_data(row["encryptedUsername"]), - self.decode_login_data(row["encryptedPassword"]), - row["hostname"] - ) for row in json_logins["logins"]] + logins = [ + ( + self.decode_login_data(row["encryptedUsername"]), + self.decode_login_data(row["encryptedPassword"]), + row["hostname"], + ) + for row in json_logins["logins"] + ] return logins def get_key(self, key4_data, master_password=b""): @@ -155,8 +155,7 @@ class FirefoxTriage: if row: global_salt, master_password, _ = self.is_master_password_correct( - key_data=row, - master_password=master_password + key_data=row, master_password=master_password ) if global_salt: try: @@ -169,9 +168,7 @@ class FirefoxTriage: if a102 == CKA_ID: decoded_a11 = decoder.decode(a11) key = self.decrypt_3des( - decoded_a11, - master_password, - global_salt + decoded_a11, master_password, global_salt ) if key is not None: fh.close() @@ -189,9 +186,7 @@ class FirefoxTriage: item2 = key_data[1] decoded_item2 = decoder.decode(item2) cleartext_data = self.decrypt_3des( - decoded_item2, - master_password, - global_salt + decoded_item2, master_password, global_salt ) if cleartext_data != "password-check\x02\x02".encode(): return "", "", "" @@ -205,8 +200,7 @@ class FirefoxTriage: users_dir_path = "Users\\*" directories = self.conn.listPath( - shareName=self.share, - path=ntpath.normpath(users_dir_path) + shareName=self.share, path=ntpath.normpath(users_dir_path) ) for d in directories: @@ -271,11 +265,7 @@ class FirefoxTriage: k = sha1(global_salt + master_password).digest() key = pbkdf2_hmac( - "sha256", - k, - entry_salt, - iteration_count, - dklen=key_length + "sha256", k, entry_salt, iteration_count, dklen=key_length ) # https://hg.mozilla.org/projects/nss/rev/fc636973ad06392d11597620b602779b4af312f6#l6.49 From 574fc5a212575e8a4b7588c03a64d91c26e972ca Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Fri, 5 May 2023 14:36:47 -0400 Subject: [PATCH 09/12] refactor: remove unnecessary variable creation --- cme/protocols/smb/db_navigator.py | 101 ++++++++---------------------- 1 file changed, 26 insertions(+), 75 deletions(-) diff --git a/cme/protocols/smb/db_navigator.py b/cme/protocols/smb/db_navigator.py index 87a83cd4..fcdfd0f7 100644 --- a/cme/protocols/smb/db_navigator.py +++ b/cme/protocols/smb/db_navigator.py @@ -175,16 +175,11 @@ class navigator(DatabaseNavigator): data = [["ShareID", "Name", "Remark"], [share_id, name, remark]] print_table(data, title="Share") host = self.db.get_hosts(filter_term=host_id)[0] - data = [["HostID", "IP", "Hostname", "Domain", "OS", "DC"]] + data = [ + ["HostID", "IP", "Hostname", "Domain", "OS", "DC"], + [host[0], host[1], host[2], host[3], host[4], host[5]], + ] - host_id = host[0] - ip = host[1] - hostname = host[2] - domain = host[3] - os = host[4] - dc = host[5] - - data.append([host_id, ip, hostname, domain, os, dc]) print_table(data, title="Share Location") if users_r_access: @@ -194,12 +189,7 @@ class navigator(DatabaseNavigator): creds = self.db.get_credentials(filter_term=userid) for cred in creds: - cred_id = cred[0] - domain = cred[1] - username = cred[2] - password = cred[3] - credtype = cred[4] - data.append([cred_id, credtype, domain, username, password]) + data.append([cred[0], cred[4], cred[1], cred[2], cred[3]]) print_table(data, title="Users(s) with Read Access") if users_w_access: @@ -209,13 +199,7 @@ class navigator(DatabaseNavigator): creds = self.db.get_credentials(filter_term=userid) for cred in creds: - cred_id = cred[0] - domain = cred[1] - username = cred[2] - password = cred[3] - credtype = cred[4] - - data.append([cred_id, credtype, domain, username, password]) + data.append([cred[0], cred[4], cred[1], cred[2], cred[3]]) print_table(data, title="Users(s) with Write Access") def help_shares(self): @@ -251,23 +235,15 @@ class navigator(DatabaseNavigator): ] for group in groups: - group_id = group[0] - domain = group[1] - name = group[2] - rid = group[3] - members = len(self.db.get_group_relations(group_id=group_id)) - ad_members = group[4] - last_query_time = group[5] - data.append( [ - group_id, - domain, - name, - rid, - members, - ad_members, - last_query_time, + group[0], + group[1], + group[2], + group[3], + len(self.db.get_group_relations(group_id=group_id)), + group[4], + group[5], ] ) print_table(data, title="Group") @@ -290,21 +266,14 @@ class navigator(DatabaseNavigator): creds = self.db.get_credentials(filter_term=userid) for cred in creds: - cred_id = cred[0] - domain = cred[1] - username = cred[2] - password = cred[3] - credtype = cred[4] - pillaged_from = cred[5] - data.append( [ - cred_id, - credtype, - pillaged_from, - domain, - username, - password, + cred[0], + cred[4], + cred[5], + cred[1], + cred[2], + cred[3], ] ) print_table(data, title="Member(s)") @@ -402,13 +371,8 @@ class navigator(DatabaseNavigator): creds = self.db.get_credentials(filter_term=cred_id) for cred in creds: - cred_id = cred[0] - domain = cred[1] - username = cred[2] - password = cred[3] - credtype = cred[4] - # pillaged_from = cred[5] - data.append([cred_id, credtype, domain, username, password]) + data.append([cred[0], cred[4], cred[1], cred[2], cred[3]]) + print_table(data, title="Credential(s) with Admin Access") def help_hosts(self): @@ -632,17 +596,9 @@ class navigator(DatabaseNavigator): cred_id_list = [] for cred in creds: - cred_id = cred[0] - cred_id_list.append(cred_id) - domain = cred[1] - username = cred[2] - password = cred[3] - credtype = cred[4] - pillaged_from = cred[5] + cred_id_list.append(cred[0]) + data.append([cred[0], cred[4], cred[5], cred[1], cred[2], cred[3]]) - data.append( - [cred_id, credtype, pillaged_from, domain, username, password] - ) print_table(data, title="Credential(s)") data = [["GroupID", "Domain", "Name"]] @@ -670,13 +626,8 @@ class navigator(DatabaseNavigator): hosts = self.db.get_hosts(host_id) for host in hosts: - host_id = host[0] - ip = host[1] - hostname = host[2] - domain = host[3] - os = host[4] + data.append([host[0], host[1], host[2], host[3], host[4]]) - data.append([host_id, ip, hostname, domain, os]) print_table(data, title="Admin Access to Host(s)") def help_creds(self): @@ -720,7 +671,7 @@ class navigator(DatabaseNavigator): """ Tab-complete 'hosts' commands. """ - commands = ["add", "remove", "dc"] + commands = ("add", "remove", "dc") mline = line.partition(" ")[2] offs = len(mline) - len(text) @@ -730,7 +681,7 @@ class navigator(DatabaseNavigator): """ Tab-complete 'creds' commands. """ - commands = ["add", "remove", "hash", "plaintext"] + commands = ("add", "remove", "hash", "plaintext") mline = line.partition(" ")[2] offs = len(mline) - len(text) From 788701cb2cd3f93b7d75658b3dfc18cb369e861f Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Fri, 5 May 2023 14:37:20 -0400 Subject: [PATCH 10/12] refactor: small perflint improvements --- cme/protocols/smb/mmcexec.py | 4 ++-- cme/protocols/smb/smbexec.py | 13 +++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/cme/protocols/smb/mmcexec.py b/cme/protocols/smb/mmcexec.py index 8bf89cda..8ffb0d89 100644 --- a/cme/protocols/smb/mmcexec.py +++ b/cme/protocols/smb/mmcexec.py @@ -28,7 +28,7 @@ # import logging -import os +from os.path import join as path_join from time import sleep from cme.helpers.misc import gen_random_string @@ -241,7 +241,7 @@ class MMCEXEC: while True: try: with open( - os.path.join("/tmp", "cme_hosted", self.__output), "r" + path_join("/tmp", "cme_hosted", self.__output), "r" ) as output: self.output_callback(output.read()) break diff --git a/cme/protocols/smb/smbexec.py b/cme/protocols/smb/smbexec.py index fa08437a..bb94a064 100755 --- a/cme/protocols/smb/smbexec.py +++ b/cme/protocols/smb/smbexec.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- import os +from os.path import join as path_join from time import sleep from impacket.dcerpc.v5 import transport, scmr from cme.helpers.misc import gen_random_string @@ -120,9 +121,7 @@ class SMBEXEC: else: command = self.__shell + data - with open( - os.path.join("/tmp", "cme_hosted", self.__batchFile), "w" - ) as batch_file: + with open(path_join("/tmp", "cme_hosted", self.__batchFile), "w") as batch_file: batch_file.write(command) self.logger.debug("Hosting batch file with command: " + command) @@ -168,7 +167,7 @@ class SMBEXEC: sleep(2) pass else: - logger.debug(e) + self.logger.debug(e) pass self.__smbconnection.deleteFile(self.__share, self.__output) @@ -187,9 +186,7 @@ class SMBEXEC: else: command = self.__shell + data - with open( - os.path.join("/tmp", "cme_hosted", self.__batchFile), "w" - ) as batch_file: + with open(path_join("/tmp", "cme_hosted", self.__batchFile), "w") as batch_file: batch_file.write(command) self.logger.debug("Hosting batch file with command: " + command) @@ -227,7 +224,7 @@ class SMBEXEC: while True: try: with open( - os.path.join("/tmp", "cme_hosted", self.__output), "rb" + path_join("/tmp", "cme_hosted", self.__output), "rb" ) as output: self.output_callback(output.read()) break From e5d997fb8896718a329b702dca367dc7a21d257a Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Fri, 5 May 2023 14:44:11 -0400 Subject: [PATCH 11/12] refactor(perflint): improve imports, specifically in forloops --- cme/loaders/moduleloader.py | 12 +++++++----- cme/loaders/protocolloader.py | 22 ++++++++++++---------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/cme/loaders/moduleloader.py b/cme/loaders/moduleloader.py index cdd81879..5ef486b1 100755 --- a/cme/loaders/moduleloader.py +++ b/cme/loaders/moduleloader.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- import importlib -import os +from os import listdir +from os.path import dirname +from os.path import join as path_join import sys import cme @@ -130,14 +132,14 @@ class ModuleLoader: """ modules = {} modules_paths = [ - os.path.join(os.path.dirname(cme.__file__), "modules"), - os.path.join(CME_PATH, "modules"), + path_join(dirname(cme.__file__), "modules"), + path_join(CME_PATH, "modules"), ] for path in modules_paths: - for module in os.listdir(path): + for module in listdir(path): if module[-3:] == ".py" and module != "example_module.py": - module_path = os.path.join(path, module) + module_path = path_join(path, module) module_data = self.get_module_info(module_path) modules.update(module_data) return modules diff --git a/cme/loaders/protocolloader.py b/cme/loaders/protocolloader.py index a6a0017d..d5ad25aa 100755 --- a/cme/loaders/protocolloader.py +++ b/cme/loaders/protocolloader.py @@ -2,13 +2,15 @@ # -*- coding: utf-8 -*- import types from importlib.machinery import SourceFileLoader -import os +from os import listdir +from os.path import join as path_join +from os.path import dirname, exists, expanduser import cme class ProtocolLoader: def __init__(self): - self.cme_path = os.path.expanduser("~/.cme") + self.cme_path = expanduser("~/.cme") def load_protocol(self, protocol_path): loader = SourceFileLoader("protocol", protocol_path) @@ -19,23 +21,23 @@ class ProtocolLoader: def get_protocols(self): protocols = {} protocol_paths = [ - os.path.join(os.path.dirname(cme.__file__), "protocols"), - os.path.join(self.cme_path, "protocols"), + path_join(dirname(cme.__file__), "protocols"), + path_join(self.cme_path, "protocols"), ] for path in protocol_paths: - for protocol in os.listdir(path): + for protocol in listdir(path): if protocol[-3:] == ".py" and protocol[:-3] != "__init__": - protocol_path = os.path.join(path, protocol) + protocol_path = path_join(path, protocol) protocol_name = protocol[:-3] protocols[protocol_name] = {"path": protocol_path} - db_file_path = os.path.join(path, protocol_name, "database.py") - db_nav_path = os.path.join(path, protocol_name, "db_navigator.py") - if os.path.exists(db_file_path): + db_file_path = path_join(path, protocol_name, "database.py") + db_nav_path = path_join(path, protocol_name, "db_navigator.py") + if exists(db_file_path): protocols[protocol_name]["dbpath"] = db_file_path - if os.path.exists(db_nav_path): + if exists(db_nav_path): protocols[protocol_name]["nvpath"] = db_nav_path return protocols From e02ecb3b3532717cab7695aeca3390b54441d73e Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Fri, 5 May 2023 15:11:33 -0400 Subject: [PATCH 12/12] update lsassy version to 3.1.8 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0364bc5c..f932edfb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ cmedb = 'cme.cmedb:main' python = "^3.7.0" requests = ">=2.27.1" beautifulsoup4 = ">=4.11,<5" -lsassy = ">=3.1.3" +lsassy = ">=3.1.8" termcolor = "^1.1.0" msgpack = "^1.0.0" neo4j = "^4.1.1"