Skip to content

Commit b988216

Browse files
committed
Finish SCCM basic enumeration
1 parent 4cb3b41 commit b988216

1 file changed

Lines changed: 82 additions & 32 deletions

File tree

nxc/modules/sccm.py

Lines changed: 82 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
import json
2-
import re
31
from impacket.ldap import ldap, ldaptypes, ldapasn1 as ldapasn1_impacket
42
from impacket.ldap.ldap import LDAPSearchError
5-
from impacket.ldap.ldaptypes import SR_SECURITY_DESCRIPTOR
63
from ldap3.protocol.microsoft import security_descriptor_control
74
from nxc.parsers.ldap_results import parse_result_attributes
85

@@ -25,7 +22,8 @@ class NXCModule:
2522
multiple_hosts = True
2623

2724
def __init__(self):
28-
self.sccm_sites = []
25+
self.sccm_site_servers = [] # List of dns host names of the SCCM site servers
26+
self.sccm_sites = {}
2927
self.base_dn = ""
3028

3129
def options(self, context, module_options):
@@ -40,12 +38,10 @@ def on_login(self, context, connection):
4038
self.base_dn = connection.ldapConnection._baseDN if not self.base_dn else self.base_dn
4139
self.sc = ldap.SimplePagedResultsControl()
4240

43-
search_filter = f"(distinguishedName=CN=System Management,CN=System,{self.base_dn})"
44-
controls = security_descriptor_control(sdflags=0x04)
45-
context.log.display(f"Starting LDAP search with search filter '{search_filter}'")
46-
4741
try:
48-
42+
search_filter = f"(distinguishedName=CN=System Management,CN=System,{self.base_dn})"
43+
controls = security_descriptor_control(sdflags=0x04)
44+
context.log.display(f"Looking for the SCCM container with filter: '{search_filter}'")
4945
result = connection.ldapConnection.search(
5046
searchFilter=search_filter,
5147
attributes=["nTSecurityDescriptor"],
@@ -55,14 +51,76 @@ def on_login(self, context, connection):
5551
)
5652
for item in result:
5753
if isinstance(item, ldapasn1_impacket.SearchResultEntry):
58-
raw_sec_descriptor = str(item[1][0][1][0]).encode("latin-1")
59-
principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(data=raw_sec_descriptor)
60-
context.log.highlight(f"Found SCCM object: {item[0]}")
61-
self.parse_dacl(principal_security_descriptor["Dacl"])
62-
self.context.log.highlight(f"Found sccm_sites: {self.sccm_sites}")
54+
self.context.log.success(f"Found SCCM object: {item[0]}")
55+
self.get_site_servers(item)
56+
self.get_sites()
57+
self.get_management_points()
58+
59+
self.context.log.success("Site Servers:")
60+
for site in self.sccm_site_servers:
61+
ip = self.connection.resolver(site)
62+
self.context.log.highlight(f"{site} - {ip['host'] if ip else 'unknown'}")
63+
self.context.log.success("SCCM Sites:")
64+
for site in self.sccm_sites:
65+
self.context.log.highlight(f"{self.sccm_sites[site]['cn']}")
66+
self.context.log.highlight(f" Site Code: {site.rjust(14)}")
67+
self.context.log.highlight(f" Assignment Site Code: {self.sccm_sites[site]['AssignmentSiteCode'].rjust(3)}")
68+
self.context.log.highlight(" Management Points:")
69+
for mp in self.sccm_sites[site]["ManagementPoints"]:
70+
self.context.log.highlight(f"\t CN:{' ':<12}{mp['cn']}")
71+
self.context.log.highlight(f"\t DNS Hostname:{' ':<2}{mp['dNSHostName']}")
72+
self.context.log.highlight(f"\t IP Address:{' ':<4}{mp['IPAddress']}")
73+
self.context.log.highlight(f"\t Default MP:{' ':<4}{mp['mSSMSDefaultMP']}")
74+
self.context.log.highlight("")
6375

6476
except LDAPSearchError as e:
65-
context.log.fail(f"Obtained unexpected exception: {e}")
77+
context.log.fail(f"Got unexpected exception: {e}")
78+
79+
def get_management_points(self):
80+
"""Searches for all SCCM management points in the Active Directory and maps them to their SCCM site."""
81+
try:
82+
response = self.connection.ldapConnection.search(
83+
searchFilter="(objectClass=mSSMSManagementPoint)",
84+
attributes="*",
85+
)
86+
response_parsed = parse_result_attributes(response)
87+
self.context.log.success("SCCM Management Points:")
88+
for mp in response_parsed:
89+
ip = self.connection.resolver(mp["dNSHostName"])
90+
self.sccm_sites[mp["mSSMSSiteCode"]]["ManagementPoints"].append({
91+
"cn": mp["cn"],
92+
"dNSHostName": mp["dNSHostName"],
93+
"IPAddress": ip if ip else "-",
94+
"mSSMSDefaultMP": mp["mSSMSDefaultMP"],
95+
})
96+
97+
except LDAPSearchError as e:
98+
self.context.log.error(f"Error searching for management points: {e}")
99+
100+
def get_sites(self):
101+
"""Searches for all SCCM sites in the Active Directory."""
102+
try:
103+
response = self.connection.ldapConnection.search(
104+
searchFilter="(objectClass=mSSMSSite)",
105+
attributes=["cn", "mSSMSSiteCode", "mSSMSAssignmentSiteCode"],
106+
)
107+
response_parsed = parse_result_attributes(response)
108+
for site in response_parsed:
109+
self.sccm_sites[site["mSSMSSiteCode"]] = {
110+
"cn": site["cn"],
111+
"AssignmentSiteCode": site["mSSMSAssignmentSiteCode"],
112+
"ManagementPoints": []
113+
}
114+
115+
except LDAPSearchError as e:
116+
self.context.log.error(f"Error searching for sites: {e}")
117+
118+
def get_site_servers(self, item):
119+
"""Extracts the site servers from the SCCM object."""
120+
raw_sec_descriptor = str(item[1][0][1][0]).encode("latin-1")
121+
principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(data=raw_sec_descriptor)
122+
self.parse_dacl(principal_security_descriptor["Dacl"])
123+
self.sccm_site_servers = set(self.sccm_site_servers) # Make list unique
66124

67125
def parse_dacl(self, dacl):
68126
"""Parses a DACL and extracts the dns host names with full control over the SCCM object."""
@@ -97,7 +155,7 @@ def resolve_SID(self, sid):
97155

98156
if int(parsed_result["sAMAccountType"]) == SAM_MACHINE_ACCOUNT:
99157
self.context.log.debug(f"Found object with full control over SCCM object. SID: {sid}, dns_hostname: {parsed_result['dNSHostName']}")
100-
self.sccm_sites.append(parsed_result["dNSHostName"])
158+
self.sccm_site_servers.append(parsed_result["dNSHostName"])
101159
elif int(parsed_result["sAMAccountType"]) == SAM_GROUP_OBJECT:
102160
if isinstance(parsed_result["member"], list):
103161
for member in parsed_result["member"]:
@@ -115,19 +173,11 @@ def resolve_SID(self, sid):
115173

116174
def dn_to_sid(self, dn) -> str:
117175
"""Tries to resolve a DN to a SID."""
118-
try:
119-
result = self.connection.ldapConnection.search(
120-
searchBase=self.base_dn,
121-
searchFilter=f"(distinguishedName={dn})",
122-
attributes=["sAMAccountName", "objectSid"],
123-
)
124-
parsed_result = parse_result_attributes(result)[0]
125-
self.context.log.highlight(f"Found object for DN {dn}: {parsed_result[0]}")
126-
if not parsed_result:
127-
return ""
128-
else:
129-
parsed_result = parsed_result[0]
130-
return parsed_result["objectSid"]
131-
except Exception as e:
132-
self.context.log.debug(f"DN not found in LDAP: {dn}, {e}")
133-
return ""
176+
result = self.connection.ldapConnection.search(
177+
searchBase=self.base_dn,
178+
searchFilter=f"(distinguishedName={dn})",
179+
attributes=["sAMAccountName", "objectSid"],
180+
)
181+
182+
sid_raw = bytes(result[0][1][0][1].components[0])
183+
return ldaptypes.LDAP_SID(data=sid_raw).formatCanonical()

0 commit comments

Comments
 (0)