@@ -29,6 +29,8 @@ def rid
29
29
end
30
30
end
31
31
32
+ attr_reader :certificate_details
33
+
32
34
def initialize ( info = { } )
33
35
super (
34
36
update_info (
@@ -167,7 +169,7 @@ def query_ldap_server(raw_filter, attributes, base_prefix: nil)
167
169
end
168
170
169
171
def query_ldap_server_certificates ( esc_raw_filter , esc_name , notes : [ ] )
170
- attributes = [ 'cn' , 'description' , 'ntSecurityDescriptor' , 'msPKI-Enrollment-Flag' , 'msPKI-RA-Signature' , 'PkiExtendedKeyUsage' ]
172
+ attributes = [ 'cn' , 'name' , ' description', 'ntSecurityDescriptor' , 'msPKI-Enrollment-Flag' , 'msPKI-RA-Signature' , 'PkiExtendedKeyUsage' ]
171
173
base_prefix = 'CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration'
172
174
esc_entries = query_ldap_server ( esc_raw_filter , attributes , base_prefix : base_prefix )
173
175
@@ -190,15 +192,16 @@ def query_ldap_server_certificates(esc_raw_filter, esc_name, notes: [])
190
192
next if allowed_sids . empty?
191
193
192
194
certificate_symbol = entry [ :cn ] [ 0 ] . to_sym
193
- if @vuln_certificate_details . key? ( certificate_symbol )
194
- @vuln_certificate_details [ certificate_symbol ] [ :vulns ] << esc_name
195
- @vuln_certificate_details [ certificate_symbol ] [ :notes ] += notes
195
+ if @certificate_details . key? ( certificate_symbol )
196
+ @certificate_details [ certificate_symbol ] [ :techniques ] << esc_name
197
+ @certificate_details [ certificate_symbol ] [ :notes ] += notes
196
198
else
197
- @vuln_certificate_details [ certificate_symbol ] = {
198
- vulns : [ esc_name ] ,
199
- dn : entry [ :dn ] [ 0 ] ,
200
- certificate_enrollment_sids : convert_sids_to_human_readable_name ( allowed_sids ) ,
201
- ca_servers_n_enrollment_sids : { } ,
199
+ @certificate_details [ certificate_symbol ] = {
200
+ name : entry [ :name ] [ 0 ] . to_s ,
201
+ techniques : [ esc_name ] ,
202
+ dn : entry [ :dn ] [ 0 ] . to_s ,
203
+ enrollment_sids : convert_sids_to_human_readable_name ( allowed_sids ) ,
204
+ ca_servers : { } ,
202
205
manager_approval : ( [ entry [ %s(mspki-enrollment-flag) ] . first . to_i ] . pack ( 'l' ) . unpack1 ( 'L' ) & Rex ::Proto ::MsCrtd ::CT_FLAG_PEND_ALL_REQUESTS ) != 0 ,
203
206
required_signatures : [ entry [ %s(mspki-ra-signature) ] . first . to_i ] . pack ( 'l' ) . unpack1 ( 'L' ) ,
204
207
notes : notes
@@ -210,18 +213,14 @@ def query_ldap_server_certificates(esc_raw_filter, esc_name, notes: [])
210
213
def convert_sids_to_human_readable_name ( sids_array )
211
214
output = [ ]
212
215
for sid in sids_array
213
- raw_filter = "(objectSID=#{ ldap_escape_filter ( sid . to_s ) } )"
214
- attributes = [ 'sAMAccountName' , 'name' ]
215
- base_prefix = 'CN=Configuration'
216
- sid_entry = query_ldap_server ( raw_filter , attributes , base_prefix : base_prefix ) # First try with prefix to find entries that may be group specific.
217
- sid_entry = query_ldap_server ( raw_filter , attributes ) if sid_entry . empty? # Retry without prefix if blank.
218
- if sid_entry . empty?
216
+ sid_entry = get_object_by_sid ( sid )
217
+ if sid_entry . nil?
219
218
print_warning ( "Could not find any details on the LDAP server for SID #{ sid } !" )
220
219
output << [ sid , nil , nil ] # Still want to print out the SID even if we couldn't get additional information.
221
- elsif sid_entry [ 0 ] [ :samaccountname ] [ 0 ]
222
- output << [ sid , sid_entry [ 0 ] [ :name ] [ 0 ] , sid_entry [ 0 ] [ :samaccountname ] [ 0 ] ]
220
+ elsif sid_entry [ :samaccountname ] [ 0 ]
221
+ output << [ sid , sid_entry [ :name ] [ 0 ] , sid_entry [ :samaccountname ] [ 0 ] ]
223
222
else
224
- output << [ sid , sid_entry [ 0 ] [ :name ] [ 0 ] , nil ]
223
+ output << [ sid , sid_entry [ :name ] [ 0 ] , nil ]
225
224
end
226
225
end
227
226
@@ -285,14 +284,14 @@ def find_esc3_vuln_cert_templates
285
284
notes = [
286
285
'ESC3: Template defines the Certificate Request Agent OID (PkiExtendedKeyUsage)'
287
286
]
288
- query_ldap_server_certificates ( esc3_template_1_raw_filter , 'ESC3_TEMPLATE_1 ' , notes : notes )
287
+ query_ldap_server_certificates ( esc3_template_1_raw_filter , 'ESC3 ' , notes : notes )
289
288
290
289
# Find the second vulnerable types of ESC3 templates, those that
291
290
# have the right template schema version and, for those with a template
292
291
# version of 2 or greater, have an Application Policy Insurance Requirement
293
292
# requiring the Certificate Request Agent EKU.
294
293
#
295
- # Additionally the certificate template must also allow for domain authentication
294
+ # Additionally, the certificate template must also allow for domain authentication
296
295
# and the CA must not have any enrollment agent restrictions.
297
296
esc3_template_2_raw_filter = '(&' \
298
297
'(objectclass=pkicertificatetemplate)' \
@@ -365,11 +364,18 @@ def find_esc13_vuln_cert_templates
365
364
366
365
note = "ESC13 groups: #{ groups . join ( ', ' ) } "
367
366
certificate_symbol = entry [ :cn ] [ 0 ] . to_sym
368
- if @vuln_certificate_details . key? ( certificate_symbol )
369
- @vuln_certificate_details [ certificate_symbol ] [ :vulns ] << 'ESC13'
370
- @vuln_certificate_details [ certificate_symbol ] [ :notes ] << note
367
+ if @certificate_details . key? ( certificate_symbol )
368
+ @certificate_details [ certificate_symbol ] [ :techniques ] << 'ESC13'
369
+ @certificate_details [ certificate_symbol ] [ :notes ] << note
371
370
else
372
- @vuln_certificate_details [ certificate_symbol ] = { vulns : [ 'ESC13' ] , dn : entry [ :dn ] [ 0 ] , certificate_enrollment_sids : convert_sids_to_human_readable_name ( allowed_sids ) , ca_servers_n_enrollment_sids : { } , notes : [ note ] }
371
+ @certificate_details [ certificate_symbol ] = {
372
+ name : certificate_symbol . to_s ,
373
+ techniques : [ 'ESC13' ] ,
374
+ dn : entry [ :dn ] [ 0 ] . to_s ,
375
+ enrollment_sids : convert_sids_to_human_readable_name ( allowed_sids ) ,
376
+ ca_servers : { } ,
377
+ notes : [ note ]
378
+ }
373
379
end
374
380
end
375
381
end
@@ -394,9 +400,9 @@ def find_enrollable_vuln_certificate_templates
394
400
# allows users to enroll in that certificate template and which users/groups
395
401
# have permissions to enroll in certificates on each server.
396
402
397
- @vuln_certificate_details . each_key do |certificate_template |
403
+ @certificate_details . each_key do |certificate_template |
398
404
certificate_enrollment_raw_filter = "(&(objectClass=pKIEnrollmentService)(certificateTemplates=#{ ldap_escape_filter ( certificate_template . to_s ) } ))"
399
- attributes = [ 'cn' , 'dnsHostname' , 'ntsecuritydescriptor' ]
405
+ attributes = [ 'cn' , 'name' , ' dnsHostname', 'ntsecuritydescriptor' ]
400
406
base_prefix = 'CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration'
401
407
enrollment_ca_data = query_ldap_server ( certificate_enrollment_raw_filter , attributes , base_prefix : base_prefix )
402
408
next if enrollment_ca_data . empty?
@@ -411,18 +417,43 @@ def find_enrollable_vuln_certificate_templates
411
417
allowed_sids = parse_acl ( security_descriptor . dacl ) if security_descriptor . dacl
412
418
next if allowed_sids . empty?
413
419
420
+ service = report_service ( {
421
+ host : ca_server [ :dnshostname ] [ 0 ] ,
422
+ port : 445 ,
423
+ proto : 'tcp' ,
424
+ name : 'AD CS' ,
425
+ info : "AD CS CA name: #{ ca_server [ :name ] [ 0 ] } "
426
+ } )
427
+
428
+ report_note ( {
429
+ data : ca_server [ :dn ] [ 0 ] . to_s ,
430
+ service : service ,
431
+ host : ca_server [ :dnshostname ] [ 0 ] ,
432
+ ntype : 'windows.ad.cs.ca.dn'
433
+ } )
434
+
435
+ report_host ( {
436
+ host : ca_server [ :dnshostname ] [ 0 ] ,
437
+ name : ca_server [ :dnshostname ] [ 0 ]
438
+ } )
439
+
414
440
ca_server_key = ca_server [ :dnshostname ] [ 0 ] . to_sym
415
- unless @vuln_certificate_details [ certificate_template ] [ :ca_servers_n_enrollment_sids ] . key? ( ca_server_key )
416
- @vuln_certificate_details [ certificate_template ] [ :ca_servers_n_enrollment_sids ] [ ca_server_key ] = { cn : ca_server [ :cn ] [ 0 ] , ca_enrollment_sids : allowed_sids }
417
- end
441
+ next if @certificate_details [ certificate_template ] [ :ca_servers ] . key? ( ca_server_key )
442
+
443
+ @certificate_details [ certificate_template ] [ :ca_servers ] [ ca_server_key ] = {
444
+ hostname : ca_server [ :dnshostname ] [ 0 ] . to_s ,
445
+ enrollment_sids : allowed_sids ,
446
+ name : ca_server [ :name ] [ 0 ] . to_s ,
447
+ dn : ca_server [ :dn ] [ 0 ] . to_s
448
+ }
418
449
end
419
450
end
420
451
end
421
452
422
453
def print_vulnerable_cert_info
423
- vuln_certificate_details = @vuln_certificate_details . select do |_key , hash |
454
+ vuln_certificate_details = @certificate_details . select do |_key , hash |
424
455
select = true
425
- select = false unless datastore [ 'REPORT_PRIVENROLLABLE' ] || hash [ :certificate_enrollment_sids ] . any? do |sid |
456
+ select = false unless datastore [ 'REPORT_PRIVENROLLABLE' ] || hash [ :enrollment_sids ] . any? do |sid |
426
457
# compare based on RIDs to avoid issues language specific issues
427
458
!( sid . value . starts_with? ( "#{ WellKnownSids ::SECURITY_NT_NON_UNIQUE } -" ) && [
428
459
# RID checks
@@ -437,44 +468,62 @@ def print_vulnerable_cert_info
437
468
] . include? ( sid . value )
438
469
end
439
470
440
- select = false unless datastore [ 'REPORT_NONENROLLABLE' ] || hash [ :ca_servers_n_enrollment_sids ] . any?
471
+ select = false unless datastore [ 'REPORT_NONENROLLABLE' ] || hash [ :ca_servers ] . any?
441
472
select
442
473
end
443
474
444
475
any_esc3t1 = vuln_certificate_details . values . any? do |hash |
445
- hash [ :vulns ] . include? ( 'ESC3_TEMPLATE_1 ' ) && ( datastore [ 'REPORT_NONENROLLABLE' ] || hash [ :ca_servers_n_enrollment_sids ] . any? )
476
+ hash [ :techniques ] . include? ( 'ESC3 ' ) && ( datastore [ 'REPORT_NONENROLLABLE' ] || hash [ :ca_servers ] . any? )
446
477
end
447
478
448
479
vuln_certificate_details . each do |key , hash |
449
- vulns = hash [ :vulns ]
450
- vulns . delete ( 'ESC3_TEMPLATE_2' ) unless any_esc3t1 # don't report ESC3_TEMPLATE_2 if there are no instances of ESC3_TEMPLATE_1
451
- next if vulns . empty?
480
+ techniques = hash [ :techniques ] . dup
481
+ techniques . delete ( 'ESC3_TEMPLATE_2' ) unless any_esc3t1 # don't report ESC3_TEMPLATE_2 if there are no instances of ESC3
482
+ next if techniques . empty?
452
483
453
- vulns . each do |vuln |
454
- vuln = 'ESC3' if vuln == 'ESC3_TEMPLATE_1'
484
+ techniques . each do |vuln |
455
485
next if vuln == 'ESC3_TEMPLATE_2'
456
486
457
487
prefix = "#{ vuln } :"
458
488
info = hash [ :notes ] . select { |note | note . start_with? ( prefix ) } . map { |note | note . delete_prefix ( prefix ) . strip } . join ( "\n " )
459
489
info = nil if info . blank?
460
490
461
- report_vuln (
462
- host : rhost ,
463
- port : rport ,
464
- proto : 'tcp' ,
465
- sname : 'AD CS' ,
466
- name : "#{ vuln } - #{ key } " ,
467
- info : info ,
468
- refs : REFERENCES [ vuln ]
469
- )
491
+ hash [ :ca_servers ] . each do |dnshostname , ca_server |
492
+ service = report_service ( {
493
+ host : dnshostname . to_s ,
494
+ port : 445 ,
495
+ proto : 'tcp' ,
496
+ name : 'AD CS' ,
497
+ info : "AD CS CA name: #{ ca_server [ :name ] } "
498
+ } )
499
+
500
+ vuln = report_vuln (
501
+ host : dnshostname . to_s ,
502
+ port : 445 ,
503
+ proto : 'tcp' ,
504
+ sname : 'AD CS' ,
505
+ name : "#{ vuln } - #{ key } " ,
506
+ info : info ,
507
+ refs : REFERENCES [ vuln ] ,
508
+ service : service
509
+ )
510
+
511
+ report_note ( {
512
+ data : hash [ :dn ] ,
513
+ service : service ,
514
+ host : dnshostname . to_s ,
515
+ ntype : 'windows.ad.cs.ca.template.dn' ,
516
+ vuln_id : vuln . id
517
+ } )
518
+ end
470
519
end
471
520
472
521
print_good ( "Template: #{ key } " )
473
522
474
523
print_status ( " Distinguished Name: #{ hash [ :dn ] } " )
475
524
print_status ( " Manager Approval: #{ hash [ :manager_approval ] ? '%redRequired' : '%grnDisabled' } %clr" )
476
525
print_status ( " Required Signatures: #{ hash [ :required_signatures ] == 0 ? '%grn0' : '%red' + hash [ :required_signatures ] . to_s } %clr" )
477
- print_good ( " Vulnerable to: #{ vulns . join ( ', ' ) } " )
526
+ print_good ( " Vulnerable to: #{ techniques . join ( ', ' ) } " )
478
527
if hash [ :notes ] . present? && hash [ :notes ] . length == 1
479
528
print_status ( " Notes: #{ hash [ :notes ] . first } " )
480
529
elsif hash [ :notes ] . present? && hash [ :notes ] . length > 1
@@ -485,15 +534,15 @@ def print_vulnerable_cert_info
485
534
end
486
535
487
536
print_status ( ' Certificate Template Enrollment SIDs:' )
488
- hash [ :certificate_enrollment_sids ] . each do |sid |
537
+ hash [ :enrollment_sids ] . each do |sid |
489
538
print_status ( " * #{ highlight_sid ( sid ) } " )
490
539
end
491
540
492
- if hash [ :ca_servers_n_enrollment_sids ] . any?
493
- hash [ :ca_servers_n_enrollment_sids ] . each do |ca_hostname , ca_hash |
494
- print_good ( " Issuing CA: #{ ca_hash [ :cn ] } (#{ ca_hostname } )" )
541
+ if hash [ :ca_servers ] . any?
542
+ hash [ :ca_servers ] . each do |ca_hostname , ca_hash |
543
+ print_good ( " Issuing CA: #{ ca_hash [ :name ] } (#{ ca_hostname } )" )
495
544
print_status ( ' Enrollment SIDs:' )
496
- convert_sids_to_human_readable_name ( ca_hash [ :ca_enrollment_sids ] ) . each do |sid |
545
+ convert_sids_to_human_readable_name ( ca_hash [ :enrollment_sids ] ) . each do |sid |
497
546
print_status ( " * #{ highlight_sid ( sid ) } " )
498
547
end
499
548
end
@@ -515,22 +564,22 @@ def highlight_sid(sid)
515
564
end
516
565
517
566
def get_pki_object_by_oid ( oid )
518
- pki_object = @ldap_mspki_enterprise_oids . find { |o | o [ 'mspki-cert-template-oid' ] . first == oid }
567
+ pki_object = @ldap_objects . find { |o | o [ 'mspki-cert-template-oid' ] & .first == oid }
519
568
520
569
if pki_object . nil?
521
570
pki_object = query_ldap_server (
522
571
"(&(objectClass=msPKI-Enterprise-Oid)(msPKI-Cert-Template-OID=#{ ldap_escape_filter ( oid . to_s ) } ))" ,
523
572
nil ,
524
573
base_prefix : 'CN=OID,CN=Public Key Services,CN=Services,CN=Configuration'
525
574
) &.first
526
- @ldap_mspki_enterprise_oids << pki_object if pki_object
575
+ @ldap_objects << pki_object if pki_object
527
576
end
528
577
529
578
pki_object
530
579
end
531
580
532
581
def get_group_by_dn ( group_dn )
533
- group = @ldap_groups . find { |o | o [ 'dn' ] . first == group_dn }
582
+ group = @ldap_objects . find { |o | o [ 'dn' ] & .first == group_dn }
534
583
535
584
if group . nil?
536
585
cn , _ , base = group_dn . partition ( ',' )
@@ -540,18 +589,29 @@ def get_group_by_dn(group_dn)
540
589
nil ,
541
590
base_prefix : base
542
591
) &.first
543
- @ldap_groups << group if group
592
+ @ldap_objects << group if group
544
593
end
545
594
546
595
group
547
596
end
548
597
598
+ def get_object_by_sid ( object_sid )
599
+ object_sid = Rex ::Proto ::MsDtyp ::MsDtypSid . new ( object_sid )
600
+ object = @ldap_objects . find { |o | o [ 'objectSID' ] . first == object_sid . to_binary_s }
601
+
602
+ if object . nil?
603
+ object = query_ldap_server ( "(objectSID=#{ ldap_escape_filter ( object_sid . to_s ) } )" , nil ) &.first
604
+ @ldap_objects << object if object
605
+ end
606
+
607
+ object
608
+ end
609
+
549
610
def run
550
611
# Define our instance variables real quick.
551
612
@base_dn = nil
552
- @ldap_mspki_enterprise_oids = [ ]
553
- @ldap_groups = [ ]
554
- @vuln_certificate_details = { } # Initialize to empty hash since we want to only keep one copy of each certificate template along with its details.
613
+ @ldap_objects = [ ]
614
+ @certificate_details = { } # Initialize to empty hash since we want to only keep one copy of each certificate template along with its details.
555
615
556
616
ldap_connect do |ldap |
557
617
validate_bind_success! ( ldap )
@@ -575,6 +635,7 @@ def run
575
635
576
636
find_enrollable_vuln_certificate_templates
577
637
print_vulnerable_cert_info
638
+ @certificate_details
578
639
end
579
640
rescue Errno ::ECONNRESET
580
641
fail_with ( Failure ::Disconnected , 'The connection was reset.' )
0 commit comments