33from ldap3 .protocol .microsoft import security_descriptor_control
44
55RELEVANT_OBJECT_TYPES = {
6- ' 00000000-0000-0000-0000-000000000000' : ' All Objects' ,
7- ' 0feb936f-47b3-49f2-9386-1dedc2c23765' : ' msDS-DelegatedManagedServiceAccount' ,
6+ " 00000000-0000-0000-0000-000000000000" : " All Objects" ,
7+ " 0feb936f-47b3-49f2-9386-1dedc2c23765" : " msDS-DelegatedManagedServiceAccount" ,
88}
99
10- EXCLUDED_SIDS_SUFFIXES = [' -512' , ' -519' ] # Domain Admins, Enterprise Admins
11- EXCLUDED_SIDS = [' S-1-5-32-544' , ' S-1-5-18' ] # Builtin Administrators, Local SYSTEM
10+ EXCLUDED_SIDS_SUFFIXES = [" -512" , " -519" ] # Domain Admins, Enterprise Admins
11+ EXCLUDED_SIDS = [" S-1-5-32-544" , " S-1-5-18" ] # Builtin Administrators, Local SYSTEM
1212
1313# Define all access rights
1414ACCESS_RIGHTS = {
1515 # Generic Rights
16- ' GenericRead' : 0x80000000 , # ADS_RIGHT_GENERIC_READ
17- ' GenericWrite' : 0x40000000 , # ADS_RIGHT_GENERIC_WRITE
18- ' GenericExecute' : 0x20000000 , # ADS_RIGHT_GENERIC_EXECUTE
19- ' GenericAll' : 0x10000000 , # ADS_RIGHT_GENERIC_ALL
20-
21- # Maximum Allowed access type
22- ' MaximumAllowed' : 0x02000000 ,
23-
24- # Access System Acl access type
25- ' AccessSystemSecurity' : 0x01000000 , # ADS_RIGHT_ACCESS_SYSTEM_SECURITY
26-
27- # Standard access types
28- ' Synchronize' : 0x00100000 , # ADS_RIGHT_SYNCHRONIZE
29- ' WriteOwner' : 0x00080000 , # ADS_RIGHT_WRITE_OWNER
30- ' WriteDACL' : 0x00040000 , # ADS_RIGHT_WRITE_DAC
31- ' ReadControl' : 0x00020000 , # ADS_RIGHT_READ_CONTROL
32- ' Delete' : 0x00010000 , # ADS_RIGHT_DELETE
33-
34- # Specific rights
35- ' AllExtendedRights' : 0x00000100 , # ADS_RIGHT_DS_CONTROL_ACCESS
36- ' ListObject' : 0x00000080 , # ADS_RIGHT_DS_LIST_OBJECT
37- ' DeleteTree' : 0x00000040 , # ADS_RIGHT_DS_DELETE_TREE
38- ' WriteProperties' : 0x00000020 , # ADS_RIGHT_DS_WRITE_PROP
39- ' ReadProperties' : 0x00000010 , # ADS_RIGHT_DS_READ_PROP
40- ' Self' : 0x00000008 , # ADS_RIGHT_DS_SELF
41- ' ListChildObjects' : 0x00000004 , # ADS_RIGHT_ACTRL_DS_LIST
42- ' DeleteChild' : 0x00000002 , # ADS_RIGHT_DS_DELETE_CHILD
43- ' CreateChild' : 0x00000001 , # ADS_RIGHT_DS_CREATE_CHILD
16+ " GenericRead" : 0x80000000 , # ADS_RIGHT_GENERIC_READ
17+ " GenericWrite" : 0x40000000 , # ADS_RIGHT_GENERIC_WRITE
18+ " GenericExecute" : 0x20000000 , # ADS_RIGHT_GENERIC_EXECUTE
19+ " GenericAll" : 0x10000000 , # ADS_RIGHT_GENERIC_ALL
20+
21+ # Maximum Allowed access type
22+ " MaximumAllowed" : 0x02000000 ,
23+
24+ # Access System Acl access type
25+ " AccessSystemSecurity" : 0x01000000 , # ADS_RIGHT_ACCESS_SYSTEM_SECURITY
26+
27+ # Standard access types
28+ " Synchronize" : 0x00100000 , # ADS_RIGHT_SYNCHRONIZE
29+ " WriteOwner" : 0x00080000 , # ADS_RIGHT_WRITE_OWNER
30+ " WriteDACL" : 0x00040000 , # ADS_RIGHT_WRITE_DAC
31+ " ReadControl" : 0x00020000 , # ADS_RIGHT_READ_CONTROL
32+ " Delete" : 0x00010000 , # ADS_RIGHT_DELETE
33+
34+ # Specific rights
35+ " AllExtendedRights" : 0x00000100 , # ADS_RIGHT_DS_CONTROL_ACCESS
36+ " ListObject" : 0x00000080 , # ADS_RIGHT_DS_LIST_OBJECT
37+ " DeleteTree" : 0x00000040 , # ADS_RIGHT_DS_DELETE_TREE
38+ " WriteProperties" : 0x00000020 , # ADS_RIGHT_DS_WRITE_PROP
39+ " ReadProperties" : 0x00000010 , # ADS_RIGHT_DS_READ_PROP
40+ " Self" : 0x00000008 , # ADS_RIGHT_DS_SELF
41+ " ListChildObjects" : 0x00000004 , # ADS_RIGHT_ACTRL_DS_LIST
42+ " DeleteChild" : 0x00000002 , # ADS_RIGHT_DS_DELETE_CHILD
43+ " CreateChild" : 0x00000001 , # ADS_RIGHT_DS_CREATE_CHILD
4444}
4545
4646# Define which rights are considered relevant for potential abuse
4747RELEVANT_RIGHTS = {
48- ' GenericAll' : ACCESS_RIGHTS [' GenericAll' ],
49- ' GenericWrite' : ACCESS_RIGHTS [' GenericWrite' ],
50- ' WriteOwner' : ACCESS_RIGHTS [' WriteOwner' ],
51- ' WriteDACL' : ACCESS_RIGHTS [' WriteDACL' ],
52- ' CreateChild' : ACCESS_RIGHTS [' CreateChild' ],
53- ' WriteProperties' : ACCESS_RIGHTS [' WriteProperties' ],
54- ' AllExtendedRights' : ACCESS_RIGHTS [' AllExtendedRights' ]
48+ " GenericAll" : ACCESS_RIGHTS [" GenericAll" ],
49+ " GenericWrite" : ACCESS_RIGHTS [" GenericWrite" ],
50+ " WriteOwner" : ACCESS_RIGHTS [" WriteOwner" ],
51+ " WriteDACL" : ACCESS_RIGHTS [" WriteDACL" ],
52+ " CreateChild" : ACCESS_RIGHTS [" CreateChild" ],
53+ " WriteProperties" : ACCESS_RIGHTS [" WriteProperties" ],
54+ " AllExtendedRights" : ACCESS_RIGHTS [" AllExtendedRights" ]
5555}
5656
5757class NXCModule :
@@ -63,7 +63,7 @@ class NXCModule:
6363
6464 name = "badsuccessor"
6565 description = "Check if vulnerable to bad successor attack (DMSA)"
66- supported_protocols = [' ldap' ]
66+ supported_protocols = [" ldap" ]
6767 opsec_safe = True
6868 multiple_hosts = True
6969
@@ -79,63 +79,57 @@ def options(self, context, module_options):
7979 def is_excluded_sid (self , sid , domain_sid ):
8080 if sid in EXCLUDED_SIDS :
8181 return True
82- for suffix in EXCLUDED_SIDS_SUFFIXES :
83- if sid .startswith (domain_sid ) and sid .endswith (suffix ):
84- return True
85- return False
82+ return any (sid .startswith (domain_sid ) and sid .endswith (suffix ) for suffix in EXCLUDED_SIDS_SUFFIXES )
8683
8784 def get_domain_sid (self , ldap_session , base_dn ):
88- """
89- Retrieve the domain SID from the domain object in LDAP
90- """
85+ """Retrieve the domain SID from the domain object in LDAP"""
9186 r = ldap_session .search (
9287 searchBase = base_dn ,
93- searchFilter = ' (objectClass=domain)' ,
94- attributes = [' objectSid' ]
88+ searchFilter = " (objectClass=domain)" ,
89+ attributes = [" objectSid" ]
9590 )
9691 parsed = parse_result_attributes (r )
97- if parsed and 'objectSid' in parsed [0 ]:
98- raw_sid = parsed [0 ]['objectSid' ]
99- return raw_sid
92+ if parsed and "objectSid" in parsed [0 ]:
93+ return parsed [0 ]["objectSid" ]
10094
10195 def find_bad_successor_ous (self , ldap_session , entries , base_dn ):
10296
10397 domain_sid = self .get_domain_sid (ldap_session , base_dn )
10498 results = {}
10599 parsed = parse_result_attributes (entries )
106100 for entry in parsed :
107- dn = entry [' distinguishedName' ]
108- sd_data = entry [' nTSecurityDescriptor' ]
101+ dn = entry [" distinguishedName" ]
102+ sd_data = entry [" nTSecurityDescriptor" ]
109103 sd = ldaptypes .SR_SECURITY_DESCRIPTOR (data = sd_data )
110104
111- for ace in sd [' Dacl' ][ ' Data' ]:
112- if ace [' AceType' ] != ldaptypes .ACCESS_ALLOWED_ACE .ACE_TYPE :
105+ for ace in sd [" Dacl" ][ " Data" ]:
106+ if ace [" AceType" ] != ldaptypes .ACCESS_ALLOWED_ACE .ACE_TYPE :
113107 continue
114108
115109 has_relevant_right = False
116- mask = int (ace [' Ace' ][ ' Mask' ][ ' Mask' ])
117- for right_name , right_value in RELEVANT_RIGHTS .items ():
110+ mask = int (ace [" Ace" ][ " Mask" ][ " Mask" ])
111+ for right_value in RELEVANT_RIGHTS .values ():
118112 if mask & right_value :
119113 has_relevant_right = True
120114 break
121115
122116 if not has_relevant_right :
123117 continue # Skip this ACE if it doesn't have any relevant rights
124118
125- object_type = getattr (ace , ' ObjectType' , None )
119+ object_type = getattr (ace , " ObjectType" , None )
126120 if object_type :
127121 object_guid = ldaptypes .bin_to_string (object_type ).lower ()
128122 if object_guid not in RELEVANT_OBJECT_TYPES :
129123 continue
130124
131- sid = ace [' Ace' ][ ' Sid' ].formatCanonical ()
125+ sid = ace [" Ace" ][ " Sid" ].formatCanonical ()
132126 if self .is_excluded_sid (sid , domain_sid ):
133127 continue
134128
135129 results .setdefault (sid , []).append (dn )
136130
137- if hasattr (sd , ' OwnerSid' ):
138- owner_sid = str (sd [' OwnerSid' ])
131+ if hasattr (sd , " OwnerSid" ):
132+ owner_sid = str (sd [" OwnerSid" ])
139133 if not self .is_excluded_sid (owner_sid , domain_sid ):
140134 results .setdefault (owner_sid , []).append (dn )
141135
@@ -146,38 +140,37 @@ def resolve_sid_to_name(self, ldap_session, sid, base_dn):
146140 Resolves a SID to a samAccountName using LDAP
147141
148142 Args:
143+ ----
149144 ldap_session: The LDAP connection
150145 sid: The SID to resolve
151146 base_dn: The base DN for the LDAP search
152147
153148 Returns:
149+ -------
154150 str: The samAccountName if found, otherwise the original SID
155151 """
156152 try :
157153 search_filter = f"(objectSid={ sid } )"
158154 response = ldap_session .search (
159155 searchBase = base_dn ,
160156 searchFilter = search_filter ,
161- attributes = [' sAMAccountName' ]
157+ attributes = [" sAMAccountName" ]
162158 )
163159
164160 parsed = parse_result_attributes (response )
165- if parsed and ' sAMAccountName' in parsed [0 ]:
166- return parsed [0 ][' sAMAccountName' ]
161+ if parsed and " sAMAccountName" in parsed [0 ]:
162+ return parsed [0 ][" sAMAccountName" ]
167163 return sid
168- except Exception as e :
164+ except Exception :
169165 return sid
170166
171167 def on_login (self , context , connection ):
172- from ldap3 .protocol .controls import build_control
173- from pyasn1 .type .namedtype import NamedTypes , NamedType
174- from pyasn1 .type .univ import Integer , Sequence
175168
176169 controls = security_descriptor_control (sdflags = 0x07 ) # OWNER_SECURITY_INFORMATION
177170 resp = connection .ldap_connection .search (
178171 searchBase = connection .ldap_connection ._baseDN ,
179- searchFilter = ' (objectClass=organizationalUnit)' ,
180- attributes = [' distinguishedName' , ' nTSecurityDescriptor' ],
172+ searchFilter = " (objectClass=organizationalUnit)" ,
173+ attributes = [" distinguishedName" , " nTSecurityDescriptor" ],
181174 searchControls = controls ) # Fixed parameter name
182175
183176 context .log .debug (f"Found { len (resp )} entries" )
0 commit comments