Skip to content

Commit 2338ad7

Browse files
committed
Implement the desired filtering
1 parent fa33c84 commit 2338ad7

File tree

1 file changed

+39
-39
lines changed

1 file changed

+39
-39
lines changed

modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,12 @@ def initialize(info = {})
9595

9696
register_options([
9797
OptString.new('BASE_DN', [false, 'LDAP base DN if you already have it']),
98-
OptEnum.new('REPORT', [true, 'What templates to report (applies filtering to results)', 'all', [ 'all', 'vulnerable-and-enrollable' ]]),
98+
OptEnum.new('REPORT', [true, 'What templates to report (applies filtering to results)', 'all', %w[all vulnerable vulnerable-and-published vulnerable-and-enrollable]]),
9999
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]),
100100
])
101101
end
102102

103+
# TODO: Spencer to check all of these are still used and shouldn't be moved
103104
# Constants Definition
104105
CERTIFICATE_ATTRIBUTES = %w[cn name description nTSecurityDescriptor msPKI-Certificate-Policy msPKI-Enrollment-Flag msPKI-RA-Signature msPKI-Template-Schema-Version pkiExtendedKeyUsage]
105106
CERTIFICATE_TEMPLATES_BASE = 'CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration'.freeze
@@ -211,7 +212,7 @@ def query_ldap_server(raw_filter, attributes, base_prefix: nil)
211212
returned_entries
212213
end
213214

214-
def query_ldap_server_certificates(esc_raw_filter, esc_id, notes: [], check_enrollment: true)
215+
def query_ldap_server_certificates(esc_raw_filter, esc_id, notes: [])
215216
esc_entries = query_ldap_server(esc_raw_filter, CERTIFICATE_ATTRIBUTES, base_prefix: CERTIFICATE_TEMPLATES_BASE)
216217

217218
if esc_entries.empty?
@@ -224,8 +225,6 @@ def query_ldap_server_certificates(esc_raw_filter, esc_id, notes: [], check_enro
224225
esc_entries.each do |entry|
225226
certificate_symbol = entry[:cn][0].to_sym
226227
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]))
229228

230229
certificate_details[:techniques] << esc_id
231230
certificate_details[:notes] += notes
@@ -354,8 +353,6 @@ def find_esc4_vuln_cert_templates
354353
current_user = adds_get_current_user(@ldap)[:samaccountname].first
355354
esc_entries.each do |entry|
356355
certificate_symbol = entry[:cn][0].to_sym
357-
next unless can_enroll?(@certificate_details[certificate_symbol])
358-
359356
if adds_obj_grants_permissions?(@ldap, entry, SecurityDescriptorMatcher::Allow.any(%i[WP]))
360357
@certificate_details[certificate_symbol][:techniques] << 'ESC4'
361358
@certificate_details[certificate_symbol][:notes] << "ESC4: The account: #{current_user} has edit permissions over the template #{certificate_symbol}."
@@ -566,9 +563,6 @@ def find_esc13_vuln_cert_templates
566563
# Grab a list of certificates that contain vulnerable settings.
567564
# Also print out the list of SIDs that can enroll in that server.
568565
esc_entries.each do |entry|
569-
certificate_symbol = entry[:cn][0].to_sym
570-
next unless can_enroll?(@certificate_details[certificate_symbol])
571-
572566
groups = []
573567
entry['mspki-certificate-policy'].each do |certificate_policy_oid|
574568
policy = get_pki_object_by_oid(certificate_policy_oid)
@@ -622,12 +616,11 @@ def build_authority_details(ldap_object)
622616
end
623617
return unless security_descriptor.dacl
624618

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
619+
permissions = []
620+
# The permissions on the CA server are a bit different than those on a template. While the UI also lists "Read", "Issue and Manage Certificates",
621+
# and "Manage CA", only the "Request Certificates" permissions can be identified by this nTSecurityDescriptor. The certificateAuthority object
622+
# under CN=Certificate Authorities,CN=Public Key Services,CN=Services,CN=Configuration,DC=domain,DC=local also does not have the extra permissions.
623+
permissions << 'REQUEST CERTIFICATES' if adds_obj_grants_permissions?(@ldap, ldap_object, SecurityDescriptorMatcher::Allow.certificate_enrollment)
631624

632625
{
633626
fqdn: ca_server_fqdn,
@@ -639,7 +632,7 @@ def build_authority_details(ldap_object)
639632
}
640633
end
641634

642-
def build_certificate_details(ldap_object)
635+
def build_template_details(ldap_object)
643636
security_descriptor = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.read(ldap_object[:ntsecuritydescriptor].first)
644637

645638
if security_descriptor.dacl
@@ -750,20 +743,26 @@ def find_enrollable_vuln_certificate_templates
750743

751744
def print_vulnerable_cert_info
752745
filtered_certificate_details = @certificate_details.sort.to_h.select do |_key, details|
753-
select = true
754-
755-
select = false if datastore['REPORT'] != 'all' && details[:techniques].empty?
756-
select
746+
case datastore['REPORT']
747+
when 'all'
748+
true
749+
when 'vulnerable'
750+
details[:techniques].present?
751+
when 'vulnerable-and-published'
752+
details[:techniques].present? && details[:ca_servers].present?
753+
when 'vulnerable-and-enrollable'
754+
(details[:permissions].include?('FULL CONTROL') || details[:permissions].include?('ENROLL')) && details[:ca_servers].values.any? { _1[:permissions].include?('REQUEST CERTIFICATES') }
755+
end
757756
end
758757

759758
any_esc3t1 = filtered_certificate_details.values.any? do |hash|
760-
hash[:techniques].include?('ESC3') && (datastore['REPORT'] == 'all' || hash[:ca_servers].any?)
759+
hash[:techniques].include?('ESC3')
761760
end
762761

763762
filtered_certificate_details.each do |key, hash|
764763
techniques = hash[:techniques].dup
765764
techniques.delete('ESC3_TEMPLATE_2') unless any_esc3t1 # don't report ESC3_TEMPLATE_2 if there are no instances of ESC3
766-
next if techniques.empty?
765+
next unless techniques.present? || datastore['REPORT'] == 'all'
767766

768767
if db
769768
techniques.each do |vuln|
@@ -807,16 +806,23 @@ def print_vulnerable_cert_info
807806
print_status(" Manager Approval: #{hash[:manager_approval] ? '%redRequired' : '%grnDisabled'}%clr")
808807
print_status(" Required Signatures: #{hash[:required_signatures] == 0 ? '%grn0' : '%red' + hash[:required_signatures].to_s}%clr")
809808

810-
if @registry_values.present?
809+
potential_techniques = []
810+
if @registry_values.blank?
811+
potential_techniques << techniques.delete('ESC9') if techniques.include?('ESC9')
812+
potential_techniques << techniques.delete('ESC10') if techniques.include?('ESC10')
813+
end
814+
815+
if techniques.present?
811816
print_good(" Vulnerable to: #{techniques.join(', ')}")
812817
else
813-
print_good(" Vulnerable to: #{(techniques - %w[ESC9 ESC10]).join(', ')}")
814-
if techniques.include?('ESC9')
815-
print_warning(' Potentially vulnerable to: ESC9 (the template is in a vulnerable configuration but in order to exploit registry key StrongCertificateBindingEnforcement must not be set to 2)')
816-
end
817-
if techniques.include?('ESC10')
818-
print_warning(' Potentially vulnerable to: ESC10 (the template is in a vulnerable configuration but in order to exploit registry key StrongCertificateBindingEnforcement must be set to 0 or CertificateMappingMethods must be set to 4)')
819-
end
818+
print_status(' Vulnerable to: (none)')
819+
end
820+
821+
if potential_techniques.include?('ESC9')
822+
print_warning(' Potentially vulnerable to: ESC9 (the template is in a vulnerable configuration but in order to exploit registry key StrongCertificateBindingEnforcement must not be set to 2)')
823+
end
824+
if potential_techniques.include?('ESC10')
825+
print_warning(' Potentially vulnerable to: ESC10 (the template is in a vulnerable configuration but in order to exploit registry key StrongCertificateBindingEnforcement must be set to 0 or CertificateMappingMethods must be set to 4)')
820826
end
821827

822828
print_status(" Permissions: #{hash[:permissions].join(', ')}")
@@ -845,7 +851,8 @@ def print_vulnerable_cert_info
845851
if hash[:ca_servers].any?
846852
hash[:ca_servers].each do |ca_fqdn, ca_hash|
847853
print_good(" Issuing CA: #{ca_hash[:name]} (#{ca_fqdn})")
848-
print_status(" Permissions: #{ca_hash[:permissions].join(', ')}")
854+
# Don't print the permissions here because it can be misleading since not all can be detected
855+
# see: #build_authority_details
849856
print_status(' Enrollment SIDs:')
850857
ca_hash[:enroll_sids].each do |sid|
851858
print_status(" * #{highlight_sid(sid)}")
@@ -965,13 +972,6 @@ def get_ip_addresses_by_fqdn(host_fqdn)
965972
ip_addresses
966973
end
967974

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-
975975
def validate
976976
super
977977
if (datastore['RUN_REGISTRY_CHECKS']) && !%w[auto plaintext ntlm].include?(datastore['LDAP::Auth'].downcase)
@@ -1006,7 +1006,7 @@ def run
10061006

10071007
templates.each do |template|
10081008
certificate_symbol = template[:cn].first.to_sym
1009-
@certificate_details[certificate_symbol] = build_certificate_details(template)
1009+
@certificate_details[certificate_symbol] = build_template_details(template)
10101010
end
10111011

10121012
registry_values = enum_registry_values if datastore['RUN_REGISTRY_CHECKS']

0 commit comments

Comments
 (0)