Merge branch 'Pennyw0rth:main' into output_users

This commit is contained in:
Alex
2025-03-16 15:50:57 +00:00
committed by GitHub
10 changed files with 98 additions and 100 deletions
+6 -23
View File
@@ -1,6 +1,6 @@
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 +20,7 @@ class NXCModule:
"""
def on_login(self, context, connection):
searchFilter = "(objectclass=user)"
searchFilter = "(unixUserPassword=*)"
try:
context.log.debug(f"Search Filter={searchFilter}")
@@ -37,27 +37,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")
+7 -24
View File
@@ -1,6 +1,6 @@
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 +20,7 @@ class NXCModule:
"""
def on_login(self, context, connection):
searchFilter = "(objectclass=user)"
searchFilter = "(userPassword=*)"
try:
context.log.debug(f"Search Filter={searchFilter}")
@@ -37,27 +37,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")
+1 -1
View File
@@ -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)]]),
+4 -1
View File
@@ -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
+11 -10
View File
@@ -1002,19 +1002,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):
+14 -13
View File
@@ -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):<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}")
+1 -1
View File
@@ -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")
+50 -23
View File
@@ -13,6 +13,11 @@ from impacket.examples.secretsdump import (
LSASecrets,
NTDSHashes,
)
from impacket.examples.regsecrets import (
RemoteOperations as RegSecretsRemoteOperations,
SAMHashes as RegSecretsSAMHashes,
LSASecrets as RegSecretsLSASecrets
)
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
@@ -1079,6 +1084,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:
@@ -1089,13 +1095,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:
@@ -1106,7 +1113,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:
@@ -1528,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()
@@ -1540,7 +1550,7 @@ class smb(connection):
@requires_admin
def sam(self):
try:
self.enable_remoteops()
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):
@@ -1558,13 +1568,20 @@ 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,
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()
@@ -1575,7 +1592,9 @@ class smb(connection):
self.remote_ops.finish()
except Exception as e:
self.logger.debug(f"Error calling remote_ops.finish(): {e}")
SAM.finish()
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.')
@@ -1792,7 +1811,7 @@ class smb(connection):
@requires_admin
def lsa(self):
try:
self.enable_remoteops()
self.enable_remoteops(regsecret=(self.args.lsa == "regdump"))
def add_lsa_secret(secret):
add_lsa_secret.secrets += 1
@@ -1811,14 +1830,21 @@ 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),
)
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,
self.remote_ops,
isRemote=True,
perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret),
)
self.logger.success("Dumping LSA secrets")
LSA.dumpCachedHashes()
LSA.exportCached(self.output_filename)
@@ -1829,7 +1855,8 @@ class smb(connection):
self.remote_ops.finish()
except Exception as e:
self.logger.debug(f"Error calling remote_ops.finish(): {e}")
LSA.finish()
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.')
+2 -2
View File
@@ -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")
Generated
+2 -2
View File
@@ -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"