Skip to content

Commit

Permalink
Resolve the CA address via DNS records in LDAP
Browse files Browse the repository at this point in the history
  • Loading branch information
zeroSteiner committed Jan 16, 2025
1 parent 5e5ab79 commit 3fe66ba
Showing 1 changed file with 73 additions and 28 deletions.
101 changes: 73 additions & 28 deletions modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::LDAP
include Msf::OptionalSession::LDAP
include Rex::Proto::MsDnsp
include Rex::Proto::Secauthz

ADS_GROUP_TYPE_BUILTIN_LOCAL_GROUP = 0x00000001
Expand Down Expand Up @@ -417,31 +418,37 @@ def find_enrollable_vuln_certificate_templates
allowed_sids = parse_acl(security_descriptor.dacl) if security_descriptor.dacl
next if allowed_sids.empty?

service = report_service({
host: ca_server[:dnshostname][0],
port: 445,
proto: 'tcp',
name: 'AD CS',
info: "AD CS CA name: #{ca_server[:name][0]}"
})

report_note({
data: ca_server[:dn][0].to_s,
service: service,
host: ca_server[:dnshostname][0],
ntype: 'windows.ad.cs.ca.dn'
})

report_host({
host: ca_server[:dnshostname][0],
name: ca_server[:dnshostname][0]
})

ca_server_key = ca_server[:dnshostname][0].to_sym
ca_server_fqdn = ca_server[:dnshostname][0].to_s.downcase
ca_server_ip_address = get_ip_addresses_by_fqdn(ca_server_fqdn)&.first

if ca_server_ip_address
service = report_service({
host: ca_server_ip_address,
port: 445,
proto: 'tcp',
name: 'AD CS',
info: "AD CS CA name: #{ca_server[:name][0]}"
})

report_note({
data: ca_server[:dn][0].to_s,
service: service,
host: ca_server_ip_address,
ntype: 'windows.ad.cs.ca.dn'
})

report_host({
host: ca_server_ip_address,
name: ca_server_fqdn
})
end

ca_server_key = ca_server_fqdn.to_sym
next if @certificate_details[certificate_template][:ca_servers].key?(ca_server_key)

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

hash[:ca_servers].each do |dnshostname, ca_server|
hash[:ca_servers].each do |ca_fqdn, ca_server|
service = report_service({
host: dnshostname.to_s,
host: ca_server[:ip_address],
port: 445,
proto: 'tcp',
name: 'AD CS',
info: "AD CS CA name: #{ca_server[:name]}"
})

vuln = report_vuln(
host: dnshostname.to_s,
host: ca_server[:ip_address],
port: 445,
proto: 'tcp',
sname: 'AD CS',
Expand All @@ -511,7 +518,7 @@ def print_vulnerable_cert_info
report_note({
data: hash[:dn],
service: service,
host: dnshostname.to_s,
host: ca_fqdn.to_s,
ntype: 'windows.ad.cs.ca.template.dn',
vuln_id: vuln.id
})
Expand Down Expand Up @@ -539,8 +546,8 @@ def print_vulnerable_cert_info
end

if hash[:ca_servers].any?
hash[:ca_servers].each do |ca_hostname, ca_hash|
print_good(" Issuing CA: #{ca_hash[:name]} (#{ca_hostname})")
hash[:ca_servers].each do |ca_fqdn, ca_hash|
print_good(" Issuing CA: #{ca_hash[:name]} (#{ca_fqdn})")
print_status(' Enrollment SIDs:')
convert_sids_to_human_readable_name(ca_hash[:enrollment_sids]).each do |sid|
print_status(" * #{highlight_sid(sid)}")
Expand Down Expand Up @@ -607,10 +614,48 @@ def get_object_by_sid(object_sid)
object
end

def get_ip_addresses_by_fqdn(host_fqdn)
return @fqdns[host_fqdn] if @fqdns.key?(host_fqdn)

vprint_status("Looking up DNS records for #{host_fqdn} in LDAP.")
hostname, _, domain = host_fqdn.partition('.')
results = query_ldap_server(
"(&(objectClass=dnsNode)(DC=#{ldap_escape_filter(hostname)}))",
%w[dnsRecord],
base_prefix: "DC=#{ldap_escape_filter(domain)},CN=MicrosoftDNS,DC=DomainDnsZones"
)
return nil if results.blank?

ip_addresses = []
results.first[:dnsrecord].each do |packed|
begin
unpacked = MsDnspDnsRecord.read(packed)
rescue ::EOFError
next
rescue ::IOError
next
end

next unless [ DnsRecordType::DNS_TYPE_A, DnsRecordType::DNS_TYPE_AAAA ].include?(unpacked.record_type)

ip_addresses << unpacked.data.to_s
end

@fqdns[host_fqdn] = ip_addresses
if ip_addresses.empty?
print_warning("No A or AAAA DNS records were found for #{host_fqdn} in LDAP.")
else
vprint_status("Found #{ip_addresses.length} IP address#{ip_addresses.length > 1 ? 'es' : ''} via A and AAAA DNS records.")
end

ip_addresses
end

def run
# Define our instance variables real quick.
@base_dn = nil
@ldap_objects = []
@fqdns = {}
@certificate_details = {} # Initialize to empty hash since we want to only keep one copy of each certificate template along with its details.

ldap_connect do |ldap|
Expand Down

0 comments on commit 3fe66ba

Please sign in to comment.