Skip to content

Commit c093768

Browse files
committed
fix ruff
1 parent d1d7002 commit c093768

1 file changed

Lines changed: 64 additions & 71 deletions

File tree

nxc/modules/badsuccessor.py

Lines changed: 64 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,55 @@
33
from ldap3.protocol.microsoft import security_descriptor_control
44

55
RELEVANT_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
1414
ACCESS_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
4747
RELEVANT_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

5757
class 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

Comments
 (0)