33from ldap3 .protocol .microsoft import security_descriptor_control
44from nxc .parsers .ldap_results import parse_result_attributes
55
6+ SAM_USER_OBJECT = 0x30000000
67SAM_MACHINE_ACCOUNT = 0x30000001
78SAM_GROUP_OBJECT = 0x10000000
9+ LDAP_MATCHING_RULE_IN_CHAIN = "1.2.840.113556.1.4.1941"
810
911
1012class NXCModule :
@@ -25,11 +27,21 @@ def __init__(self):
2527 self .sccm_site_servers = [] # List of dns host names of the SCCM site servers
2628 self .sccm_sites = {} # List of SCCM sites with their management points (Sorted by site code)
2729 self .base_dn = ""
30+ self .recursive_resolve = False
31+
32+ self .user_objects = []
33+ self .computer_objects = []
34+ self .group_objects = {}
2835
2936 def options (self , context , module_options ):
30- """BASE_DN The base domain name for the LDAP query"""
37+ """
38+ BASE_DN The base domain name for the LDAP query
39+ REC_RESOLVE Resolve members of groups recursively. Default: False
40+ """
3141 if module_options and "BASE_DN" in module_options :
3242 self .base_dn = module_options ["BASE_DN" ]
43+ if module_options and "REC_RESOLVE" in module_options :
44+ self .recursive_resolve = bool (module_options ["REC_RESOLVE" ])
3345
3446 def on_login (self , context , connection ):
3547 """On a successful LDAP login we perform a search for all PKI Enrollment Server or Certificate Templates Names."""
@@ -76,30 +88,89 @@ def on_login(self, context, connection):
7688 self .context .log .highlight (f" CAS: { ' ' :<17} { False } " )
7789 self .context .log .highlight (" Management Points:" )
7890 for mp in self .sccm_sites [site ]["ManagementPoints" ]:
79- self .context .log .highlight (f"\t CN:{ ' ' :<12} { mp ['cn' ]} " )
80- self .context .log .highlight (f"\t DNS Hostname:{ ' ' :<2} { mp ['dNSHostName' ]} " )
81- self .context .log .highlight (f"\t IP Address:{ ' ' :<4} { mp ['IPAddress' ]} " )
82- self .context .log .highlight (f"\t Default MP:{ ' ' :<4} { mp ['mSSMSDefaultMP' ]} " )
91+ self .context .log .highlight (f" CN:{ ' ' :<12} { mp ['cn' ]} " )
92+ self .context .log .highlight (f" DNS Hostname:{ ' ' :<2} { mp ['dNSHostName' ]} " )
93+ self .context .log .highlight (f" IP Address:{ ' ' :<4} { mp ['IPAddress' ]} " )
94+ self .context .log .highlight (f" Default MP:{ ' ' :<4} { mp ['mSSMSDefaultMP' ]} " )
8395 else :
8496 self .context .log .highlight (f" CAS: { ' ' :<17} { True } " )
8597 self .context .log .highlight ("" )
8698 except LDAPSearchError as e :
8799 context .log .fail (f"Got unexpected exception: { e } " )
88100
89- # Enumerate users/groups/computers with "SCCM" in their name
101+ # SCCM named objects enumeration
102+ self .get_sccm_named_objects (context , connection )
103+ if self .user_objects :
104+ context .log .success (f"Found { len (self .user_objects )} SCCM related user objects:" )
105+ for user in self .user_objects :
106+ context .log .highlight (user )
107+ if self .computer_objects :
108+ context .log .success (f"Found { len (self .computer_objects )} SCCM related computer objects:" )
109+ for computer in self .computer_objects :
110+ context .log .highlight (computer )
111+ if self .group_objects :
112+ context .log .success (f"Found { len (self .group_objects )} SCCM related group objects:" )
113+ for group in self .group_objects :
114+ context .log .highlight (self .group_objects [group ]["sAMAccountName" ])
115+ for child in self .group_objects [group ]["children" ]:
116+ if int (child ["sAMAccountType" ]) == SAM_USER_OBJECT :
117+ context .log .highlight (f" { child ['sAMAccountName' ]} -> User" )
118+ elif int (child ["sAMAccountType" ]) == SAM_MACHINE_ACCOUNT :
119+ context .log .highlight (f" { child ['sAMAccountName' ]} -> Computer" )
120+ elif int (child ["sAMAccountType" ]) == SAM_GROUP_OBJECT :
121+ context .log .highlight (f" { child ['sAMAccountName' ]} -> Group" )
122+
123+ def get_sccm_named_objects (self , context , connection ):
124+ """Enumerate users/groups/computers with "SCCM" in their name"""
90125 # hippity hoppity your code is now my property, filter stolen from the awesome sccmhunter repository
91126 # https://github.com/garrettfoster13/sccmhunter
92127 try :
93- yoinkers = ' (|(samaccountname=*sccm*)(samaccountname=*mecm*)(description=*sccm*)(description=*mecm*)(name=*sccm*)(name=*mecm*))'
128+ yoinkers = " (|(samaccountname=*sccm*)(samaccountname=*mecm*)(description=*sccm*)(description=*mecm*)(name=*sccm*)(name=*mecm*))"
94129 context .log .display ("Searching for SCCM related objects" )
95130 result = connection .ldapConnection .search (
96131 searchFilter = yoinkers ,
97132 searchBase = self .base_dn ,
98- attributes = ["sAMAccountName" , "distinguishedName" ],
133+ attributes = ["sAMAccountName" , "distinguishedName" , "sAMAccountType" ],
99134 )
135+
136+ result = parse_result_attributes (result )
137+ for res in result :
138+ if "sAMAccountType" in res and int (res ["sAMAccountType" ]) == SAM_USER_OBJECT :
139+ self .user_objects .append (res ["sAMAccountName" ])
140+ elif "sAMAccountType" in res and int (res ["sAMAccountType" ]) == SAM_MACHINE_ACCOUNT :
141+ self .computer_objects .append (res ["sAMAccountName" ])
142+ elif "sAMAccountType" in res and int (res ["sAMAccountType" ]) == SAM_GROUP_OBJECT :
143+ self .group_objects [res ["distinguishedName" ]] = {
144+ "sAMAccountName" : res ["sAMAccountName" ],
145+ "children" : [],
146+ }
147+ if self .recursive_resolve :
148+ self .resolve_recursive (res ["distinguishedName" ])
149+
100150 except LDAPSearchError as e :
101151 context .log .fail (f"Got unexpected exception: { e } " )
102152
153+ def resolve_recursive (self , dn ):
154+ """Recursively resolve members of a group."""
155+ try :
156+ self .context .log .debug (f"Resolving group members recursively for { dn } " )
157+ # Somehow BaseDN is not working together with the LDAP_MATCHING_RULE_IN_CHAIN
158+ result = self .connection .ldapConnection .search (
159+ searchFilter = f"(memberOf:{ LDAP_MATCHING_RULE_IN_CHAIN } :={ dn } )" ,
160+ attributes = ["sAMAccountName" , "distinguishedName" , "sAMAccountType" ],
161+ )
162+
163+ result = parse_result_attributes (result )
164+ for res in result :
165+ self .group_objects [dn ]["children" ].append ({
166+ "sAMAccountName" : res ["sAMAccountName" ],
167+ "distinguishedName" : res ["distinguishedName" ],
168+ "sAMAccountType" : res ["sAMAccountType" ],
169+ })
170+
171+ except LDAPSearchError as e :
172+ self .context .log .error (f"Error resolving group members: { e } " )
173+
103174 def get_management_points (self ):
104175 """Searches for all SCCM management points in the Active Directory and maps them to their SCCM site via the site code."""
105176 try :
0 commit comments