Skip to content

Commit 3fe66ba

Browse files
committed
Resolve the CA address via DNS records in LDAP
1 parent 5e5ab79 commit 3fe66ba

File tree

1 file changed

+73
-28
lines changed

1 file changed

+73
-28
lines changed

modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb

Lines changed: 73 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ class MetasploitModule < Msf::Auxiliary
33
include Msf::Auxiliary::Report
44
include Msf::Exploit::Remote::LDAP
55
include Msf::OptionalSession::LDAP
6+
include Rex::Proto::MsDnsp
67
include Rex::Proto::Secauthz
78

89
ADS_GROUP_TYPE_BUILTIN_LOCAL_GROUP = 0x00000001
@@ -417,31 +418,37 @@ def find_enrollable_vuln_certificate_templates
417418
allowed_sids = parse_acl(security_descriptor.dacl) if security_descriptor.dacl
418419
next if allowed_sids.empty?
419420

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-
440-
ca_server_key = ca_server[:dnshostname][0].to_sym
421+
ca_server_fqdn = ca_server[:dnshostname][0].to_s.downcase
422+
ca_server_ip_address = get_ip_addresses_by_fqdn(ca_server_fqdn)&.first
423+
424+
if ca_server_ip_address
425+
service = report_service({
426+
host: ca_server_ip_address,
427+
port: 445,
428+
proto: 'tcp',
429+
name: 'AD CS',
430+
info: "AD CS CA name: #{ca_server[:name][0]}"
431+
})
432+
433+
report_note({
434+
data: ca_server[:dn][0].to_s,
435+
service: service,
436+
host: ca_server_ip_address,
437+
ntype: 'windows.ad.cs.ca.dn'
438+
})
439+
440+
report_host({
441+
host: ca_server_ip_address,
442+
name: ca_server_fqdn
443+
})
444+
end
445+
446+
ca_server_key = ca_server_fqdn.to_sym
441447
next if @certificate_details[certificate_template][:ca_servers].key?(ca_server_key)
442448

443449
@certificate_details[certificate_template][:ca_servers][ca_server_key] = {
444-
hostname: ca_server[:dnshostname][0].to_s,
450+
fqdn: ca_server_fqdn,
451+
ip_address: ca_server_ip_address,
445452
enrollment_sids: allowed_sids,
446453
name: ca_server[:name][0].to_s,
447454
dn: ca_server[:dn][0].to_s
@@ -488,17 +495,17 @@ def print_vulnerable_cert_info
488495
info = hash[:notes].select { |note| note.start_with?(prefix) }.map { |note| note.delete_prefix(prefix).strip }.join("\n")
489496
info = nil if info.blank?
490497

491-
hash[:ca_servers].each do |dnshostname, ca_server|
498+
hash[:ca_servers].each do |ca_fqdn, ca_server|
492499
service = report_service({
493-
host: dnshostname.to_s,
500+
host: ca_server[:ip_address],
494501
port: 445,
495502
proto: 'tcp',
496503
name: 'AD CS',
497504
info: "AD CS CA name: #{ca_server[:name]}"
498505
})
499506

500507
vuln = report_vuln(
501-
host: dnshostname.to_s,
508+
host: ca_server[:ip_address],
502509
port: 445,
503510
proto: 'tcp',
504511
sname: 'AD CS',
@@ -511,7 +518,7 @@ def print_vulnerable_cert_info
511518
report_note({
512519
data: hash[:dn],
513520
service: service,
514-
host: dnshostname.to_s,
521+
host: ca_fqdn.to_s,
515522
ntype: 'windows.ad.cs.ca.template.dn',
516523
vuln_id: vuln.id
517524
})
@@ -539,8 +546,8 @@ def print_vulnerable_cert_info
539546
end
540547

541548
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})")
549+
hash[:ca_servers].each do |ca_fqdn, ca_hash|
550+
print_good(" Issuing CA: #{ca_hash[:name]} (#{ca_fqdn})")
544551
print_status(' Enrollment SIDs:')
545552
convert_sids_to_human_readable_name(ca_hash[:enrollment_sids]).each do |sid|
546553
print_status(" * #{highlight_sid(sid)}")
@@ -607,10 +614,48 @@ def get_object_by_sid(object_sid)
607614
object
608615
end
609616

617+
def get_ip_addresses_by_fqdn(host_fqdn)
618+
return @fqdns[host_fqdn] if @fqdns.key?(host_fqdn)
619+
620+
vprint_status("Looking up DNS records for #{host_fqdn} in LDAP.")
621+
hostname, _, domain = host_fqdn.partition('.')
622+
results = query_ldap_server(
623+
"(&(objectClass=dnsNode)(DC=#{ldap_escape_filter(hostname)}))",
624+
%w[dnsRecord],
625+
base_prefix: "DC=#{ldap_escape_filter(domain)},CN=MicrosoftDNS,DC=DomainDnsZones"
626+
)
627+
return nil if results.blank?
628+
629+
ip_addresses = []
630+
results.first[:dnsrecord].each do |packed|
631+
begin
632+
unpacked = MsDnspDnsRecord.read(packed)
633+
rescue ::EOFError
634+
next
635+
rescue ::IOError
636+
next
637+
end
638+
639+
next unless [ DnsRecordType::DNS_TYPE_A, DnsRecordType::DNS_TYPE_AAAA ].include?(unpacked.record_type)
640+
641+
ip_addresses << unpacked.data.to_s
642+
end
643+
644+
@fqdns[host_fqdn] = ip_addresses
645+
if ip_addresses.empty?
646+
print_warning("No A or AAAA DNS records were found for #{host_fqdn} in LDAP.")
647+
else
648+
vprint_status("Found #{ip_addresses.length} IP address#{ip_addresses.length > 1 ? 'es' : ''} via A and AAAA DNS records.")
649+
end
650+
651+
ip_addresses
652+
end
653+
610654
def run
611655
# Define our instance variables real quick.
612656
@base_dn = nil
613657
@ldap_objects = []
658+
@fqdns = {}
614659
@certificate_details = {} # Initialize to empty hash since we want to only keep one copy of each certificate template along with its details.
615660

616661
ldap_connect do |ldap|

0 commit comments

Comments
 (0)