1+ import json
12import re
2- from impacket .ldap import ldap , ldapasn1
3+ from impacket .ldap import ldap , ldaptypes , ldapasn1 as ldapasn1_impacket
34from impacket .ldap .ldap import LDAPSearchError
5+ from impacket .ldap .ldaptypes import SR_SECURITY_DESCRIPTOR
6+ from ldap3 .protocol .microsoft import security_descriptor_control
7+ from nxc .parsers .ldap_results import parse_result_attributes
8+
9+ SAM_MACHINE_ACCOUNT = 0x30000001
10+ SAM_GROUP_OBJECT = 0x10000000
11+
12+ # Universal SIDs
13+ WELL_KNOWN_SIDS = {
14+ "S-1-0" : "Null Authority" ,
15+ "S-1-0-0" : "Nobody" ,
16+ "S-1-1" : "World Authority" ,
17+ "S-1-1-0" : "Everyone" ,
18+ "S-1-2" : "Local Authority" ,
19+ "S-1-2-0" : "Local" ,
20+ "S-1-2-1" : "Console Logon" ,
21+ "S-1-3" : "Creator Authority" ,
22+ "S-1-3-0" : "Creator Owner" ,
23+ "S-1-3-1" : "Creator Group" ,
24+ "S-1-3-2" : "Creator Owner Server" ,
25+ "S-1-3-3" : "Creator Group Server" ,
26+ "S-1-3-4" : "Owner Rights" ,
27+ "S-1-5-80-0" : "All Services" ,
28+ "S-1-4" : "Non-unique Authority" ,
29+ "S-1-5" : "NT Authority" ,
30+ "S-1-5-1" : "Dialup" ,
31+ "S-1-5-2" : "Network" ,
32+ "S-1-5-3" : "Batch" ,
33+ "S-1-5-4" : "Interactive" ,
34+ "S-1-5-6" : "Service" ,
35+ "S-1-5-7" : "Anonymous" ,
36+ "S-1-5-8" : "Proxy" ,
37+ "S-1-5-9" : "Enterprise Domain Controllers" ,
38+ "S-1-5-10" : "Principal Self" ,
39+ "S-1-5-11" : "Authenticated Users" ,
40+ "S-1-5-12" : "Restricted Code" ,
41+ "S-1-5-13" : "Terminal Server Users" ,
42+ "S-1-5-14" : "Remote Interactive Logon" ,
43+ "S-1-5-15" : "This Organization" ,
44+ "S-1-5-17" : "This Organization" ,
45+ "S-1-5-18" : "Local System" ,
46+ "S-1-5-19" : "NT Authority" ,
47+ "S-1-5-20" : "NT Authority" ,
48+ "S-1-5-32-544" : "Administrators" ,
49+ "S-1-5-32-545" : "Users" ,
50+ "S-1-5-32-546" : "Guests" ,
51+ "S-1-5-32-547" : "Power Users" ,
52+ "S-1-5-32-548" : "Account Operators" ,
53+ "S-1-5-32-549" : "Server Operators" ,
54+ "S-1-5-32-550" : "Print Operators" ,
55+ "S-1-5-32-551" : "Backup Operators" ,
56+ "S-1-5-32-552" : "Replicators" ,
57+ "S-1-5-64-10" : "NTLM Authentication" ,
58+ "S-1-5-64-14" : "SChannel Authentication" ,
59+ "S-1-5-64-21" : "Digest Authority" ,
60+ "S-1-5-80" : "NT Service" ,
61+ "S-1-5-83-0" : "NT VIRTUAL MACHINE\\ Virtual Machines" ,
62+ "S-1-16-0" : "Untrusted Mandatory Level" ,
63+ "S-1-16-4096" : "Low Mandatory Level" ,
64+ "S-1-16-8192" : "Medium Mandatory Level" ,
65+ "S-1-16-8448" : "Medium Plus Mandatory Level" ,
66+ "S-1-16-12288" : "High Mandatory Level" ,
67+ "S-1-16-16384" : "System Mandatory Level" ,
68+ "S-1-16-20480" : "Protected Process Mandatory Level" ,
69+ "S-1-16-28672" : "Secure Process Mandatory Level" ,
70+ "S-1-5-32-554" : "BUILTIN\\ Pre-Windows 2000 Compatible Access" ,
71+ "S-1-5-32-555" : "BUILTIN\\ Remote Desktop Users" ,
72+ "S-1-5-32-557" : "BUILTIN\\ Incoming Forest Trust Builders" ,
73+ "S-1-5-32-556" : "BUILTIN\\ Network Configuration Operators" ,
74+ "S-1-5-32-558" : "BUILTIN\\ Performance Monitor Users" ,
75+ "S-1-5-32-559" : "BUILTIN\\ Performance Log Users" ,
76+ "S-1-5-32-560" : "BUILTIN\\ Windows Authorization Access Group" ,
77+ "S-1-5-32-561" : "BUILTIN\\ Terminal Server License Servers" ,
78+ "S-1-5-32-562" : "BUILTIN\\ Distributed COM Users" ,
79+ "S-1-5-32-569" : "BUILTIN\\ Cryptographic Operators" ,
80+ "S-1-5-32-573" : "BUILTIN\\ Event Log Readers" ,
81+ "S-1-5-32-574" : "BUILTIN\\ Certificate Service DCOM Access" ,
82+ "S-1-5-32-575" : "BUILTIN\\ RDS Remote Access Servers" ,
83+ "S-1-5-32-576" : "BUILTIN\\ RDS Endpoint Servers" ,
84+ "S-1-5-32-577" : "BUILTIN\\ RDS Management Servers" ,
85+ "S-1-5-32-578" : "BUILTIN\\ Hyper-V Administrators" ,
86+ "S-1-5-32-579" : "BUILTIN\\ Access Control Assistance Operators" ,
87+ "S-1-5-32-580" : "BUILTIN\\ Remote Management Users" ,
88+ }
489
590
691class NXCModule :
792 """
8- Find PKI Enrollment Services in Active Directory and Certificate Templates Names.
93+ Implementation of the SCCM RECON-1 technique to find SCCM related objects in Active Directory.
94+ See: https://github.com/subat0mik/Misconfiguration-Manager/blob/main/attack-techniques/RECON/RECON-1/recon-1_description.md
995
10- Module by Tobias Neitzel (@qtc_de) and Sam Freeside (@snovvcrash)
96+ Module by @NeffIsBack
1197 """
1298
13- name = "adcs "
14- description = "Find PKI Enrollment Services in Active Directory and Certificate Templates Names "
99+ name = "sccm "
100+ description = "Find a SCCM infrastructure in the Active Directory"
15101 supported_protocols = ["ldap" ]
16102 opsec_safe = True
17103 multiple_hosts = True
18104
19- def __init__ (self , context = None , module_options = None ):
20- self .context = context
21- self .module_options = module_options
22- self .server = None
23- self .regex = None
105+ def __init__ (self ):
106+ self .sAMAccountNames = []
107+ self .base_dn = ""
24108
25109 def options (self , context , module_options ):
26- """
27- BASE_DN The base domain name for the LDAP query
28- """
29- self .regex = re .compile ("(https?://.+)" )
30-
31- self .server = None
32- self .base_dn = None
33- if module_options and "SERVER" in module_options :
34- self .server = module_options ["SERVER" ]
110+ """BASE_DN The base domain name for the LDAP query"""
35111 if module_options and "BASE_DN" in module_options :
36112 self .base_dn = module_options ["BASE_DN" ]
37113
38114 def on_login (self , context , connection ):
39115 """On a successful LDAP login we perform a search for all PKI Enrollment Server or Certificate Templates Names."""
40116 self .context = context
41- search_filter = "(|(objectClass=mSSMSSite)(objectClass=mSSMSManagementPoint)(objectClass=mSSMSRoamingBoundaryRange)(objectClass=mSSMSServer))"
117+ self .connection = connection
118+ self .base_dn = connection .ldapConnection ._baseDN if not self .base_dn else self .base_dn
119+ self .sc = ldap .SimplePagedResultsControl ()
120+
121+ search_filter = f"(distinguishedName=CN=System Management,CN=System,{ self .base_dn } )"
122+ controls = security_descriptor_control (sdflags = 0x04 )
42123 context .log .display (f"Starting LDAP search with search filter '{ search_filter } '" )
43124
44125 try :
45- sc = ldap .SimplePagedResultsControl ()
46- base_dn_root = connection .ldapConnection ._baseDN if self .base_dn is None else self .base_dn
47126
48127 result = connection .ldapConnection .search (
49128 searchFilter = search_filter ,
50- attributes = [],
129+ attributes = ["nTSecurityDescriptor" ],
51130 sizeLimit = 0 ,
52- searchControls = [ sc ] ,
53- searchBase = base_dn_root ,
131+ searchControls = controls ,
132+ searchBase = self . base_dn ,
54133 )
134+ for item in result :
135+ if isinstance (item , ldapasn1_impacket .SearchResultEntry ):
136+ raw_sec_descriptor = str (item [1 ][0 ][1 ][0 ]).encode ("latin-1" )
137+ principal_security_descriptor = ldaptypes .SR_SECURITY_DESCRIPTOR (data = raw_sec_descriptor )
138+ context .log .highlight (f"Found SCCM object: { item [0 ]} " )
139+ self .parse_dacl (principal_security_descriptor ["Dacl" ])
140+ self .context .log .highlight (f"Found sAMAccountNames: { self .sAMAccountNames } " )
141+
142+
55143 except LDAPSearchError as e :
56144 context .log .fail (f"Obtained unexpected exception: { e } " )
57145
58-
146+ def parse_dacl (self , dacl ):
147+ """Parses a DACL and extracts the sAMAccountNames with full control."""
148+ parsed_dacl = []
149+ self .context .log .debug ("Parsing DACL" )
150+ for ace in dacl ["Data" ]:
151+ parsed_ace = self .parse_ace (ace )
152+ parsed_dacl .append (parsed_ace )
153+
154+ def parse_ace (self , ace ):
155+ """Parses an ACE and appends the sAMAccountName to the list of known sAMAccountNames if the SID of the ACE has full control."""
156+ if ace ["TypeName" ] in ["ACCESS_ALLOWED_ACE" , "ACCESS_ALLOWED_OBJECT_ACE" ]:
157+ ace = ace ["Ace" ]
158+ sid = ace ["Sid" ].formatCanonical ()
159+ mask = ace ["Mask" ]
160+ fullcontrol = 0xf01ff
161+ if mask .hasPriv (fullcontrol ):
162+ self .context .log .debug (f"Full control for { sid } " )
163+ print (f"SID: { sid } , sAMAccountName: { self .resolveSID (sid )} " )
164+ self .sAMAccountNames .append (str (self .resolveSID (sid )))
165+
166+ def resolveSID (self , sid ) -> str :
167+ """Tries to resolve a SID and returns the corresponding sAMAccountName if found."""
168+ try :
169+ result = self .connection .ldapConnection .search (
170+ searchBase = self .base_dn ,
171+ searchFilter = f"(objectSid={ sid } )" ,
172+ attributes = ["sAMAccountName" , "sAMAccountType" , "member" , "dNSHostName" ],
173+ )
174+ parsed_result = parse_result_attributes (result )
175+ if not parsed_result :
176+ return ""
177+ else :
178+ parsed_result = parsed_result [0 ]
179+
180+ if int (parsed_result ["sAMAccountType" ]) == SAM_MACHINE_ACCOUNT :
181+ print (f"{ parsed_result ['sAMAccountName' ]} IS MACHINE ACCOUNT" )
182+ return parsed_result ["sAMAccountName" ]
183+ elif int (parsed_result ["sAMAccountType" ]) == SAM_GROUP_OBJECT :
184+ print (f"{ parsed_result ['sAMAccountName' ]} IS GROUP OBJECT" )
185+ print (parsed_result ["member" ])
186+ return ""
187+
188+
189+ except Exception as e :
190+ print (e .with_traceback ())
191+ self .context .log .debug (f"SID not found in LDAP: { sid } , { e } " )
192+ return ""
0 commit comments