Skip to content

Commit 018ad9d

Browse files
committed
Working on SCCM enumeration
1 parent 1f83e63 commit 018ad9d

1 file changed

Lines changed: 160 additions & 26 deletions

File tree

nxc/modules/sccm.py

Lines changed: 160 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,192 @@
1+
import json
12
import re
2-
from impacket.ldap import ldap, ldapasn1
3+
from impacket.ldap import ldap, ldaptypes, ldapasn1 as ldapasn1_impacket
34
from 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

691
class 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

Comments
 (0)