@@ -95,9 +95,8 @@ def initialize(info = {})
95
95
96
96
register_options ( [
97
97
OptString . new ( 'BASE_DN' , [ false , 'LDAP base DN if you already have it' ] ) ,
98
- OptBool . new ( 'REPORT_NONENROLLABLE' , [ true , 'Report nonenrollable certificate templates' , false ] ) ,
99
- OptBool . new ( 'REPORT_PRIVENROLLABLE' , [ true , 'Report certificate templates restricted to domain and enterprise admins' , false ] ) ,
100
- OptBool . new ( 'RUN_REGISTRY_CHECKS' , [ true , 'Authenticate to WinRM to query the registry values to enhance reporting for ESC9 and ESC10. Must be a privleged user in order to query successfully' , false ] ) ,
98
+ OptEnum . new ( 'REPORT' , [ true , 'What templates to report (applies filtering to results)' , 'all' , [ 'all' , 'vulnerable-and-enrollable' ] ] ) ,
99
+ OptBool . new ( 'RUN_REGISTRY_CHECKS' , [ true , 'Authenticate to WinRM to query the registry values to enhance reporting for ESC9, ESC10 and ESC16. Must be a privileged user in order to query successfully' , false ] ) ,
101
100
] )
102
101
end
103
102
@@ -212,7 +211,7 @@ def query_ldap_server(raw_filter, attributes, base_prefix: nil)
212
211
returned_entries
213
212
end
214
213
215
- def query_ldap_server_certificates ( esc_raw_filter , esc_id , notes : [ ] )
214
+ def query_ldap_server_certificates ( esc_raw_filter , esc_id , notes : [ ] , check_enrollment : true )
216
215
esc_entries = query_ldap_server ( esc_raw_filter , CERTIFICATE_ATTRIBUTES , base_prefix : CERTIFICATE_TEMPLATES_BASE )
217
216
218
217
if esc_entries . empty?
@@ -224,10 +223,12 @@ def query_ldap_server_certificates(esc_raw_filter, esc_id, notes: [])
224
223
# Also print out the list of SIDs that can enroll in that server.
225
224
esc_entries . each do |entry |
226
225
certificate_symbol = entry [ :cn ] [ 0 ] . to_sym
227
- next if @certificate_details [ certificate_symbol ] [ :enroll_sids ] . empty?
226
+ certificate_details = @certificate_details [ certificate_symbol ]
227
+ next if certificate_details [ :enroll_sids ] . empty?
228
+ next unless ( !check_enrollment || can_enroll? ( @certificate_details [ certificate_symbol ] ) )
228
229
229
- @ certificate_details[ certificate_symbol ] [ :techniques ] << esc_id
230
- @ certificate_details[ certificate_symbol ] [ :notes ] += notes
230
+ certificate_details [ :techniques ] << esc_id
231
+ certificate_details [ :notes ] += notes
231
232
end
232
233
end
233
234
@@ -353,7 +354,7 @@ def find_esc4_vuln_cert_templates
353
354
current_user = adds_get_current_user ( @ldap ) [ :samaccountname ] . first
354
355
esc_entries . each do |entry |
355
356
certificate_symbol = entry [ :cn ] [ 0 ] . to_sym
356
- next if @certificate_details [ certificate_symbol ] [ :enroll_sids ] . empty?
357
+ next unless can_enroll? ( @certificate_details [ certificate_symbol ] )
357
358
358
359
if adds_obj_grants_permissions? ( @ldap , entry , SecurityDescriptorMatcher ::Allow . any ( %i[ WP ] ) )
359
360
@certificate_details [ certificate_symbol ] [ :techniques ] << 'ESC4'
@@ -489,9 +490,7 @@ def find_esc9_vuln_cert_templates
489
490
certificate_symbol = template [ :cn ] [ 0 ] . to_sym
490
491
491
492
enroll_sids = @certificate_details [ certificate_symbol ] [ :enroll_sids ]
492
-
493
493
users = find_users_with_write_and_enroll_rights ( enroll_sids )
494
-
495
494
next if users . empty?
496
495
497
496
user_plural = users . size > 1 ? 'accounts' : 'account'
@@ -527,9 +526,9 @@ def find_esc10_vuln_cert_templates
527
526
esc10_templates = query_ldap_server ( esc10_raw_filter , CERTIFICATE_ATTRIBUTES , base_prefix : CERTIFICATE_TEMPLATES_BASE )
528
527
esc10_templates . each do |template |
529
528
certificate_symbol = template [ :cn ] [ 0 ] . to_sym
529
+
530
530
enroll_sids = @certificate_details [ certificate_symbol ] [ :enroll_sids ]
531
531
users = find_users_with_write_and_enroll_rights ( enroll_sids )
532
-
533
532
next if users . empty?
534
533
535
534
user_plural = users . size > 1 ? 'accounts' : 'account'
@@ -568,7 +567,7 @@ def find_esc13_vuln_cert_templates
568
567
# Also print out the list of SIDs that can enroll in that server.
569
568
esc_entries . each do |entry |
570
569
certificate_symbol = entry [ :cn ] [ 0 ] . to_sym
571
- next if @certificate_details [ certificate_symbol ] [ :enroll_sids ] . empty?
570
+ next unless can_enroll? ( @certificate_details [ certificate_symbol ] )
572
571
573
572
groups = [ ]
574
573
entry [ 'mspki-certificate-policy' ] . each do |certificate_policy_oid |
@@ -595,7 +594,52 @@ def find_esc13_vuln_cert_templates
595
594
end
596
595
end
597
596
598
- def build_certificate_details ( ldap_object , techniques : [ ] , notes : [ ] )
597
+ def build_authority_details ( ldap_object )
598
+ ca_server_fqdn = ldap_object [ :dnshostname ] [ 0 ] . to_s . downcase
599
+ return unless ca_server_fqdn . present?
600
+
601
+ ca_server_ip_address = get_ip_addresses_by_fqdn ( ca_server_fqdn ) &.first
602
+
603
+ if ca_server_ip_address
604
+ report_service ( {
605
+ host : ca_server_ip_address ,
606
+ port : 445 ,
607
+ proto : 'tcp' ,
608
+ name : 'AD CS' ,
609
+ info : "AD CS CA name: #{ ldap_object [ :name ] [ 0 ] } "
610
+ } )
611
+
612
+ report_host ( {
613
+ host : ca_server_ip_address ,
614
+ name : ca_server_fqdn
615
+ } )
616
+ end
617
+
618
+ begin
619
+ security_descriptor = Rex ::Proto ::MsDtyp ::MsDtypSecurityDescriptor . read ( ldap_object [ :ntsecuritydescriptor ] [ 0 ] )
620
+ rescue IOError => e
621
+ fail_with ( Failure ::UnexpectedReply , "Unable to read security descriptor! Error was: #{ e . message } " )
622
+ end
623
+ return unless security_descriptor . dacl
624
+
625
+ if adds_obj_grants_permissions? ( @ldap , ldap_object , SecurityDescriptorMatcher ::Allow . full_control )
626
+ permissions = [ 'FULL CONTROL' ]
627
+ else
628
+ permissions = [ 'READ' ] # if we have the object, we can assume we have read permissions
629
+ permissions << 'REQUEST CERTIFICATES' if adds_obj_grants_permissions? ( @ldap , ldap_object , SecurityDescriptorMatcher ::Allow . certificate_enrollment )
630
+ end
631
+
632
+ {
633
+ fqdn : ca_server_fqdn ,
634
+ ip_address : ca_server_ip_address ,
635
+ enroll_sids : get_sids_for_enroll ( security_descriptor . dacl ) ,
636
+ permissions : permissions ,
637
+ name : ldap_object [ :name ] [ 0 ] . to_s ,
638
+ dn : ldap_object [ :dn ] [ 0 ] . to_s
639
+ }
640
+ end
641
+
642
+ def build_certificate_details ( ldap_object )
599
643
security_descriptor = Rex ::Proto ::MsDtyp ::MsDtypSecurityDescriptor . read ( ldap_object [ :ntsecuritydescriptor ] . first )
600
644
601
645
if security_descriptor . dacl
@@ -606,19 +650,29 @@ def build_certificate_details(ldap_object, techniques: [], notes: [])
606
650
write_sids = nil
607
651
end
608
652
653
+ if adds_obj_grants_permissions? ( @ldap , ldap_object , SecurityDescriptorMatcher ::Allow . full_control )
654
+ permissions = [ 'FULL CONTROL' ]
655
+ else
656
+ permissions = [ 'READ' ] # if we have the object, we can assume we have read permissions
657
+ permissions << 'WRITE' if adds_obj_grants_permissions? ( @ldap , ldap_object , SecurityDescriptorMatcher ::Allow . new ( :WP ) )
658
+ permissions << 'ENROLL' if adds_obj_grants_permissions? ( @ldap , ldap_object , SecurityDescriptorMatcher ::Allow . certificate_enrollment )
659
+ permissions << 'AUTOENROLL' if adds_obj_grants_permissions? ( @ldap , ldap_object , SecurityDescriptorMatcher ::Allow . certificate_autoenrollment )
660
+ end
661
+
609
662
{
610
663
name : ldap_object [ :cn ] [ 0 ] . to_s ,
611
- techniques : techniques ,
664
+ techniques : [ ] ,
612
665
dn : ldap_object [ :dn ] [ 0 ] . to_s ,
613
666
enroll_sids : enroll_sids ,
614
667
write_sids : write_sids ,
615
668
security_descriptor : security_descriptor ,
669
+ permissions : permissions ,
616
670
ekus : ldap_object [ :pkiextendedkeyusage ] . map ( &:to_s ) ,
617
671
schema_version : ldap_object [ %s(mspki-template-schema-version) ] . first ,
618
672
ca_servers : { } ,
619
673
manager_approval : ( [ ldap_object [ %s(mspki-enrollment-flag) ] . first . to_i ] . pack ( 'l' ) . unpack1 ( 'L' ) & Rex ::Proto ::MsCrtd ::CT_FLAG_PEND_ALL_REQUESTS ) != 0 ,
620
674
required_signatures : [ ldap_object [ %s(mspki-ra-signature) ] . first . to_i ] . pack ( 'l' ) . unpack1 ( 'L' ) ,
621
- notes : notes
675
+ notes : [ ]
622
676
}
623
677
end
624
678
@@ -654,16 +708,18 @@ def find_esc16_vuln_cert_templates
654
708
return if esc_entries . empty?
655
709
656
710
if @registry_values [ :strong_certificate_binding_enforcement ] && ( @registry_values [ :strong_certificate_binding_enforcement ] == 0 || @registry_values [ :strong_certificate_binding_enforcement ] == 1 )
657
- # Scenario 1 - StrongCertificateBindingEnforcement = 1 or 0 then it's same same as ESC9 - mark them all as vulnerable
711
+ # Scenario 1 - StrongCertificateBindingEnforcement = 1 or 0 then it's the same as ESC9 - mark them all as vulnerable
658
712
esc_entries . each do |entry |
659
713
certificate_symbol = entry [ :cn ] [ 0 ] . to_sym
714
+
660
715
@certificate_details [ certificate_symbol ] [ :techniques ] << 'ESC16'
661
716
@certificate_details [ certificate_symbol ] [ :notes ] << "ESC16: Template is vulnerable due StrongCertificateBindingEnforcement = #{ @registry_values [ :strong_certificate_binding_enforcement ] } and the CA's disabled policy extension list includes: 1.3.6.1.4.1.311.25.2."
662
717
end
663
718
elsif @registry_values [ :edit_flags ] & EDITF_ATTRIBUTESUBJECTALTNAME2 != 0
664
719
# Scenario 2 - StrongCertificateBindingEnforcement = 2 (or nil) but if EditFlags in the active policy module has EDITF_ATTRIBUTESUBJECTALTNAME2 set then ESC6 is essentially re-enabled and we mark them all as vulnerable
665
720
esc_entries . each do |entry |
666
721
certificate_symbol = entry [ :cn ] [ 0 ] . to_sym
722
+
667
723
@certificate_details [ certificate_symbol ] [ :techniques ] << 'ESC16'
668
724
@certificate_details [ certificate_symbol ] [ :notes ] << 'ESC16: Template is vulnerable due to the active policy EditFlags having: EDITF_ATTRIBUTESUBJECTALTNAME2 set (which is essentially ESC6) combined with the CA\'s disabled policy extension list including: 1.3.6.1.4.1.311.25.2.'
669
725
end
@@ -675,84 +731,36 @@ def find_enrollable_vuln_certificate_templates
675
731
# allows users to enroll in that certificate template and which users/groups
676
732
# have permissions to enroll in certificates on each server.
677
733
734
+ authority_details = { }
678
735
@certificate_details . each_key do |certificate_template |
679
736
certificate_enrollment_raw_filter = "(&(objectClass=pKIEnrollmentService)(certificateTemplates=#{ ldap_escape_filter ( certificate_template . to_s ) } ))"
680
737
attributes = [ 'cn' , 'name' , 'dnsHostname' , 'ntsecuritydescriptor' ]
681
738
base_prefix = 'CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration'
682
739
enrollment_ca_data = query_ldap_server ( certificate_enrollment_raw_filter , attributes , base_prefix : base_prefix )
683
740
next if enrollment_ca_data . empty?
684
741
685
- enrollment_ca_data . each do |ca_server |
686
- begin
687
- security_descriptor = Rex ::Proto ::MsDtyp ::MsDtypSecurityDescriptor . read ( ca_server [ :ntsecuritydescriptor ] [ 0 ] )
688
- rescue IOError => e
689
- fail_with ( Failure ::UnexpectedReply , "Unable to read security descriptor! Error was: #{ e . message } " )
690
- end
691
-
692
- enroll_sids = get_sids_for_enroll ( security_descriptor . dacl ) if security_descriptor . dacl
693
- next if enroll_sids . empty?
694
-
695
- ca_server_fqdn = ca_server [ :dnshostname ] [ 0 ] . to_s . downcase
696
- unless ca_server_fqdn . blank?
697
- ca_server_ip_address = get_ip_addresses_by_fqdn ( ca_server_fqdn ) &.first
698
-
699
- if ca_server_ip_address
700
- report_service ( {
701
- host : ca_server_ip_address ,
702
- port : 445 ,
703
- proto : 'tcp' ,
704
- name : 'AD CS' ,
705
- info : "AD CS CA name: #{ ca_server [ :name ] [ 0 ] } "
706
- } )
707
-
708
- report_host ( {
709
- host : ca_server_ip_address ,
710
- name : ca_server_fqdn
711
- } )
712
- end
713
- end
714
-
715
- ca_server_key = ca_server_fqdn . to_sym
742
+ enrollment_ca_data . each do |ldap_object |
743
+ ca_server_key = ldap_object [ :dnshostname ] . first . to_s . downcase . to_sym
716
744
next if @certificate_details [ certificate_template ] [ :ca_servers ] . key? ( ca_server_key )
717
745
718
- @certificate_details [ certificate_template ] [ :ca_servers ] [ ca_server_key ] = {
719
- fqdn : ca_server_fqdn ,
720
- ip_address : ca_server_ip_address ,
721
- enroll_sids : enroll_sids ,
722
- name : ca_server [ :name ] [ 0 ] . to_s ,
723
- dn : ca_server [ :dn ] [ 0 ] . to_s
724
- }
746
+ authority_details [ ca_server_key ] = @certificate_details [ certificate_template ] [ :ca_servers ] [ ca_server_key ] = authority_details . fetch ( ca_server_key ) { build_authority_details ( ldap_object ) }
725
747
end
726
748
end
727
749
end
728
750
729
751
def print_vulnerable_cert_info
730
- vuln_certificate_details = @certificate_details . sort . to_h . select do |_key , hash |
752
+ filtered_certificate_details = @certificate_details . sort . to_h . select do |_key , details |
731
753
select = true
732
- select = false unless datastore [ 'REPORT_PRIVENROLLABLE' ] || hash [ :enroll_sids ] . any? do |sid |
733
- # compare based on RIDs to avoid issues language specific issues
734
- !( sid . value . starts_with? ( "#{ WellKnownSids ::SECURITY_NT_NON_UNIQUE } -" ) && [
735
- # RID checks
736
- WellKnownSids ::DOMAIN_GROUP_RID_ADMINS ,
737
- WellKnownSids ::DOMAIN_GROUP_RID_ENTERPRISE_ADMINS ,
738
- WellKnownSids ::DOMAIN_GROUP_RID_ENTERPRISE_READONLY_DOMAIN_CONTROLLERS ,
739
- WellKnownSids ::DOMAIN_GROUP_RID_CONTROLLERS ,
740
- WellKnownSids ::DOMAIN_GROUP_RID_SCHEMA_ADMINS
741
- ] . include? ( sid . rid ) ) && ![
742
- # SID checks
743
- WellKnownSids ::SECURITY_ENTERPRISE_CONTROLLERS_SID
744
- ] . include? ( sid . value )
745
- end
746
754
747
- select = false unless datastore [ 'REPORT_NONENROLLABLE ' ] || hash [ :ca_servers ] . any ?
755
+ select = false if datastore [ 'REPORT ' ] != 'all' && details [ :techniques ] . empty ?
748
756
select
749
757
end
750
758
751
- any_esc3t1 = vuln_certificate_details . values . any? do |hash |
752
- hash [ :techniques ] . include? ( 'ESC3' ) && ( datastore [ 'REPORT_NONENROLLABLE' ] || hash [ :ca_servers ] . any? )
759
+ any_esc3t1 = filtered_certificate_details . values . any? do |hash |
760
+ hash [ :techniques ] . include? ( 'ESC3' ) && ( datastore [ 'REPORT' ] == 'all' || hash [ :ca_servers ] . any? )
753
761
end
754
762
755
- vuln_certificate_details . each do |key , hash |
763
+ filtered_certificate_details . each do |key , hash |
756
764
techniques = hash [ :techniques ] . dup
757
765
techniques . delete ( 'ESC3_TEMPLATE_2' ) unless any_esc3t1 # don't report ESC3_TEMPLATE_2 if there are no instances of ESC3
758
766
next if techniques . empty?
@@ -811,6 +819,8 @@ def print_vulnerable_cert_info
811
819
end
812
820
end
813
821
822
+ print_status ( " Permissions: #{ hash [ :permissions ] . join ( ', ' ) } " )
823
+
814
824
if hash [ :notes ] . present? && hash [ :notes ] . length == 1
815
825
print_status ( " Notes: #{ hash [ :notes ] . first } " )
816
826
elsif hash [ :notes ] . present? && hash [ :notes ] . length > 1
@@ -835,6 +845,7 @@ def print_vulnerable_cert_info
835
845
if hash [ :ca_servers ] . any?
836
846
hash [ :ca_servers ] . each do |ca_fqdn , ca_hash |
837
847
print_good ( " Issuing CA: #{ ca_hash [ :name ] } (#{ ca_fqdn } )" )
848
+ print_status ( " Permissions: #{ ca_hash [ :permissions ] . join ( ', ' ) } " )
838
849
print_status ( ' Enrollment SIDs:' )
839
850
ca_hash [ :enroll_sids ] . each do |sid |
840
851
print_status ( " * #{ highlight_sid ( sid ) } " )
@@ -954,6 +965,13 @@ def get_ip_addresses_by_fqdn(host_fqdn)
954
965
ip_addresses
955
966
end
956
967
968
+ def can_enroll? ( details )
969
+ return false unless ( details [ :permissions ] . include? ( 'FULL CONTROL' ) || details [ :permissions ] . include? ( 'ENROLL' ) )
970
+ return false unless details [ :ca_servers ] . values . any? { _1 [ :permissions ] . include? ( 'FULL CONTROL' ) || _1 [ :permissions ] . include? ( 'REQUEST CERTIFICATES' ) }
971
+
972
+ true
973
+ end
974
+
957
975
def validate
958
976
super
959
977
if ( datastore [ 'RUN_REGISTRY_CHECKS' ] ) && !%w[ auto plaintext ntlm ] . include? ( datastore [ 'LDAP::Auth' ] . downcase )
@@ -993,6 +1011,7 @@ def run
993
1011
994
1012
registry_values = enum_registry_values if datastore [ 'RUN_REGISTRY_CHECKS' ]
995
1013
1014
+ find_enrollable_vuln_certificate_templates
996
1015
find_esc1_vuln_cert_templates
997
1016
find_esc2_vuln_cert_templates
998
1017
find_esc3_vuln_cert_templates
@@ -1016,7 +1035,6 @@ def run
1016
1035
find_esc16_vuln_cert_templates
1017
1036
end
1018
1037
1019
- find_enrollable_vuln_certificate_templates
1020
1038
print_vulnerable_cert_info
1021
1039
1022
1040
@certificate_details
0 commit comments