Skip to content

Commit e2a692d

Browse files
author
SkelSec
committed
new certify command, adcs parsing fixes
1 parent cf452d4 commit e2a692d

File tree

4 files changed

+187
-23
lines changed

4 files changed

+187
-23
lines changed

msldap/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
__version__ = "0.5.12"
2+
__version__ = "0.5.13"
33
__banner__ = \
44
"""
55
# msldap %s

msldap/client.py

+3
Original file line numberDiff line numberDiff line change
@@ -1579,6 +1579,7 @@ async def whoamifull(self):
15791579
if err is not None:
15801580
raise err
15811581
result['raw'] = res
1582+
result['tokengroups'] = []
15821583
if res.startswith('u:') is True:
15831584
domain, samaccountname = res[2:].split('\\', 1)
15841585
result['domain'] = domain
@@ -1587,10 +1588,12 @@ async def whoamifull(self):
15871588
if err is not None:
15881589
raise err
15891590
result['sid'] = str(user.objectSid)
1591+
result['tokengroups'].append(str(user.objectSid))
15901592
result['groups'] = {}
15911593
async for group_sid, err in self.get_tokengroups(user.distinguishedName):
15921594
if err is not None:
15931595
raise err
1596+
result['tokengroups'].append(str(group_sid))
15941597
result['groups'][group_sid] = ('NA','NA')
15951598
domain, username, err = await self.resolv_sid(group_sid)
15961599
if err is not None:

msldap/examples/msldapclient.py

+63
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,69 @@ async def do_sidresolv(self, sid, to_print = True):
12991299
traceback.print_exc()
13001300
return False
13011301

1302+
async def do_certify2(self, username=None):
1303+
"""ADCA security test - new version"""
1304+
try:
1305+
es = await self.do_enrollmentservices(to_print=False)
1306+
if es is False:
1307+
raise Exception('Listing enrollment Services error! %s' % es)
1308+
if es is None:
1309+
raise Exception('No Enrollment Services present, stopping!')
1310+
1311+
templates = await self.do_certtemplates(to_print=False)
1312+
if templates is False:
1313+
raise Exception('Listing templates error! %s' % es)
1314+
1315+
if templates is None:
1316+
raise Exception('No templates exists!')
1317+
1318+
results = []
1319+
tokengroups = None
1320+
if username is not None:
1321+
tokengroups, err = await self.connection.get_tokengroups_user(username)
1322+
if err is not None:
1323+
raise err
1324+
else:
1325+
res, err = await self.connection.whoamifull()
1326+
if err is not None:
1327+
raise err
1328+
tokengroups = res['tokengroups']
1329+
1330+
for template in templates:
1331+
res = template.is_vulnerable2(tokengroups)
1332+
if len(res) == 0:
1333+
continue
1334+
1335+
es = template.enrollment_services
1336+
is_enabled = True
1337+
if len(es) == 0:
1338+
es = [(None, None)]
1339+
is_enabled = False
1340+
1341+
for hostname, service in es:
1342+
t = 'Template Name: %s\n' % template.name
1343+
t += 'Enrollment Service Name: %s\n' % service
1344+
t += 'Enrollment Service Host: %s\n' % hostname
1345+
t += 'Enabled: %s\n' % is_enabled
1346+
t += 'Vulnerabilities:\n'
1347+
for r in res:
1348+
t += '\t%s - %s\n' % (r, res[r].get('Reason', ''))
1349+
print(t)
1350+
1351+
entry = {
1352+
'enabled': is_enabled,
1353+
'template' : template.name,
1354+
'service' : service,
1355+
'hostname' : hostname,
1356+
'vulns' : res
1357+
}
1358+
results.append(entry)
1359+
1360+
return results
1361+
except:
1362+
traceback.print_exc()
1363+
return False
1364+
13021365
async def do_certify(self, cmd = None, username = None):
13031366
"""ADCA security test"""
13041367
try:

msldap/ldap_objects/adcertificatetemplate.py

+120-22
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class EnrollmentFlag(enum.IntFlag):
4040
ALLOW_PREVIOUS_APPROVAL_KEYBASEDRENEWAL_VALIDATE_REENROLLMENT = 0x00010000
4141
ISSUANCE_POLICIES_FROM_REQUEST = 0x00020000
4242
SKIP_AUTO_RENEWAL = 0x00040000
43+
NO_SECURITY_EXTENSION = 0x00080000
4344

4445
class PrivateKeyFlag(enum.IntFlag):
4546
REQUIRE_PRIVATE_KEY_ARCHIVAL = 0x00000001
@@ -196,6 +197,14 @@ def from_ldap(entry):
196197

197198
adi.calc_aces()
198199
return adi
200+
201+
def isLowPrivSid(self, sid):
202+
sid = str(sid)
203+
if sid in ['S-1-1-0', 'S-1-5-11']:
204+
return True
205+
if sid.startswith('S-1-5-21-') is True and sid.rsplit('-',1)[1] in ['513','515','545']:
206+
return True
207+
return False
199208

200209
def allows_authentication(self):
201210
return self.can_be_used_for_any_purpose() or len(set([EKU_CLIENT_AUTHENTICATION_OID, EKU_SMART_CARD_LOGON_OID, EKU_PKINIT_CLIENT_AUTHENTICATION_OID]).intersection(set(self.pKIExtendedKeyUsage))) > 0
@@ -216,24 +225,24 @@ def allows_to_request_agent_certificate(self):
216225
return EKU_CERTIFICATE_REQUEST_AGENT_OID in self.pKIExtendedKeyUsage
217226

218227
def allows_to_use_agent_certificate(self):
219-
return self.Template_Schema_Version == 1 \
220-
or (
221-
self.Template_Schema_Version > 1 \
222-
and self.RA_Signature == 1 \
223-
and EKU_CERTIFICATE_REQUEST_AGENT_OID in self.RA_Application_Policies
224-
)
225-
226-
def is_vulnerable(self, tokengroups = None):
227-
def isLowPrivSid(sid):
228-
sid = str(sid)
229-
if sid in ['S-1-1-0', 'S-1-5-11']:
230-
return True
231-
if sid.startswith('S-1-5-21-') is True and sid.rsplit('-',1)[1] in ['513','515','545']:
232-
return True
233-
return False
228+
if EKU_ANY_PURPOSE_OID in self.pKIExtendedKeyUsage:
229+
return True
230+
if EKU_CERTIFICATE_REQUEST_AGENT_OID in self.pKIExtendedKeyUsage:
231+
return True
234232

233+
#return self.Template_Schema_Version == 1 \
234+
# or (
235+
# self.Template_Schema_Version > 1 \
236+
# and self.RA_Signature == 1 \
237+
# and EKU_CERTIFICATE_REQUEST_AGENT_OID in self.RA_Application_Policies
238+
# )
239+
240+
def no_securty_extension(self):
241+
return EnrollmentFlag.NO_SECURITY_EXTENSION in EnrollmentFlag(self.Enrollment_Flag)
242+
243+
def is_vulnerable(self, tokengroups = None):
235244
if tokengroups is None:
236-
if isLowPrivSid(str(self.nTSecurityDescriptor.Owner)) is True:
245+
if self.isLowPrivSid(str(self.nTSecurityDescriptor.Owner)) is True:
237246
return True, 'Owner is low priv user'
238247

239248
else:
@@ -242,22 +251,22 @@ def isLowPrivSid(sid):
242251

243252
lowprivcanenroll = False
244253
if tokengroups is None:
245-
if any(isLowPrivSid(str(sid)) for sid in self.fullcontrol_sids) is True:
254+
if any(self.isLowPrivSid(str(sid)) for sid in self.fullcontrol_sids) is True:
246255
return True, 'Lowpriv SID has full control'
247256

248-
if any(isLowPrivSid(str(sid)) for sid in self.write_dacl_sids) is True:
257+
if any(self.isLowPrivSid(str(sid)) for sid in self.write_dacl_sids) is True:
249258
return True, 'Lowpriv SID can write DACLs'
250259

251-
if any(isLowPrivSid(str(sid)) for sid in self.write_owner_sids) is True:
260+
if any(self.isLowPrivSid(str(sid)) for sid in self.write_owner_sids) is True:
252261
return True, 'Lowpriv SID can change Owner'
253262

254-
if any(isLowPrivSid(str(sid)) for sid in self.write_property_sids) is True:
263+
if any(self.isLowPrivSid(str(sid)) for sid in self.write_property_sids) is True:
255264
return True, 'Lowpriv SID can write property'
256265

257-
if any(isLowPrivSid(str(sid)) for sid in self.enroll_sids) is True:
266+
if any(self.isLowPrivSid(str(sid)) for sid in self.enroll_sids) is True:
258267
lowprivcanenroll = True
259268

260-
if any(isLowPrivSid(str(sid)) for sid in self.allextendedrights_sids) is True:
269+
if any(self.isLowPrivSid(str(sid)) for sid in self.allextendedrights_sids) is True:
261270
lowprivcanenroll = True
262271

263272
else:
@@ -290,6 +299,83 @@ def isLowPrivSid(sid):
290299

291300
return False, 'No match found'
292301

302+
def check_dangerous_permissions(self, tokengroups = None):
303+
issues = []
304+
if tokengroups is None or len(tokengroups) == 0:
305+
if self.isLowPrivSid(str(self.nTSecurityDescriptor.Owner)) is True:
306+
issues.append('Owner is low priv user')
307+
308+
if any(self.isLowPrivSid(str(sid)) for sid in self.fullcontrol_sids) is True:
309+
issues.append('Lowpriv SID has full control')
310+
311+
if any(self.isLowPrivSid(str(sid)) for sid in self.write_dacl_sids) is True:
312+
issues.append('Lowpriv SID can write DACLs')
313+
314+
if any(self.isLowPrivSid(str(sid)) for sid in self.write_owner_sids) is True:
315+
issues.append('Lowpriv SID can change Owner')
316+
317+
if any(self.isLowPrivSid(str(sid)) for sid in self.write_property_sids) is True:
318+
issues.append('Lowpriv SID can write property')
319+
320+
else:
321+
if len(self.write_dacl_sids.intersection(set(tokengroups))) > 0:
322+
issues.append('Current user can write DACLs')
323+
324+
if len(self.write_owner_sids.intersection(set(tokengroups))) > 0:
325+
issues.append('Current user can change Owner')
326+
327+
if len(self.write_property_sids.intersection(set(tokengroups))) > 0:
328+
issues.append('Current user can write property')
329+
330+
if len(self.fullcontrol_sids.intersection(set(tokengroups))) > 0:
331+
issues.append('Current user has full control')
332+
333+
if str(self.nTSecurityDescriptor.Owner) in tokengroups:
334+
issues.append('The current user can control the owner -or is the owner-')
335+
336+
return issues
337+
338+
def is_vulnerable2(self, tokengroups = None):
339+
vulns = {}
340+
if tokengroups is None:
341+
tokengroups = []
342+
343+
user_can_enroll = False
344+
if len(set(self.enroll_sids).intersection(set(tokengroups))) > 0:
345+
user_can_enroll = True
346+
347+
if user_can_enroll and self.allows_authentication() and self.allows_to_specify_san():
348+
vulns['ESC1'] = {
349+
'SIDs': self.enroll_sids,
350+
'Reason': 'Users can enroll, enrollee supplies subject and template allows client authentication'
351+
}
352+
353+
if user_can_enroll and self.can_be_used_for_any_purpose() is True:
354+
vulns['ESC2'] = {
355+
'SIDs': self.enroll_sids,
356+
'Reason': 'Users can enroll and template allows any purpose'
357+
}
358+
359+
if user_can_enroll and self.allows_to_use_agent_certificate():
360+
vulns['ESC3'] = {
361+
'SIDs': self.enroll_sids,
362+
'Reason': 'Users can enroll and template allows certificate request agent'
363+
}
364+
365+
if user_can_enroll and self.no_securty_extension():
366+
vulns['ESC9'] = {
367+
'SIDs': self.enroll_sids,
368+
'Reason': 'Users can enroll and template does not require security extension'
369+
}
370+
371+
perm_issues = self.check_dangerous_permissions(tokengroups)
372+
if len(perm_issues) > 0:
373+
vulns['ESC4'] = {
374+
'SIDs': tokengroups,
375+
'Reason': ', '.join(perm_issues)
376+
}
377+
return vulns
378+
293379
def calc_aces(self):
294380
if self.nTSecurityDescriptor is None:
295381
return
@@ -324,6 +410,18 @@ def calc_aces(self):
324410
@property
325411
def is_enabled(self):
326412
return len(self.enroll_services) > 0
413+
414+
@property
415+
def enrollment_services(self):
416+
res = []
417+
for es in self.enroll_services:
418+
if es.find('\\') != -1:
419+
hostname, service = es.split('\\', 1)
420+
res.append((hostname, service))
421+
else:
422+
res.append((None, service))
423+
return res
424+
327425

328426
def __str__(self):
329427
t = '== MSADCertificateTemplate ==\r\n'

0 commit comments

Comments
 (0)