Skip to content

Commit 4e8d650

Browse files
authored
Merge pull request Pennyw0rth#811 from Pennyw0rth/neff-fix-daclread
Add ldap parsing to daclread
2 parents 858d211 + 985a988 commit 4e8d650

2 files changed

Lines changed: 61 additions & 91 deletions

File tree

nxc/modules/daclread.py

Lines changed: 59 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import binascii
22
import codecs
33
import json
4-
import re
54
import datetime
65
from enum import Enum
76
from impacket.ldap import ldaptypes
87
from impacket.uuid import bin_to_string
98
from nxc.helpers.msada_guids import SCHEMA_OBJECTS, EXTENDED_RIGHTS
10-
from ldap3.protocol.formatters.formatters import format_sid
9+
from nxc.parsers.ldap_results import parse_result_attributes
1110
from ldap3.utils.conv import escape_filter_chars
1211
from ldap3.protocol.microsoft import security_descriptor_control
1312
import sys
13+
import traceback
14+
from os.path import isfile
1415

1516
OBJECT_TYPES_GUID = {}
1617
OBJECT_TYPES_GUID.update(SCHEMA_OBJECTS)
@@ -227,12 +228,12 @@ def options(self, context, module_options):
227228

228229
if module_options and "TARGET" in module_options:
229230
context.log.debug("There is a target specified!")
230-
if re.search(r"^(.+)\/([^\/]+)$", module_options["TARGET"]) is not None:
231+
if isfile(module_options["TARGET"]):
231232
try:
232233
self.target_file = open(module_options["TARGET"]) # noqa: SIM115
233234
self.target_sAMAccountName = None
234235
except Exception:
235-
context.log.fail("The file doesn't exist or cannot be openned.")
236+
context.log.fail("The file doesn't exist or cannot be opened.")
236237
else:
237238
context.log.debug(f"Setting target_sAMAccountName to {module_options['TARGET']}")
238239
self.target_sAMAccountName = module_options["TARGET"]
@@ -274,37 +275,50 @@ def on_login(self, context, connection):
274275
context.log.highlight("Be careful, this module cannot read the DACLS recursively.")
275276
self.baseDN = connection.ldap_connection._baseDN
276277
self.ldap_session = connection.ldap_connection
278+
self.connection = connection
279+
self.context = context
277280

278281
# Searching for the principal SID
279282
if self.principal_sAMAccountName is not None:
280-
_lookedup_principal = self.principal_sAMAccountName
281283
try:
282-
self.principal_sid = format_sid(
283-
self.ldap_session.search(
284-
searchBase=self.baseDN,
285-
searchFilter=f"(sAMAccountName={escape_filter_chars(_lookedup_principal)})",
286-
attributes=["objectSid"],
287-
)[0][1][0][1][0]
284+
resp = connection.search(
285+
searchFilter=f"(sAMAccountName={escape_filter_chars(self.principal_sAMAccountName)})",
286+
attributes=["objectSid"],
288287
)
288+
resp_parsed = parse_result_attributes(resp)[0]
289+
self.principal_sid = resp_parsed["objectSid"]
289290
context.log.highlight(f"Found principal SID to filter on: {self.principal_sid}")
290-
except Exception:
291-
context.log.fail(f"Principal SID not found in LDAP ({_lookedup_principal})")
292-
sys.exit(1)
291+
except Exception as e:
292+
context.log.fail(f"Principal SID not found in LDAP ({self.principal_sAMAccountName})")
293+
context.log.debug(f"Exception: {e}, {traceback.format_exc()}")
294+
return
293295

294296
# Searching for the targets SID and their Security Descriptors
295297
# If there is only one target
296298
if (self.target_sAMAccountName or self.target_DN) and self.target_file is None:
297-
# Searching for target account with its security descriptor
298299
try:
299-
self.search_target_principal_security_descriptor(context, connection)
300+
# Searching for target account with its security descriptor
301+
if self.target_sAMAccountName: # noqa: SIM108
302+
search_filter = f"(sAMAccountName={escape_filter_chars(self.target_sAMAccountName)})"
303+
else:
304+
search_filter = f"(distinguishedName={escape_filter_chars(self.target_DN)})"
305+
306+
resp = connection.search(
307+
searchFilter=search_filter,
308+
attributes=["distinguishedName", "nTSecurityDescriptor"],
309+
searchControls=security_descriptor_control(sdflags=0x04),
310+
)
311+
resp_parsed = parse_result_attributes(resp)[0]
312+
300313
# Extract security descriptor data
301-
self.target_principal_dn = self.target_principal[0]
302-
self.principal_raw_security_descriptor = str(self.target_principal[1][0][1][0]).encode("latin-1")
314+
self.target_principal_dn = resp_parsed["distinguishedName"]
315+
self.principal_raw_security_descriptor = resp_parsed["nTSecurityDescriptor"]
303316
self.principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(data=self.principal_raw_security_descriptor)
304-
context.log.highlight(f"Target principal found in LDAP ({self.target_principal[0]})")
305-
except Exception:
317+
context.log.highlight(f"Target principal found in LDAP ({self.target_principal_dn})")
318+
except Exception as e:
306319
context.log.fail(f"Target SID not found in LDAP ({self.target_sAMAccountName})")
307-
sys.exit(1)
320+
context.log.debug(f"Exception: {e}, {traceback.format_exc()}")
321+
return
308322

309323
if self.action == "read":
310324
self.read(context)
@@ -318,10 +332,16 @@ def on_login(self, context, connection):
318332
try:
319333
self.target_sAMAccountName = target.strip()
320334
# Searching for target account with its security descriptor
321-
self.search_target_principal_security_descriptor(context, connection)
335+
resp = connection.search(
336+
searchFilter=f"(sAMAccountName={escape_filter_chars(self.target_sAMAccountName)})",
337+
attributes=["distinguishedName", "nTSecurityDescriptor"],
338+
searchControls=security_descriptor_control(sdflags=0x04),
339+
)
340+
resp_parsed = parse_result_attributes(resp)[0]
341+
322342
# Extract security descriptor data
323-
self.target_principal_dn = self.target_principal[0]
324-
self.principal_raw_security_descriptor = str(self.target_principal[1][0][1][0]).encode("latin-1")
343+
self.target_principal_dn = resp_parsed["distinguishedName"]
344+
self.principal_raw_security_descriptor = resp_parsed["nTSecurityDescriptor"]
325345
self.principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(data=self.principal_raw_security_descriptor)
326346
context.log.highlight(f"Target principal found in LDAP ({self.target_sAMAccountName})")
327347
except Exception:
@@ -355,72 +375,22 @@ def backup(self, context):
355375
context.log.highlight("DACL backed up to %s", self.filename)
356376
self.filename = None
357377

358-
# Attempts to retrieve the DACL in the Security Descriptor of the specified target
359-
def search_target_principal_security_descriptor(self, context, connection):
360-
_lookedup_principal = ""
361-
# Set SD flags to only query for DACL
362-
controls = security_descriptor_control(sdflags=0x04)
363-
if self.target_sAMAccountName is not None:
364-
_lookedup_principal = self.target_sAMAccountName
365-
target = self.ldap_session.search(
366-
searchBase=self.baseDN,
367-
searchFilter=f"(sAMAccountName={escape_filter_chars(_lookedup_principal)})",
368-
attributes=["nTSecurityDescriptor"],
369-
searchControls=controls,
370-
)
371-
if self.target_DN is not None:
372-
_lookedup_principal = self.target_DN
373-
target = self.ldap_session.search(
374-
searchBase=_lookedup_principal,
375-
searchFilter=f"(distinguishedName={_lookedup_principal})",
376-
attributes=["nTSecurityDescriptor"],
377-
searchControls=controls,
378-
)
379-
try:
380-
self.target_principal = target[0]
381-
except Exception:
382-
context.log.fail(f"Principal not found in LDAP ({_lookedup_principal}), probably an LDAP session issue.")
383-
sys.exit(0)
384-
385-
# Attempts to retrieve the SID and Distinguisehd Name from the sAMAccountName
386-
# Not used for the moment
387-
# - samname : a sAMAccountName
388-
def get_user_info(self, context, samname):
389-
self.ldap_session.search(
390-
searchBase=self.baseDN,
391-
searchFilter=f"(sAMAccountName={escape_filter_chars(samname)})",
392-
attributes=["objectSid"],
393-
)
394-
try:
395-
dn = self.ldap_session.entries[0].entry_dn
396-
sid = format_sid(self.ldap_session.entries[0]["objectSid"].raw_values[0])
397-
return dn, sid
398-
except Exception:
399-
context.log.fail(f"User not found in LDAP: {samname}")
400-
return False
401-
402-
# Attempts to resolve a SID and return the corresponding samaccountname
403-
# - sid : the SID to resolve
404-
def resolveSID(self, context, sid):
378+
def resolveSID(self, sid):
379+
"""Resolves a SID to its corresponding sAMAccountName."""
405380
# Tries to resolve the SID from the well known SIDs
406381
if sid in WELL_KNOWN_SIDS:
407382
return WELL_KNOWN_SIDS[sid]
383+
408384
# Tries to resolve the SID from the LDAP domain dump
409-
else:
410-
try:
411-
self.ldap_session.search(
412-
searchBase=self.baseDN,
413-
searchFilter=f"(objectSid={sid})",
414-
attributes=["sAMAccountName"],
415-
)[0][0]
416-
return self.ldap_session.search(
417-
searchBase=self.baseDN,
418-
searchFilter=f"(objectSid={sid})",
419-
attributes=["sAMAccountName"],
420-
)[0][1][0][1][0]
421-
except Exception:
422-
context.log.debug(f"SID not found in LDAP: {sid}")
423-
return ""
385+
try:
386+
resp = self.connection.search(
387+
searchFilter=f"(objectSid={sid})",
388+
attributes=["sAMAccountName"],
389+
)
390+
return parse_result_attributes(resp)[0]["sAMAccountName"]
391+
except Exception:
392+
self.context.log.debug(f"SID not found in LDAP: {sid}")
393+
return ""
424394

425395
# Parses a full DACL
426396
# - dacl : the DACL to parse, submitted in a Security Desciptor format
@@ -458,7 +428,7 @@ def parse_ace(self, context, ace):
458428
# Extracts the access mask (by parsing the simple permissions) and the principal's SID
459429
if ace["TypeName"] in ["ACCESS_ALLOWED_ACE", "ACCESS_DENIED_ACE"]:
460430
access_mask = f"{', '.join(self.parse_perms(ace['Ace']['Mask']['Mask']))} (0x{ace['Ace']['Mask']['Mask']:x})"
461-
trustee_sid = f"{self.resolveSID(context, ace['Ace']['Sid'].formatCanonical()) or 'UNKNOWN'} ({ace['Ace']['Sid'].formatCanonical()})"
431+
trustee_sid = f"{self.resolveSID(ace['Ace']['Sid'].formatCanonical()) or 'UNKNOWN'} ({ace['Ace']['Sid'].formatCanonical()})"
462432
parsed_ace = {
463433
"Access mask": access_mask,
464434
"Trustee (SID)": trustee_sid
@@ -486,7 +456,7 @@ def parse_ace(self, context, ace):
486456
parsed_ace["Inherited type (GUID)"] = f"UNKNOWN ({inh_obj_type})"
487457
# Extract the Trustee SID (the object that has the right over the DACL bearer)
488458
parsed_ace["Trustee (SID)"] = "{} ({})".format(
489-
self.resolveSID(context, ace["Ace"]["Sid"].formatCanonical()) or "UNKNOWN",
459+
self.resolveSID(ace["Ace"]["Sid"].formatCanonical()) or "UNKNOWN",
490460
ace["Ace"]["Sid"].formatCanonical(),
491461
)
492462
else: # if the ACE is not an access allowed
@@ -501,7 +471,7 @@ def parse_ace(self, context, ace):
501471

502472
def print_parsed_dacl(self, context, parsed_dacl):
503473
"""Prints a full DACL by printing each parsed ACE
504-
474+
505475
parsed_dacl : a parsed DACL from parse_dacl()
506476
"""
507477
context.log.debug("Printing parsed DACL")

nxc/protocols/ldap.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ def getUnixTime(self, t):
694694
t /= 10000000
695695
return t
696696

697-
def search(self, searchFilter, attributes, sizeLimit=0, baseDN=None) -> list:
697+
def search(self, searchFilter, attributes, sizeLimit=0, baseDN=None, searchControls=None) -> list:
698698
if baseDN is None and self.args.base_dn is not None:
699699
baseDN = self.args.base_dn
700700
elif baseDN is None:
@@ -712,7 +712,7 @@ def search(self, searchFilter, attributes, sizeLimit=0, baseDN=None) -> list:
712712
searchFilter=searchFilter,
713713
attributes=attributes,
714714
sizeLimit=sizeLimit,
715-
searchControls=paged_search_control,
715+
searchControls=searchControls if searchControls else paged_search_control,
716716
)
717717
except ldap_impacket.LDAPSearchError as e:
718718
if "sizeLimitExceeded" in str(e):

0 commit comments

Comments
 (0)