11import binascii
22import codecs
33import json
4- import re
54import datetime
65from enum import Enum
76from impacket .ldap import ldaptypes
87from impacket .uuid import bin_to_string
98from 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
1110from ldap3 .utils .conv import escape_filter_chars
1211from ldap3 .protocol .microsoft import security_descriptor_control
1312import sys
13+ import traceback
14+ from os .path import isfile
1415
1516OBJECT_TYPES_GUID = {}
1617OBJECT_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" )
0 commit comments