@@ -31,6 +31,8 @@ def rid
31
31
end
32
32
end
33
33
34
+ attr_reader :certificate_details
35
+
34
36
def initialize ( info = { } )
35
37
super (
36
38
update_info (
@@ -206,7 +208,7 @@ def query_ldap_server(raw_filter, attributes, base_prefix: nil)
206
208
end
207
209
208
210
def query_ldap_server_certificates ( esc_raw_filter , esc_name , notes : [ ] )
209
- attributes = [ 'cn' , 'description' , 'ntSecurityDescriptor' , 'msPKI-Enrollment-Flag' , 'msPKI-RA-Signature' , 'PkiExtendedKeyUsage' ]
211
+ attributes = [ 'cn' , 'name' , ' description', 'ntSecurityDescriptor' , 'msPKI-Enrollment-Flag' , 'msPKI-RA-Signature' , 'PkiExtendedKeyUsage' ]
210
212
base_prefix = 'CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration'
211
213
esc_entries = query_ldap_server ( esc_raw_filter , attributes , base_prefix : base_prefix )
212
214
@@ -228,15 +230,16 @@ def query_ldap_server_certificates(esc_raw_filter, esc_name, notes: [])
228
230
next if allowed_sids . empty?
229
231
230
232
certificate_symbol = entry [ :cn ] [ 0 ] . to_sym
231
- if @vuln_certificate_details . key? ( certificate_symbol )
232
- @vuln_certificate_details [ certificate_symbol ] [ :vulns ] << esc_name
233
- @vuln_certificate_details [ certificate_symbol ] [ :notes ] += notes
233
+ if @certificate_details . key? ( certificate_symbol )
234
+ @certificate_details [ certificate_symbol ] [ :techniques ] << esc_name
235
+ @certificate_details [ certificate_symbol ] [ :notes ] += notes
234
236
else
235
- @vuln_certificate_details [ certificate_symbol ] = {
236
- vulns : [ esc_name ] ,
237
- dn : entry [ :dn ] [ 0 ] ,
238
- certificate_enrollment_sids : convert_sids_to_human_readable_name ( allowed_sids ) ,
239
- ca_servers_n_enrollment_sids : { } ,
237
+ @certificate_details [ certificate_symbol ] = {
238
+ name : entry [ :name ] [ 0 ] . to_s ,
239
+ techniques : [ esc_name ] ,
240
+ dn : entry [ :dn ] [ 0 ] . to_s ,
241
+ enrollment_sids : convert_sids_to_human_readable_name ( allowed_sids ) ,
242
+ ca_servers : { } ,
240
243
manager_approval : ( [ entry [ %s(mspki-enrollment-flag) ] . first . to_i ] . pack ( 'l' ) . unpack1 ( 'L' ) & Rex ::Proto ::MsCrtd ::CT_FLAG_PEND_ALL_REQUESTS ) != 0 ,
241
244
required_signatures : [ entry [ %s(mspki-ra-signature) ] . first . to_i ] . pack ( 'l' ) . unpack1 ( 'L' ) ,
242
245
notes : notes . dup
@@ -248,18 +251,14 @@ def query_ldap_server_certificates(esc_raw_filter, esc_name, notes: [])
248
251
def convert_sids_to_human_readable_name ( sids_array )
249
252
output = [ ]
250
253
for sid in sids_array
251
- raw_filter = "(objectSID=#{ ldap_escape_filter ( sid . to_s ) } )"
252
- attributes = [ 'sAMAccountName' , 'name' ]
253
- base_prefix = 'CN=Configuration'
254
- sid_entry = query_ldap_server ( raw_filter , attributes , base_prefix : base_prefix ) # First try with prefix to find entries that may be group specific.
255
- sid_entry = query_ldap_server ( raw_filter , attributes ) if sid_entry . empty? # Retry without prefix if blank.
256
- if sid_entry . empty?
254
+ sid_entry = get_object_by_sid ( sid )
255
+ if sid_entry . nil?
257
256
print_warning ( "Could not find any details on the LDAP server for SID #{ sid } !" )
258
257
output << [ sid , nil , nil ] # Still want to print out the SID even if we couldn't get additional information.
259
- elsif sid_entry [ 0 ] [ :samaccountname ] [ 0 ]
260
- output << [ sid , sid_entry [ 0 ] [ :name ] [ 0 ] , sid_entry [ 0 ] [ :samaccountname ] [ 0 ] ]
258
+ elsif sid_entry [ :samaccountname ] [ 0 ]
259
+ output << [ sid , sid_entry [ :name ] [ 0 ] , sid_entry [ :samaccountname ] [ 0 ] ]
261
260
else
262
- output << [ sid , sid_entry [ 0 ] [ :name ] [ 0 ] , nil ]
261
+ output << [ sid , sid_entry [ :name ] [ 0 ] , nil ]
263
262
end
264
263
end
265
264
@@ -323,14 +322,14 @@ def find_esc3_vuln_cert_templates
323
322
notes = [
324
323
'ESC3: Template defines the Certificate Request Agent OID (PkiExtendedKeyUsage)'
325
324
]
326
- query_ldap_server_certificates ( esc3_template_1_raw_filter , 'ESC3_TEMPLATE_1 ' , notes : notes )
325
+ query_ldap_server_certificates ( esc3_template_1_raw_filter , 'ESC3 ' , notes : notes )
327
326
328
327
# Find the second vulnerable types of ESC3 templates, those that
329
328
# have the right template schema version and, for those with a template
330
329
# version of 2 or greater, have an Application Policy Insurance Requirement
331
330
# requiring the Certificate Request Agent EKU.
332
331
#
333
- # Additionally the certificate template must also allow for domain authentication
332
+ # Additionally, the certificate template must also allow for domain authentication
334
333
# and the CA must not have any enrollment agent restrictions.
335
334
esc3_template_2_raw_filter = '(&' \
336
335
'(objectclass=pkicertificatetemplate)' \
@@ -521,11 +520,18 @@ def find_esc13_vuln_cert_templates
521
520
522
521
note = "ESC13 groups: #{ groups . join ( ', ' ) } "
523
522
certificate_symbol = entry [ :cn ] [ 0 ] . to_sym
524
- if @vuln_certificate_details . key? ( certificate_symbol )
525
- @vuln_certificate_details [ certificate_symbol ] [ :vulns ] << 'ESC13'
526
- @vuln_certificate_details [ certificate_symbol ] [ :notes ] << note
523
+ if @certificate_details . key? ( certificate_symbol )
524
+ @certificate_details [ certificate_symbol ] [ :techniques ] << 'ESC13'
525
+ @certificate_details [ certificate_symbol ] [ :notes ] << note
527
526
else
528
- @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 ] }
527
+ @certificate_details [ certificate_symbol ] = {
528
+ name : certificate_symbol . to_s ,
529
+ techniques : [ 'ESC13' ] ,
530
+ dn : entry [ :dn ] [ 0 ] . to_s ,
531
+ enrollment_sids : convert_sids_to_human_readable_name ( allowed_sids ) ,
532
+ ca_servers : { } ,
533
+ notes : [ note ]
534
+ }
529
535
end
530
536
end
531
537
end
@@ -550,9 +556,9 @@ def find_enrollable_vuln_certificate_templates
550
556
# allows users to enroll in that certificate template and which users/groups
551
557
# have permissions to enroll in certificates on each server.
552
558
553
- @vuln_certificate_details . each_key do |certificate_template |
559
+ @certificate_details . each_key do |certificate_template |
554
560
certificate_enrollment_raw_filter = "(&(objectClass=pKIEnrollmentService)(certificateTemplates=#{ ldap_escape_filter ( certificate_template . to_s ) } ))"
555
- attributes = [ 'cn' , 'dnsHostname' , 'ntsecuritydescriptor' ]
561
+ attributes = [ 'cn' , 'name' , ' dnsHostname', 'ntsecuritydescriptor' ]
556
562
base_prefix = 'CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration'
557
563
enrollment_ca_data = query_ldap_server ( certificate_enrollment_raw_filter , attributes , base_prefix : base_prefix )
558
564
next if enrollment_ca_data . empty?
@@ -567,18 +573,43 @@ def find_enrollable_vuln_certificate_templates
567
573
allowed_sids = parse_acl ( security_descriptor . dacl ) if security_descriptor . dacl
568
574
next if allowed_sids . empty?
569
575
576
+ service = report_service ( {
577
+ host : ca_server [ :dnshostname ] [ 0 ] ,
578
+ port : 445 ,
579
+ proto : 'tcp' ,
580
+ name : 'AD CS' ,
581
+ info : "AD CS CA name: #{ ca_server [ :name ] [ 0 ] } "
582
+ } )
583
+
584
+ report_note ( {
585
+ data : ca_server [ :dn ] [ 0 ] . to_s ,
586
+ service : service ,
587
+ host : ca_server [ :dnshostname ] [ 0 ] ,
588
+ ntype : 'windows.ad.cs.ca.dn'
589
+ } )
590
+
591
+ report_host ( {
592
+ host : ca_server [ :dnshostname ] [ 0 ] ,
593
+ name : ca_server [ :dnshostname ] [ 0 ]
594
+ } )
595
+
570
596
ca_server_key = ca_server [ :dnshostname ] [ 0 ] . to_sym
571
- unless @vuln_certificate_details [ certificate_template ] [ :ca_servers_n_enrollment_sids ] . key? ( ca_server_key )
572
- @vuln_certificate_details [ certificate_template ] [ :ca_servers_n_enrollment_sids ] [ ca_server_key ] = { cn : ca_server [ :cn ] [ 0 ] , ca_enrollment_sids : allowed_sids }
573
- end
597
+ next if @certificate_details [ certificate_template ] [ :ca_servers ] . key? ( ca_server_key )
598
+
599
+ @certificate_details [ certificate_template ] [ :ca_servers ] [ ca_server_key ] = {
600
+ hostname : ca_server [ :dnshostname ] [ 0 ] . to_s ,
601
+ enrollment_sids : allowed_sids ,
602
+ name : ca_server [ :name ] [ 0 ] . to_s ,
603
+ dn : ca_server [ :dn ] [ 0 ] . to_s
604
+ }
574
605
end
575
606
end
576
607
end
577
608
578
609
def print_vulnerable_cert_info
579
- vuln_certificate_details = @vuln_certificate_details . select do |_key , hash |
610
+ vuln_certificate_details = @certificate_details . select do |_key , hash |
580
611
select = true
581
- select = false unless datastore [ 'REPORT_PRIVENROLLABLE' ] || hash [ :certificate_enrollment_sids ] . any? do |sid |
612
+ select = false unless datastore [ 'REPORT_PRIVENROLLABLE' ] || hash [ :enrollment_sids ] . any? do |sid |
582
613
# compare based on RIDs to avoid issues language specific issues
583
614
!( sid . value . starts_with? ( "#{ WellKnownSids ::SECURITY_NT_NON_UNIQUE } -" ) && [
584
615
# RID checks
@@ -593,44 +624,62 @@ def print_vulnerable_cert_info
593
624
] . include? ( sid . value )
594
625
end
595
626
596
- select = false unless datastore [ 'REPORT_NONENROLLABLE' ] || hash [ :ca_servers_n_enrollment_sids ] . any?
627
+ select = false unless datastore [ 'REPORT_NONENROLLABLE' ] || hash [ :ca_servers ] . any?
597
628
select
598
629
end
599
630
600
631
any_esc3t1 = vuln_certificate_details . values . any? do |hash |
601
- hash [ :vulns ] . include? ( 'ESC3_TEMPLATE_1 ' ) && ( datastore [ 'REPORT_NONENROLLABLE' ] || hash [ :ca_servers_n_enrollment_sids ] . any? )
632
+ hash [ :techniques ] . include? ( 'ESC3 ' ) && ( datastore [ 'REPORT_NONENROLLABLE' ] || hash [ :ca_servers ] . any? )
602
633
end
603
634
604
635
vuln_certificate_details . each do |key , hash |
605
- vulns = hash [ :vulns ]
606
- vulns . delete ( 'ESC3_TEMPLATE_2' ) unless any_esc3t1 # don't report ESC3_TEMPLATE_2 if there are no instances of ESC3_TEMPLATE_1
607
- next if vulns . empty?
636
+ techniques = hash [ :techniques ] . dup
637
+ techniques . delete ( 'ESC3_TEMPLATE_2' ) unless any_esc3t1 # don't report ESC3_TEMPLATE_2 if there are no instances of ESC3
638
+ next if techniques . empty?
608
639
609
- vulns . each do |vuln |
610
- vuln = 'ESC3' if vuln == 'ESC3_TEMPLATE_1'
640
+ techniques . each do |vuln |
611
641
next if vuln == 'ESC3_TEMPLATE_2'
612
642
613
643
prefix = "#{ vuln } :"
614
644
info = hash [ :notes ] . select { |note | note . start_with? ( prefix ) } . map { |note | note . delete_prefix ( prefix ) . strip } . join ( "\n " )
615
645
info = nil if info . blank?
616
646
617
- report_vuln (
618
- host : rhost ,
619
- port : rport ,
620
- proto : 'tcp' ,
621
- sname : 'AD CS' ,
622
- name : "#{ vuln } - #{ key } " ,
623
- info : info ,
624
- refs : REFERENCES [ vuln ]
625
- )
647
+ hash [ :ca_servers ] . each do |dnshostname , ca_server |
648
+ service = report_service ( {
649
+ host : dnshostname . to_s ,
650
+ port : 445 ,
651
+ proto : 'tcp' ,
652
+ name : 'AD CS' ,
653
+ info : "AD CS CA name: #{ ca_server [ :name ] } "
654
+ } )
655
+
656
+ vuln = report_vuln (
657
+ host : dnshostname . to_s ,
658
+ port : 445 ,
659
+ proto : 'tcp' ,
660
+ sname : 'AD CS' ,
661
+ name : "#{ vuln } - #{ key } " ,
662
+ info : info ,
663
+ refs : REFERENCES [ vuln ] ,
664
+ service : service
665
+ )
666
+
667
+ report_note ( {
668
+ data : hash [ :dn ] ,
669
+ service : service ,
670
+ host : dnshostname . to_s ,
671
+ ntype : 'windows.ad.cs.ca.template.dn' ,
672
+ vuln_id : vuln . id
673
+ } )
674
+ end
626
675
end
627
676
628
677
print_good ( "Template: #{ key } " )
629
678
630
679
print_status ( " Distinguished Name: #{ hash [ :dn ] } " )
631
680
print_status ( " Manager Approval: #{ hash [ :manager_approval ] ? '%redRequired' : '%grnDisabled' } %clr" )
632
681
print_status ( " Required Signatures: #{ hash [ :required_signatures ] == 0 ? '%grn0' : '%red' + hash [ :required_signatures ] . to_s } %clr" )
633
- print_good ( " Vulnerable to: #{ vulns . join ( ', ' ) } " )
682
+ print_good ( " Vulnerable to: #{ techniques . join ( ', ' ) } " )
634
683
if hash [ :notes ] . present? && hash [ :notes ] . length == 1
635
684
print_status ( " Notes: #{ hash [ :notes ] . first } " )
636
685
elsif hash [ :notes ] . present? && hash [ :notes ] . length > 1
@@ -648,15 +697,15 @@ def print_vulnerable_cert_info
648
697
end
649
698
650
699
print_status ( ' Certificate Template Enrollment SIDs:' )
651
- hash [ :certificate_enrollment_sids ] . each do |sid |
700
+ hash [ :enrollment_sids ] . each do |sid |
652
701
print_status ( " * #{ highlight_sid ( sid ) } " )
653
702
end
654
703
655
- if hash [ :ca_servers_n_enrollment_sids ] . any?
656
- hash [ :ca_servers_n_enrollment_sids ] . each do |ca_hostname , ca_hash |
657
- print_good ( " Issuing CA: #{ ca_hash [ :cn ] } (#{ ca_hostname } )" )
704
+ if hash [ :ca_servers ] . any?
705
+ hash [ :ca_servers ] . each do |ca_hostname , ca_hash |
706
+ print_good ( " Issuing CA: #{ ca_hash [ :name ] } (#{ ca_hostname } )" )
658
707
print_status ( ' Enrollment SIDs:' )
659
- convert_sids_to_human_readable_name ( ca_hash [ :ca_enrollment_sids ] ) . each do |sid |
708
+ convert_sids_to_human_readable_name ( ca_hash [ :enrollment_sids ] ) . each do |sid |
660
709
print_status ( " * #{ highlight_sid ( sid ) } " )
661
710
end
662
711
end
@@ -678,21 +727,21 @@ def highlight_sid(sid)
678
727
end
679
728
680
729
def get_pki_object_by_oid ( oid )
681
- pki_object = @ldap_mspki_enterprise_oids . find { |o | o [ 'mspki-cert-template-oid' ] . first == oid }
730
+ pki_object = @ldap_objects . find { |o | o [ 'mspki-cert-template-oid' ] & .first == oid }
682
731
683
732
if pki_object . nil?
684
733
pki_object = query_ldap_server (
685
734
"(&(objectClass=msPKI-Enterprise-Oid)(msPKI-Cert-Template-OID=#{ ldap_escape_filter ( oid . to_s ) } ))" ,
686
735
nil ,
687
736
base_prefix : 'CN=OID,CN=Public Key Services,CN=Services,CN=Configuration'
688
737
) &.first
689
- @ldap_mspki_enterprise_oids << pki_object if pki_object
738
+ @ldap_objects << pki_object if pki_object
690
739
end
691
740
pki_object
692
741
end
693
742
694
743
def get_group_by_dn ( group_dn )
695
- group = @ldap_groups . find { |o | o [ 'dn' ] . first == group_dn }
744
+ group = @ldap_objects . find { |o | o [ 'dn' ] & .first == group_dn }
696
745
697
746
if group . nil?
698
747
cn , _ , base = group_dn . partition ( ',' )
@@ -702,18 +751,29 @@ def get_group_by_dn(group_dn)
702
751
nil ,
703
752
base_prefix : base
704
753
) &.first
705
- @ldap_groups << group if group
754
+ @ldap_objects << group if group
706
755
end
707
756
708
757
group
709
758
end
710
759
760
+ def get_object_by_sid ( object_sid )
761
+ object_sid = Rex ::Proto ::MsDtyp ::MsDtypSid . new ( object_sid )
762
+ object = @ldap_objects . find { |o | o [ 'objectSID' ] . first == object_sid . to_binary_s }
763
+
764
+ if object . nil?
765
+ object = query_ldap_server ( "(objectSID=#{ ldap_escape_filter ( object_sid . to_s ) } )" , nil ) &.first
766
+ @ldap_objects << object if object
767
+ end
768
+
769
+ object
770
+ end
771
+
711
772
def run
712
773
# Define our instance variables real quick.
713
774
@base_dn = nil
714
- @ldap_mspki_enterprise_oids = [ ]
715
- @ldap_groups = [ ]
716
- @vuln_certificate_details = { } # Initialize to empty hash since we want to only keep one copy of each certificate template along with its details.
775
+ @ldap_objects = [ ]
776
+ @certificate_details = { } # Initialize to empty hash since we want to only keep one copy of each certificate template along with its details.
717
777
718
778
ldap_connect do |ldap |
719
779
validate_bind_success! ( ldap )
@@ -738,6 +798,7 @@ def run
738
798
739
799
find_enrollable_vuln_certificate_templates
740
800
print_vulnerable_cert_info
801
+ @certificate_details
741
802
end
742
803
rescue Errno ::ECONNRESET
743
804
fail_with ( Failure ::Disconnected , 'The connection was reset.' )
0 commit comments