feat(ldap): allow for specifying specific users to dump for ldap and print last password set date
parent
0608628aff
commit
0eee328ea0
|
@ -0,0 +1,13 @@
|
|||
from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
||||
|
||||
def parse_result_attributes(ldap_response):
|
||||
parsed_response = []
|
||||
for entry in ldap_response:
|
||||
# SearchResultReferences may be returned
|
||||
if not isinstance(entry, ldapasn1_impacket.SearchResultEntry):
|
||||
continue
|
||||
attribute_map = {}
|
||||
for attribute in entry["attributes"]:
|
||||
attribute_map[str(attribute["type"])] = str(attribute["vals"][0])
|
||||
parsed_response.append(attribute_map)
|
||||
return parsed_response
|
|
@ -5,7 +5,7 @@ import hmac
|
|||
import os
|
||||
import socket
|
||||
from binascii import hexlify
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from re import sub, I
|
||||
from zipfile import ZipFile
|
||||
from termcolor import colored
|
||||
|
@ -38,6 +38,7 @@ from nxc.logger import NXCAdapter, nxc_logger
|
|||
from nxc.protocols.ldap.bloodhound import BloodHound
|
||||
from nxc.protocols.ldap.gmsa import MSDS_MANAGEDPASSWORD_BLOB
|
||||
from nxc.protocols.ldap.kerberos import KerberosAttacks
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
|
||||
ldap_error_status = {
|
||||
"1": "STATUS_NOT_SUPPORTED",
|
||||
|
@ -753,37 +754,51 @@ class ldap(connection):
|
|||
return False
|
||||
|
||||
def users(self):
|
||||
# Building the search filter
|
||||
search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(objectclass=*)"
|
||||
attributes = [
|
||||
"sAMAccountName",
|
||||
"description",
|
||||
"badPasswordTime",
|
||||
"badPwdCount",
|
||||
"pwdLastSet",
|
||||
]
|
||||
"""
|
||||
Retrieves user information from the LDAP server.
|
||||
|
||||
resp = self.search(search_filter, attributes, sizeLimit=0)
|
||||
Args:
|
||||
input_attributes (list): Optional. List of attributes to retrieve for each user.
|
||||
TODO: allow users to pass this in
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if len(self.args.users) > 1:
|
||||
self.logger.display(f"Trying to dumping users: {', '.join(self.args.users)}")
|
||||
search_filter = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.users)})"
|
||||
else:
|
||||
self.logger.info("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 = self.args.ldap_attributes
|
||||
self.logger.debug(f"{request_attributes=}")
|
||||
|
||||
resp = self.search(search_filter, request_attributes, sizeLimit=0)
|
||||
if resp:
|
||||
self.logger.display(f"Total of records returned {len(resp):d}")
|
||||
for item in resp:
|
||||
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
|
||||
continue
|
||||
sAMAccountName = ""
|
||||
description = ""
|
||||
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"]) == "description":
|
||||
description = str(attribute["vals"][0])
|
||||
self.logger.highlight(f"{sAMAccountName:<30} {description}")
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Skipping item, cannot process due to error {e}")
|
||||
return
|
||||
# I think this was here for anonymous ldap bindings, so I kept it, but we might just want to remove it
|
||||
if self.username == "":
|
||||
self.logger.display(f"Total records returned: {len(resp):d}")
|
||||
for item in resp:
|
||||
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
|
||||
continue
|
||||
self.logger.highlight(f"{item['objectName']}")
|
||||
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"Total records returned: {len(users):d}")
|
||||
self.logger.highlight(f"{'Username':<30} {'Last PW Set':<20}\t{'Description'}") # header
|
||||
for user in users:
|
||||
self.logger.debug(f"{user=}")
|
||||
# we default attributes to blank strings if they don't exist in the dict
|
||||
timestamp_seconds = int(user.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 = "<never>"
|
||||
self.logger.highlight(f"{user.get('sAMAccountName', ''):<30} {parsed_pw_last_set:<20}\t{user.get('description', '')}")
|
||||
|
||||
def groups(self):
|
||||
# Building the search filter
|
||||
|
@ -1390,3 +1405,4 @@ class ldap(connection):
|
|||
if each_file.startswith(self.output_filename.split("/")[-1]) and each_file.endswith("json"):
|
||||
z.write(each_file)
|
||||
os.remove(each_file)
|
||||
|
||||
|
|
|
@ -20,11 +20,12 @@ def proto_args(parser, std_parser, module_parser):
|
|||
vgroup.add_argument("--trusted-for-delegation", action="store_true", help="Get the list of users and computers with flag TRUSTED_FOR_DELEGATION")
|
||||
vgroup.add_argument("--password-not-required", action="store_true", help="Get the list of users with flag PASSWD_NOTREQD")
|
||||
vgroup.add_argument("--admin-count", action="store_true", help="Get objets that had the value adminCount=1")
|
||||
vgroup.add_argument("--users", action="store_true", help="Enumerate enabled domain users")
|
||||
vgroup.add_argument("--users", nargs="*", help="Enumerate enabled domain users")
|
||||
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("--ldap-attributes", nargs="+", default=["sAMAccountName", "description", "pwdLastSet"], help="Attributes to search for")
|
||||
|
||||
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")
|
||||
|
|
Loading…
Reference in New Issue