From fff8d09d2a4cc7be0eadce567a0c19dada27110e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Mon, 10 Mar 2025 16:04:41 +0100 Subject: [PATCH 1/9] 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 2621dcad02d40fe13088ecee4c9645e616ee1437 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Tue, 11 Mar 2025 14:51:35 +0100 Subject: [PATCH 2/9] 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 3/9] 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 4/9] 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 5/9] =?UTF-8?q?Fix=20ldap=20query=20output=20for=20output?= =?UTF-8?q?=20containing=20special=20chars,=20e.g.=20german=20=C3=A4=C3=B6?= =?UTF-8?q?=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 6/9] 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 7/9] 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 8/9] 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 adb423ef09a60605136feef207cce228de7f72c4 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 15 Mar 2025 10:20:30 -0400 Subject: [PATCH 9/9] 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