mirror of
https://github.com/Pennyw0rth/NetExec
synced 2026-06-06 16:34:30 +00:00
Merge branch 'Pennyw0rth:main' into output_users
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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
@@ -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)]]),
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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
@@ -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.')
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user