diff --git a/nxc/config.py b/nxc/config.py index 477bdaf5c2..dd22e503c1 100644 --- a/nxc/config.py +++ b/nxc/config.py @@ -48,3 +48,18 @@ def process_secret(text): reveal = text[:reveal_chars_of_pwd] return text if not audit_mode else reveal + (audit_mode if len(audit_mode) > 1 else audit_mode * 8) + + +def process_secret_dump(line, sep=":", keep=1): + """ + Redacts the credential material of a dumped secret line (SAM/LSA/NTDS) when audit_mode is + enabled, keeping only the first `keep` fields (username, RID, secret name, etc.) before + masking the rest of the line. + """ + if not audit_mode: + return line + parts = line.split(sep) + if len(parts) <= keep: + return process_secret(line) + mask = audit_mode if len(audit_mode) > 1 else audit_mode * 8 + return sep.join([*parts[:keep], mask]) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index fd034fa427..dce4c62043 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -38,7 +38,7 @@ from impacket.smbconnection import SessionError from impacket.ntlm import getNTLMSSPType1 -from nxc.config import process_secret, host_info_colors +from nxc.config import process_secret, process_secret_dump, host_info_colors from nxc.connection import connection from nxc.helpers.bloodhound import add_user_bh from nxc.helpers.misc import get_bloodhound_info, convert, d2b, parse_argument @@ -321,7 +321,7 @@ def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", if self.username and self.password == "" and self.args.asreproast: hash_tgt = KerberosAttacks(self).get_tgt_asroast(self.username) if hash_tgt: - self.logger.highlight(f"{hash_tgt}") + self.logger.highlight(process_secret_dump(hash_tgt)) with open(self.args.asreproast, "a+") as hash_asreproast: hash_asreproast.write(f"{hash_tgt}\n") return False @@ -446,7 +446,7 @@ def plaintext_login(self, domain, username, password): if self.username and self.password == "" and self.args.asreproast: hash_tgt = KerberosAttacks(self).get_tgt_asroast(self.username) if hash_tgt: - self.logger.highlight(f"{hash_tgt}") + self.logger.highlight(process_secret_dump(hash_tgt)) with open(self.args.asreproast, "a+") as hash_asreproast: hash_asreproast.write(f"{hash_tgt}\n") return False @@ -542,7 +542,7 @@ def hash_login(self, domain, username, ntlm_hash): if self.username and self.hash == "" and self.args.asreproast: hash_tgt = KerberosAttacks(self).get_tgt_asroast(self.username) if hash_tgt: - self.logger.highlight(f"{hash_tgt}") + self.logger.highlight(process_secret_dump(hash_tgt)) with open(self.args.asreproast, "a+") as hash_asreproast: hash_asreproast.write(f"{hash_tgt}\n") return False @@ -969,7 +969,7 @@ def asreproast(self): for user in resp_parsed: hash_TGT = KerberosAttacks(self).get_tgt_asroast(user["sAMAccountName"]) if hash_TGT: - self.logger.highlight(f"{hash_TGT}") + self.logger.highlight(process_secret_dump(hash_TGT)) with open(self.args.asreproast, "a+") as hash_asreproast: hash_asreproast.write(f"{hash_TGT}\n") @@ -1071,7 +1071,7 @@ def kerberoasting(self): pwdLastSet = "" if str(user.get("pwdLastSet", 0)) == "0" else str(datetime.fromtimestamp(self.getUnixTime(int(user["pwdLastSet"])))) lastLogon = "" if str(user.get("lastLogon", 0)) == "0" else str(datetime.fromtimestamp(self.getUnixTime(int(user["lastLogon"])))) self.logger.display(f"sAMAccountName: {user['sAMAccountName']}, memberOf: {user.get('memberOf', [])}, pwdLastSet: {pwdLastSet}, lastLogon: {lastLogon}") - self.logger.highlight(out) + self.logger.highlight(process_secret_dump(out, sep="*", keep=2)) if self.args.kerberoasting: with open(self.args.kerberoasting, "a+") as hash_kerberoasting: hash_kerberoasting.write(out + "\n") @@ -1339,10 +1339,10 @@ def gmsa(self): aes128 = aes256 = "" if "msDS-ManagedPassword" in acc: rc4, aes128, aes256 = self.gmsa_compute_secrets(acc["msDS-ManagedPassword"], acc["sAMAccountName"]) - self.logger.highlight(f"Account: {acc['sAMAccountName']:<20} NTLM: {rc4:<36} PrincipalsAllowedToReadPassword: {principal_with_read}") + self.logger.highlight(f"Account: {acc['sAMAccountName']:<20} NTLM: {process_secret(rc4):<36} PrincipalsAllowedToReadPassword: {principal_with_read}") if aes128 and aes256: - self.logger.highlight(f"Account: {acc['sAMAccountName']:<20} aes128-cts-hmac-sha1-96: {aes128}") - self.logger.highlight(f"Account: {acc['sAMAccountName']:<20} aes256-cts-hmac-sha1-96: {aes256}") + self.logger.highlight(f"Account: {acc['sAMAccountName']:<20} aes128-cts-hmac-sha1-96: {process_secret(aes128)}") + self.logger.highlight(f"Account: {acc['sAMAccountName']:<20} aes256-cts-hmac-sha1-96: {process_secret(aes256)}") def gmsa_compute_secrets(self, password_data: bytes, sAMAccountName: str): """Generate RC4, AES128, and AES256 keys for a GMSA account based on the provided password data and username.""" @@ -1407,12 +1407,12 @@ def gmsa_decrypt_lsa(self): # Compute the password and keys data = bytes.fromhex(gmsa_pass) rc4, aes128, aes256 = self.gmsa_compute_secrets(data, sAMAccountName) - self.logger.highlight(f"Account: {sAMAccountName:<20} NTLM: {rc4}") + self.logger.highlight(f"Account: {sAMAccountName:<20} NTLM: {process_secret(rc4)}") if not sAMAccountName: self.logger.fail("Could not find the GMSA account associated with the provided ID.") else: - self.logger.highlight(f"Account: {sAMAccountName:<20} aes128-cts-hmac-sha1-96: {aes128}") - self.logger.highlight(f"Account: {sAMAccountName:<20} aes256-cts-hmac-sha1-96: {aes256}") + self.logger.highlight(f"Account: {sAMAccountName:<20} aes128-cts-hmac-sha1-96: {process_secret(aes128)}") + self.logger.highlight(f"Account: {sAMAccountName:<20} aes256-cts-hmac-sha1-96: {process_secret(aes256)}") else: self.logger.fail("The provided string does not appear to be a valid GMSA LSA secret.") diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index dbf342c7c6..382f90d9ad 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -54,7 +54,7 @@ from impacket.dcerpc.v5 import tsts as TSTS -from nxc.config import process_secret, host_info_colors, check_guest_account +from nxc.config import process_secret, process_secret_dump, host_info_colors, check_guest_account 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 @@ -2102,7 +2102,7 @@ def sam(self): host_id = self.db.get_hosts(filter_term=self.host)[0][0] def add_sam_hash(sam_hash, host_id): - self.logger.highlight(sam_hash) + self.logger.highlight(process_secret_dump(sam_hash)) if "_history" in sam_hash: return username, _, lmhash, nthash, _, _, _ = sam_hash.split(":") @@ -2185,7 +2185,7 @@ def sccm(self): 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.logger.highlight(f"[{tag}] {secret.username.decode('latin-1')}:{process_secret(secret.password.decode('latin-1'))}") self.db.add_dpapi_secrets( target.address, f"SCCM - {tag}", @@ -2196,7 +2196,7 @@ def sccm_callback(secret): ) elif isinstance(secret, SCCMSecret): tag = "Task sequences secret" - self.logger.highlight(f"[{tag}] {secret.secret.decode('latin-1')}") + self.logger.highlight(f"[{tag}] {process_secret(secret.secret.decode('latin-1'))}") self.db.add_dpapi_secrets( target.address, f"SCCM - {tag}", @@ -2207,7 +2207,7 @@ def sccm_callback(secret): ) elif isinstance(secret, SCCMCollection): tag = "Collection Variable" - self.logger.highlight(f"[{tag}] {secret.variable.decode('latin-1')}:{secret.value.decode('latin-1')}") + self.logger.highlight(f"[{tag}] {secret.variable.decode('latin-1')}:{process_secret(secret.value.decode('latin-1'))}") self.db.add_dpapi_secrets( target.address, f"SCCM - {tag}", @@ -2267,7 +2267,7 @@ def dpapi(self): # Collect User and Machine Credentials Manager secrets def credential_callback(credential): tag = "CREDENTIAL" - line = f"[{credential.winuser}][{tag}] {credential.target} - {credential.username}:{credential.password}" + line = f"[{credential.winuser}][{tag}] {credential.target} - {credential.username}:{process_secret(credential.password)}" self.logger.highlight(line) if self.output_file: self.output_file.write(line + "\n") @@ -2305,7 +2305,7 @@ def credential_callback(credential): def browser_callback(secret): if isinstance(secret, LoginData): secret_url = secret.url + " -" if secret.url != "" else "-" - line = f"[{secret.winuser}][{secret.browser.upper()}] {secret_url} {secret.username}:{secret.password}" + line = f"[{secret.winuser}][{secret.browser.upper()}] {secret_url} {secret.username}:{process_secret(secret.password)}" self.logger.highlight(line) if self.output_file: self.output_file.write(line + "\n") @@ -2318,7 +2318,7 @@ def browser_callback(secret): secret.url, ) elif isinstance(secret, GoogleRefreshToken): - line = f"[{secret.winuser}][{secret.browser.upper()}] Google Refresh Token: {secret.service}:{secret.token}" + line = f"[{secret.winuser}][{secret.browser.upper()}] Google Refresh Token: {secret.service}:{process_secret(secret.token)}" self.logger.highlight(line) if self.output_file: self.output_file.write(line + "\n") @@ -2331,7 +2331,7 @@ def browser_callback(secret): "Google Refresh Token", ) elif isinstance(secret, Cookie): - line = f"[{secret.winuser}][{secret.browser.upper()}] {secret.host}{secret.path} - {secret.cookie_name}:{secret.cookie_value}" + line = f"[{secret.winuser}][{secret.browser.upper()}] {secret.host}{secret.path} - {secret.cookie_name}:{process_secret(secret.cookie_value)}" self.logger.highlight(line) if self.output_file: self.output_file.write(line + "\n") @@ -2346,7 +2346,7 @@ def vault_callback(secret): tag = "IEX" if secret.type == "Internet Explorer": resource = secret.resource + " -" if secret.resource != "" else "-" - line = f"[{secret.winuser}][{tag}] {resource} - {secret.username}:{secret.password}" + line = f"[{secret.winuser}][{tag}] {resource} - {secret.username}:{process_secret(secret.password)}" self.logger.highlight(line) if self.output_file: self.output_file.write(line + "\n") @@ -2370,7 +2370,7 @@ def firefox_callback(secret): tag = "FIREFOX" if isinstance(secret, FirefoxData): url = secret.url + " -" if secret.url != "" else "-" - line = f"[{secret.winuser}][{tag}] {url} {secret.username}:{secret.password}" + line = f"[{secret.winuser}][{tag}] {url} {secret.username}:{process_secret(secret.password)}" self.logger.highlight(line) if self.output_file: self.output_file.write(line + "\n") @@ -2383,7 +2383,7 @@ def firefox_callback(secret): secret.url, ) elif isinstance(secret, FirefoxCookie): - line = f"[{secret.winuser}][{tag}] {secret.host}{secret.path} {secret.cookie_name}:{secret.cookie_value}" + line = f"[{secret.winuser}][{tag}] {secret.host}{secret.path} {secret.cookie_name}:{process_secret(secret.cookie_value)}" self.logger.highlight(line) if self.output_file: self.output_file.write(line + "\n") @@ -2422,7 +2422,7 @@ def lsa(self): def add_lsa_secret(secret): add_lsa_secret.secrets += 1 - self.logger.highlight(secret) + self.logger.highlight(process_secret_dump(secret)) if "_SC_GMSA_{84A78B8C" in secret: gmsa_id = secret.split("_")[4].split(":")[0] data = bytes.fromhex(secret.split("_")[4].split(":")[1]) @@ -2432,7 +2432,7 @@ def add_lsa_secret(secret): ntlm_hash = MD4.new() ntlm_hash.update(currentPassword) passwd = binascii.hexlify(ntlm_hash.digest()).decode("utf-8") - self.logger.highlight(f"GMSA ID: {gmsa_id:<20} NTLM: {passwd}") + self.logger.highlight(process_secret_dump(f"GMSA ID: {gmsa_id:<20} NTLM: {passwd}", sep="NTLM: ")) add_lsa_secret.secrets = 0 @@ -2494,10 +2494,10 @@ def add_hash(secret_type, secret, host_id): if self.args.enabled: if "Enabled" in secret: secret = " ".join(secret.split(" ")[:-1]) - self.logger.highlight(secret) + self.logger.highlight(process_secret_dump(secret, keep=2)) else: secret = " ".join(secret.split(" ")[:-1]) if " " in secret else secret - self.logger.highlight(secret) + self.logger.highlight(process_secret_dump(secret, keep=2)) # Filter out computer accounts, history hashes and kerberos keys for adding to db if secret.find("$") == -1 and secret_type == NTDSHashes.SECRET_TYPE.NTDS and "_history" not in secret: