From c1a2a92816f431677f6435802d09a5b56a6ebf39 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Fri, 22 Mar 2024 18:42:33 +0300 Subject: [PATCH 1/7] Update LDAP active users lookup to match SMB --- nxc/protocols/ldap.py | 89 ++++++++++++++++++++++++-------- nxc/protocols/ldap/proto_args.py | 2 +- 2 files changed, 68 insertions(+), 23 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index b1e03cc6..2d3bf53f 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -846,32 +846,77 @@ class ldap(connection): self.logger.fail(f"Skipping item, cannot process due to error {e}") def active_users(self): - # Building the search filter - search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(objectclass=*)" - attributes = ["sAMAccountName", "userAccountControl"] + """ + Retrieves active user information from the LDAP server. + Args: + ---- + input_attributes (list): Optional. List of attributes to retrieve for each user. + Returns: + ------- + None + """ + if len(self.args.active_users) > 0: + arg = True + self.logger.debug(f"Dumping users: {', '.join(self.args.active_users)}") + search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(objectclass=*)" + search_filter_args = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.active_users)})" + + else: + arg = False + self.logger.debug("Trying to dump all users") + 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"] + resp = self.search(search_filter, request_attributes, sizeLimit=0) + allusers = parse_result_attributes(resp) + + count = 0 + activeusers = [] + argsusers = [] + + if arg: + resp_args = self.search(search_filter_args, request_attributes, sizeLimit=0) + users_args = parse_result_attributes(resp_args) + # This try except for, if user gives a doesn't exist username. If it does, parsing process is crashing + try: + for i in range(0, len(self.args.active_users)): + argsusers.append(users_args[i]) + except: + pass + else: + argsusers = allusers + + for user in allusers: + account_disabled = int(user.get('userAccountControl')) & 2 + if not account_disabled: + count += 1 + activeusers.append(user.get('sAMAccountName').lower()) - resp = self.search(search_filter, attributes, sizeLimit=0) - if resp: - for item in resp: + if self.username == "": + self.logger.display(f"Total records returned: {len(resp):d}") + for item in resp_args: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue - sAMAccountName = "" - userAccountControl = "" - try: - if self.username == "": - self.logger.highlight(f"{item['objectName']}") - else: - for attribute in item["attributes"]: - if str(attribute["type"]) == "sAMAccountName": - sAMAccountName = str(attribute["vals"][0]) - elif str(attribute["type"]) == "userAccountControl": - userAccountControl = int(attribute["vals"][0]) - account_disabled = userAccountControl & 2 - if not account_disabled: - self.logger.highlight(f"{sAMAccountName}") - except Exception as e: - self.logger.debug(f"Skipping item, cannot process due to error {e}") + self.logger.highlight(f"{item['objectName']}") return + self.logger.display(f"Total records returned: {len(allusers)}, Total {len(allusers) - count:d} user(s) disabled") if not arg else self.logger.display(f"Total records returned: {len(argsusers)}, Total {len(allusers) - count:d} user(s) disabled") + self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}") + + + for arguser in argsusers: + timestamp_seconds = int(arguser.get("pwdLastSet", "")) / 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": + parsed_pw_last_set = "" + + if arguser.get('sAMAccountName').lower() in activeusers and arg == False: + self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<30}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") + elif (arguser.get('sAMAccountName').lower() not in activeusers) and (arg == True): + self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<7} {'(Disabled)':<22}{parsed_pw_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}") def asreproast(self): if self.password == "" and self.nthash == "" and self.kerberos is False: diff --git a/nxc/protocols/ldap/proto_args.py b/nxc/protocols/ldap/proto_args.py index ba1be223..9110da37 100644 --- a/nxc/protocols/ldap/proto_args.py +++ b/nxc/protocols/ldap/proto_args.py @@ -24,7 +24,7 @@ def proto_args(parser, std_parser, module_parser): vgroup.add_argument("--groups", action="store_true", help="Enumerate domain groups") vgroup.add_argument("--dc-list", action="store_true", help="Enumerate Domain Controllers") vgroup.add_argument("--get-sid", action="store_true", help="Get domain sid") - vgroup.add_argument("--active-users", action="store_true", help="Get Active Domain Users Accounts") + vgroup.add_argument("--active-users", nargs="*", help="Get Active Domain Users Accounts") ggroup = ldap_parser.add_argument_group("Retrevie gmsa on the remote DC", "Options to play with gmsa") ggroup.add_argument("--gmsa", action="store_true", help="Enumerate GMSA passwords") From 7c07ab04d7bebca31655fbe0f2a9fe1d16991b06 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Fri, 22 Mar 2024 18:58:45 +0300 Subject: [PATCH 2/7] Update ldap.py line 914 and 916 Comparison edited to "is" Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 2d3bf53f..e8f3d118 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -911,9 +911,9 @@ class ldap(connection): if parsed_pw_last_set == "1601-01-01 00:00:00": parsed_pw_last_set = "" - if arguser.get('sAMAccountName').lower() in activeusers and arg == False: + 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}") - elif (arguser.get('sAMAccountName').lower() not in activeusers) and (arg == True): + elif (arguser.get('sAMAccountName').lower() not in activeusers) and (arg is True): self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<7} {'(Disabled)':<22}{parsed_pw_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}") From 9603f8f450fc4266ff65a702d96f7cb333d9b72f Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Fri, 22 Mar 2024 19:04:40 +0300 Subject: [PATCH 3/7] Update ldap.py Fixed single quotes and blank spaces Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index e8f3d118..ff7b3dc1 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -880,18 +880,19 @@ class ldap(connection): users_args = parse_result_attributes(resp_args) # This try except for, if user gives a doesn't exist username. If it does, parsing process is crashing try: - for i in range(0, len(self.args.active_users)): + for i in range(len(self.args.active_users)): argsusers.append(users_args[i]) - except: - pass + except Exception as e: + self.logger.debug("Exception:", exc_info=True) + self.logger.debug(f"Skipping item, cannot process due to error {e}") else: argsusers = allusers for user in allusers: - account_disabled = int(user.get('userAccountControl')) & 2 + account_disabled = int(user.get("userAccountControl")) & 2 if not account_disabled: count += 1 - activeusers.append(user.get('sAMAccountName').lower()) + activeusers.append(user.get("sAMAccountName").lower()) if self.username == "": self.logger.display(f"Total records returned: {len(resp):d}") @@ -912,11 +913,11 @@ class ldap(connection): parsed_pw_last_set = "" 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}{parsed_pw_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', ''):<7} {'(Disabled)':<22}{parsed_pw_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", ""):<7} {'(Disabled)':<22}{parsed_pw_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}") def asreproast(self): if self.password == "" and self.nthash == "" and self.kerberos is False: From fc40c778fe2214a2931f39a1bfc04087f91d6b74 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Fri, 22 Mar 2024 19:12:26 +0300 Subject: [PATCH 4/7] Update ldap.py Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index ff7b3dc1..0b4ac039 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -903,7 +903,6 @@ class ldap(connection): return self.logger.display(f"Total records returned: {len(allusers)}, Total {len(allusers) - count:d} user(s) disabled") if not arg else self.logger.display(f"Total records returned: {len(argsusers)}, Total {len(allusers) - count:d} user(s) disabled") self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}") - for arguser in argsusers: timestamp_seconds = int(arguser.get("pwdLastSet", "")) / 10**7 @@ -913,11 +912,11 @@ class ldap(connection): parsed_pw_last_set = "" 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}{parsed_pw_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", ""):<7} {'(Disabled)':<22}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") + self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<7} {'(Disabled)':<22}{parsed_pw_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}{parsed_pw_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: From e0b6f571c8c4dd0c3e714b75f48313b65d9b611f Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Fri, 22 Mar 2024 19:22:10 +0300 Subject: [PATCH 5/7] Update ldap.py Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 0b4ac039..58516645 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -881,7 +881,7 @@ class ldap(connection): # This try except for, if user gives a doesn't exist username. If it does, parsing process is crashing try: for i in range(len(self.args.active_users)): - argsusers.append(users_args[i]) + argsusers = [users_args[i] for i in range(len(self.args.active_users))] except Exception as e: self.logger.debug("Exception:", exc_info=True) self.logger.debug(f"Skipping item, cannot process due to error {e}") @@ -911,11 +911,11 @@ class ldap(connection): if parsed_pw_last_set == "1601-01-01 00:00:00": parsed_pw_last_set = "" - if arguser.get('sAMAccountName').lower() in activeusers and arg is False: + 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}") - elif (arguser.get('sAMAccountName').lower() not in activeusers) and (arg is True): + elif (arguser.get("sAMAccountName").lower() not in activeusers) and (arg is True): self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<7} {'(Disabled)':<22}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") - elif (arguser.get('sAMAccountName').lower() in activeusers): + 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}") def asreproast(self): From 6c3746643e487a362d8f5fba02643dd499dda7d3 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Fri, 22 Mar 2024 19:24:41 +0300 Subject: [PATCH 6/7] Update ldap.py Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/protocols/ldap.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 58516645..792e496e 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -846,15 +846,6 @@ class ldap(connection): self.logger.fail(f"Skipping item, cannot process due to error {e}") def active_users(self): - """ - Retrieves active user information from the LDAP server. - Args: - ---- - input_attributes (list): Optional. List of attributes to retrieve for each user. - Returns: - ------- - None - """ if len(self.args.active_users) > 0: arg = True self.logger.debug(f"Dumping users: {', '.join(self.args.active_users)}") @@ -883,8 +874,8 @@ class ldap(connection): for i in range(len(self.args.active_users)): argsusers = [users_args[i] for i in range(len(self.args.active_users))] except Exception as e: - self.logger.debug("Exception:", exc_info=True) - self.logger.debug(f"Skipping item, cannot process due to error {e}") + self.logger.debug("Exception:", exc_info=True) + self.logger.debug(f"Skipping item, cannot process due to error {e}") else: argsusers = allusers From 774f77e7193c4c71b10865d258bb0969abac2ddd Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 24 Mar 2024 19:01:29 +0100 Subject: [PATCH 7/7] Fixing output if typo in username, fix indent when querying for specific user and formating --- nxc/protocols/ldap.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 792e496e..0738546c 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -851,39 +851,38 @@ class ldap(connection): self.logger.debug(f"Dumping users: {', '.join(self.args.active_users)}") search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(objectclass=*)" search_filter_args = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.active_users)})" - else: arg = False self.logger.debug("Trying to dump all users") 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"] resp = self.search(search_filter, request_attributes, sizeLimit=0) allusers = parse_result_attributes(resp) - + count = 0 activeusers = [] argsusers = [] - + if arg: resp_args = self.search(search_filter_args, request_attributes, sizeLimit=0) users_args = parse_result_attributes(resp_args) # This try except for, if user gives a doesn't exist username. If it does, parsing process is crashing - try: - for i in range(len(self.args.active_users)): - argsusers = [users_args[i] for i in range(len(self.args.active_users))] - except Exception as e: - self.logger.debug("Exception:", exc_info=True) - self.logger.debug(f"Skipping item, cannot process due to error {e}") + for i in range(len(self.args.active_users)): + try: + argsusers.append(users_args[i]) + except Exception as e: + self.logger.debug("Exception:", exc_info=True) + self.logger.debug(f"Skipping item, cannot process due to error {e}") else: argsusers = allusers - - for user in allusers: + + for user in allusers: account_disabled = int(user.get("userAccountControl")) & 2 if not account_disabled: count += 1 - activeusers.append(user.get("sAMAccountName").lower()) + activeusers.append(user.get("sAMAccountName").lower()) if self.username == "": self.logger.display(f"Total records returned: {len(resp):d}") @@ -901,11 +900,11 @@ class ldap(connection): 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": parsed_pw_last_set = "" - + 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}") - elif (arguser.get("sAMAccountName").lower() not in activeusers) and (arg is True): - self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<7} {'(Disabled)':<22}{parsed_pw_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}") 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}")