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] 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: