11import binascii
2- import codecs
32import json
43import datetime
54from enum import Enum
@@ -189,6 +188,12 @@ class ALLOWED_OBJECT_ACE_MASK_FLAGS(Enum):
189188 Self = ldaptypes .ACCESS_ALLOWED_OBJECT_ACE .ADS_RIGHT_DS_SELF
190189
191190
191+ SEARCH_FILTERS = {
192+ "TARGET" : lambda target : f"(sAMAccountName={ escape_filter_chars (target )} )" ,
193+ "TARGET_DN" : lambda target : f"(distinguishedName={ escape_filter_chars (target )} )"
194+ }
195+
196+
192197class NXCModule :
193198 """Module to read and backup the Discretionary Access Control List of one or multiple objects.
194199
@@ -205,6 +210,14 @@ def __init__(self, context=None, module_options=None):
205210 self .context = context
206211 self .module_options = module_options
207212
213+ # Initialize module variables
214+ self .principal_sAMAccountName = None
215+ self .principal_sid = None
216+ self .action = "read"
217+ self .ace_type = "allowed"
218+ self .rights = None
219+ self .rights_guid = None
220+
208221 def options (self , context , module_options ):
209222 """
210223 Be careful, this module cannot read the DACLS recursively.
@@ -226,57 +239,48 @@ def options(self, context, module_options):
226239 context .log .fail ("Select an option, example: -M daclread -o TARGET=Administrator ACTION=read" )
227240 sys .exit (1 )
228241
229- if module_options and "TARGET" in module_options :
230- context .log .debug ("There is a target specified!" )
231- if isfile (module_options ["TARGET" ]):
232- try :
233- self .target_file = open (module_options ["TARGET" ]) # noqa: SIM115
234- self .target_sAMAccountName = None
235- except Exception :
236- context .log .fail ("The file doesn't exist or cannot be opened." )
237- else :
238- context .log .debug (f"Setting target_sAMAccountName to { module_options ['TARGET' ]} " )
239- self .target_sAMAccountName = module_options ["TARGET" ]
240- self .target_file = None
241- self .target_DN = None
242+ self .targets = []
242243 self .target_SID = None
243- if module_options and "TARGET_DN" in module_options :
244- self .target_DN = module_options ["TARGET_DN" ]
245- self .target_sAMAccountName = None
246- self .target_file = None
247244
248- if module_options and "PRINCIPAL" in module_options :
245+ for option in "TARGET" , "TARGET_DN" :
246+ if option in module_options :
247+ context .log .debug ("There is a target specified!" )
248+ if isfile (module_options [option ]):
249+ try :
250+ target_file = open (module_options [option ]) # noqa: SIM115
251+ for line in target_file :
252+ context .log .debug (f"Adding target from file: { line } " )
253+ self .targets .append ((line .strip (), SEARCH_FILTERS [option ]))
254+ except Exception :
255+ context .log .fail ("The file doesn't exist or cannot be opened." )
256+ else :
257+ context .log .debug (f"Adding target: { module_options [option ]} " )
258+ self .targets .append ((module_options [option ].strip (), SEARCH_FILTERS [option ]))
259+
260+ if not self .targets :
261+ context .log .fail ("No target specified, please specify at least one target with the TARGET or TARGET_DN options." )
262+ sys .exit (1 )
263+
264+ if "PRINCIPAL" in module_options :
249265 self .principal_sAMAccountName = module_options ["PRINCIPAL" ]
250- else :
251- self .principal_sAMAccountName = None
252- self .principal_sid = None
253266
254- if module_options and "ACTION" in module_options :
267+ if "ACTION" in module_options :
255268 self .action = module_options ["ACTION" ]
256- else :
257- self .action = "read"
258- if module_options and "ACE_TYPE" in module_options :
269+
270+ if "ACE_TYPE" in module_options :
259271 self .ace_type = module_options ["ACE_TYPE" ]
260- else :
261- self .ace_type = "allowed"
262- if module_options and "RIGHTS" in module_options :
272+
273+ if "RIGHTS" in module_options :
263274 self .rights = module_options ["RIGHTS" ]
264- else :
265- self .rights = None
266- if module_options and "RIGHTS_GUID" in module_options :
275+
276+ if "RIGHTS_GUID" in module_options :
267277 self .rights_guid = module_options ["RIGHTS_GUID" ]
268- else :
269- self .rights_guid = None
270- self .filename = None
271278
272279 def on_login (self , context , connection ):
273- self .context = context
274280 """On a successful LDAP login we perform a search for the targets' SID, their Security Descriptors and the principal's SID if there is one specified"""
275281 context .log .highlight ("Be careful, this module cannot read the DACLS recursively." )
276- self .baseDN = connection .ldap_connection ._baseDN
277- self .ldap_session = connection .ldap_connection
278- self .connection = connection
279282 self .context = context
283+ self .connection = connection
280284
281285 # Searching for the principal SID
282286 if self .principal_sAMAccountName is not None :
@@ -294,86 +298,49 @@ def on_login(self, context, connection):
294298 return
295299
296300 # Searching for the targets SID and their Security Descriptors
297- # If there is only one target
298- if (self .target_sAMAccountName or self .target_DN ) and self .target_file is None :
301+ for target , search_filter in self .targets :
299302 try :
300303 # 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-
306304 resp = connection .search (
307- searchFilter = search_filter ,
305+ searchFilter = search_filter ( target ) ,
308306 attributes = ["distinguishedName" , "nTSecurityDescriptor" ],
309307 searchControls = security_descriptor_control (sdflags = 0x04 ),
310308 )
311309 resp_parsed = parse_result_attributes (resp )[0 ]
312310
313311 # Extract security descriptor data
314- self . target_principal_dn = resp_parsed ["distinguishedName" ]
315- self . principal_raw_security_descriptor = resp_parsed ["nTSecurityDescriptor" ]
316- self . principal_security_descriptor = ldaptypes .SR_SECURITY_DESCRIPTOR (data = self . principal_raw_security_descriptor )
317- context .log .highlight (f"Target principal found in LDAP ({ self . target_principal_dn } )" )
312+ target_principal_dn = resp_parsed ["distinguishedName" ]
313+ principal_raw_security_descriptor = resp_parsed ["nTSecurityDescriptor" ]
314+ principal_security_descriptor = ldaptypes .SR_SECURITY_DESCRIPTOR (data = principal_raw_security_descriptor )
315+ context .log .highlight (f"Target principal found in LDAP ({ target_principal_dn } )" )
318316 except Exception as e :
319- context .log .fail (f"Target SID not found in LDAP ({ self . target_sAMAccountName } )" )
317+ context .log .fail (f"Target SID not found in LDAP ({ target } )" )
320318 context .log .debug (f"Exception: { e } , { traceback .format_exc ()} " )
321- return
319+ continue
322320
323321 if self .action == "read" :
324- self .read (context )
322+ self .read (principal_security_descriptor )
325323 if self .action == "backup" :
326- self .backup (context )
327-
328- # If there are multiple targets
329- else :
330- targets = self .target_file .readlines ()
331- for target in targets :
332- try :
333- self .target_sAMAccountName = target .strip ()
334- # Searching for target account with its security descriptor
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-
342- # Extract security descriptor data
343- self .target_principal_dn = resp_parsed ["distinguishedName" ]
344- self .principal_raw_security_descriptor = resp_parsed ["nTSecurityDescriptor" ]
345- self .principal_security_descriptor = ldaptypes .SR_SECURITY_DESCRIPTOR (data = self .principal_raw_security_descriptor )
346- context .log .highlight (f"Target principal found in LDAP ({ self .target_sAMAccountName } )" )
347- except Exception :
348- context .log .fail (f"Target SID not found in LDAP ({ self .target_sAMAccountName } )" )
349- continue
350-
351- if self .action == "read" :
352- self .read (context )
353- if self .action == "backup" :
354- self .backup (context )
324+ self .backup (target , target_principal_dn , principal_raw_security_descriptor )
355325
356326 # Main read funtion
357327 # Prints the parsed DACL
358- def read (self , context ):
359- parsed_dacl = self .parse_dacl (context , self . principal_security_descriptor ["Dacl" ])
360- self .print_parsed_dacl (context , parsed_dacl )
328+ def read (self , principal_security_descriptor ):
329+ parsed_dacl = self .parse_dacl (principal_security_descriptor ["Dacl" ])
330+ self .print_parsed_dacl (parsed_dacl )
361331
362332 # Permits to export the DACL of the targets
363333 # This function is called before any writing action (write, remove or restore)
364- def backup (self , context ):
334+ def backup (self , target , target_principal_dn , principal_raw_security_descriptor ):
365335 backup = {}
366- backup ["sd" ] = binascii .hexlify (self .principal_raw_security_descriptor ).decode ("latin-1" )
367- backup ["dn" ] = str (self .target_principal_dn )
368- if not self .filename :
369- self .filename = "dacledit-{}-{}.bak" .format (
370- datetime .datetime .now ().strftime ("%Y%m%d-%H%M%S" ),
371- self .target_sAMAccountName ,
372- )
373- with codecs .open (self .filename , "w" , "latin-1" ) as outfile :
336+ backup ["sd" ] = binascii .hexlify (principal_raw_security_descriptor ).decode ("latin-1" )
337+ backup ["dn" ] = str (target_principal_dn )
338+
339+ timestamp = datetime .datetime .now ().strftime ("%Y%m%d-%H%M%S" )
340+ filename = f"dacledit-{ timestamp } -{ target } .bak"
341+ with open (filename , "w" , encoding = "latin-1" ) as outfile :
374342 json .dump (backup , outfile )
375- context .log .highlight ("DACL backed up to %s" , self .filename )
376- self .filename = None
343+ self .context .log .highlight (f"DACL backed up to { filename } " )
377344
378345 def resolveSID (self , sid ):
379346 """Resolves a SID to its corresponding sAMAccountName."""
@@ -394,11 +361,11 @@ def resolveSID(self, sid):
394361
395362 # Parses a full DACL
396363 # - dacl : the DACL to parse, submitted in a Security Desciptor format
397- def parse_dacl (self , context , dacl ):
364+ def parse_dacl (self , dacl ):
398365 parsed_dacl = []
399- context .log .debug ("Parsing DACL" )
366+ self . context .log .debug ("Parsing DACL" )
400367 for ace in dacl ["Data" ]:
401- parsed_ace = self .parse_ace (context , ace )
368+ parsed_ace = self .parse_ace (ace )
402369 parsed_dacl .append (parsed_ace )
403370 return parsed_dacl
404371
@@ -413,7 +380,7 @@ def parse_perms(self, access_mask):
413380
414381 # Parses a specified ACE and extract the different values (Flags, Access Mask, Trustee, ObjectType, InheritedObjectType)
415382 # - ace : the ACE to parse
416- def parse_ace (self , context , ace ):
383+ def parse_ace (self , ace ):
417384 # For the moment, only the Allowed and Denied Access ACE are supported
418385 if ace ["TypeName" ] in [
419386 "ACCESS_ALLOWED_ACE" ,
@@ -455,12 +422,9 @@ def parse_ace(self, context, ace):
455422 except KeyError :
456423 parsed_ace ["Inherited type (GUID)" ] = f"UNKNOWN ({ inh_obj_type } )"
457424 # Extract the Trustee SID (the object that has the right over the DACL bearer)
458- parsed_ace ["Trustee (SID)" ] = "{} ({})" .format (
459- self .resolveSID (ace ["Ace" ]["Sid" ].formatCanonical ()) or "UNKNOWN" ,
460- ace ["Ace" ]["Sid" ].formatCanonical (),
461- )
425+ parsed_ace ["Trustee (SID)" ] = f"{ self .resolveSID (ace ['Ace' ]['Sid' ].formatCanonical ()) or 'UNKNOWN' } ({ ace ['Ace' ]['Sid' ].formatCanonical ()} )"
462426 else : # if the ACE is not an access allowed
463- context .log .debug (f"ACE Type ({ ace ['TypeName' ]} ) unsupported for parsing yet, feel free to contribute" )
427+ self . context .log .debug (f"ACE Type ({ ace ['TypeName' ]} ) unsupported for parsing yet, feel free to contribute" )
464428 _ace_flags = [FLAG .name for FLAG in ACE_FLAGS if ace .hasFlag (FLAG .value )]
465429 parsed_ace = {
466430 "ACE type" : ace ["TypeName" ],
@@ -469,18 +433,18 @@ def parse_ace(self, context, ace):
469433 }
470434 return parsed_ace
471435
472- def print_parsed_dacl (self , context , parsed_dacl ):
436+ def print_parsed_dacl (self , parsed_dacl ):
473437 """Prints a full DACL by printing each parsed ACE
474438
475439 parsed_dacl : a parsed DACL from parse_dacl()
476440 """
477- context .log .debug ("Printing parsed DACL" )
441+ self . context .log .debug ("Printing parsed DACL" )
478442 # If a specific right or a specific GUID has been specified, only the ACE with this right will be printed
479443 # If an ACE type has been specified, only the ACE with this type will be specified
480444 # If a principal has been specified, only the ACE where he is the trustee will be printed
481445 for i , parsed_ace in enumerate (parsed_dacl ):
482446 print_ace = True
483- context .log .debug (f"{ parsed_ace = } , { self .rights = } , { self .rights_guid = } , { self .ace_type = } , { self .principal_sid = } " )
447+ self . context .log .debug (f"{ parsed_ace = } , { self .rights = } , { self .rights_guid = } , { self .ace_type = } , { self .principal_sid = } " )
484448
485449 # Filter on specific rights
486450 if self .rights is not None :
@@ -494,37 +458,37 @@ def print_parsed_dacl(self, context, parsed_dacl):
494458 if (self .rights == "ResetPassword" ) and (("Object type (GUID)" not in parsed_ace ) or (RIGHTS_GUID .ResetPassword .value not in parsed_ace ["Object type (GUID)" ])):
495459 print_ace = False
496460 except Exception as e :
497- context .log .debug (f"Error filtering with { parsed_ace = } and { self .rights = } , probably because of ACE type unsupported for parsing yet ({ e } )" )
461+ self . context .log .debug (f"Error filtering with { parsed_ace = } and { self .rights = } , probably because of ACE type unsupported for parsing yet ({ e } )" )
498462
499463 # Filter on specific right GUID
500464 if self .rights_guid is not None :
501465 try :
502466 if ("Object type (GUID)" not in parsed_ace ) or (self .rights_guid not in parsed_ace ["Object type (GUID)" ]):
503467 print_ace = False
504468 except Exception as e :
505- context .log .debug (f"Error filtering with { parsed_ace = } and { self .rights_guid = } , probably because of ACE type unsupported for parsing yet ({ e } )" )
469+ self . context .log .debug (f"Error filtering with { parsed_ace = } and { self .rights_guid = } , probably because of ACE type unsupported for parsing yet ({ e } )" )
506470
507471 # Filter on ACE type
508472 if self .ace_type == "allowed" :
509473 try :
510474 if ("ACCESS_ALLOWED_OBJECT_ACE" not in parsed_ace ["ACE Type" ]) and ("ACCESS_ALLOWED_ACE" not in parsed_ace ["ACE Type" ]):
511475 print_ace = False
512476 except Exception as e :
513- context .log .debug (f"Error filtering with { parsed_ace = } and { self .ace_type = } , probably because of ACE type unsupported for parsing yet ({ e } )" )
477+ self . context .log .debug (f"Error filtering with { parsed_ace = } and { self .ace_type = } , probably because of ACE type unsupported for parsing yet ({ e } )" )
514478 else :
515479 try :
516480 if ("ACCESS_DENIED_OBJECT_ACE" not in parsed_ace ["ACE Type" ]) and ("ACCESS_DENIED_ACE" not in parsed_ace ["ACE Type" ]):
517481 print_ace = False
518482 except Exception as e :
519- context .log .debug (f"Error filtering with { parsed_ace = } and { self .ace_type = } , probably because of ACE type unsupported for parsing yet ({ e } )" )
483+ self . context .log .debug (f"Error filtering with { parsed_ace = } and { self .ace_type = } , probably because of ACE type unsupported for parsing yet ({ e } )" )
520484
521485 # Filter on trusted principal
522486 if self .principal_sid is not None :
523487 try :
524488 if self .principal_sid not in parsed_ace ["Trustee (SID)" ]:
525489 print_ace = False
526490 except Exception as e :
527- context .log .debug (f"Error filtering with { parsed_ace = } and { self .principal_sid = } , probably because of ACE type unsupported for parsing yet ({ e } )" )
491+ self . context .log .debug (f"Error filtering with { parsed_ace = } and { self .principal_sid = } , probably because of ACE type unsupported for parsing yet ({ e } )" )
528492 if print_ace :
529493 self .context .log .highlight (f"ACE[{ i } ] info" )
530494 self .print_parsed_ace (parsed_ace )
0 commit comments