Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
647 lines (619 sloc) 22.6 KB
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
# http://metasploit.com/
##
require 'msf/core'
require "net/dns/resolver"
class Metasploit3 < Msf::Auxiliary
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
'Name' => 'DNS Record Scanner and Enumerator ',
'Description' => %q{
This module can be used to gather information about a domain from a
given DNS server by performing various DNS queries such as zone
transfers, reverse lookups, SRV record bruteforcing, and other techniques.
},
'Author' => [ 'Carlos Perez <carlos_perez[at]darkoperator.com>' ],
'License' => MSF_LICENSE,
'Version' => '$Revision$',
'References' =>
[
['CVE', '1999-0532'],
['OSVDB', '492'],
]
))
register_options(
[
OptString.new('DOMAIN', [ true, "The target domain name"]),
OptBool.new('ENUM_AXFR', [ true, 'Initiate a zone transfer against each NS record', false]),
OptBool.new('ENUM_TLD', [ true, 'Perform a TLD expansion by replacing the TLD with the IANA TLD list', false]),
OptBool.new('ENUM_STD', [ true, 'Enumerate standard record types (A,MX,NS,TXT and SOA)', true]),
OptBool.new('ENUM_BRT', [ true, 'Brute force subdomains and hostnames via the supplied wordlist', false]),
OptBool.new('ENUM_IP6', [ true, 'Brute force hosts with IPv6 AAAA records',false]),
OptBool.new('ENUM_RVL', [ true, 'Reverse lookup a range of IP addresses', false]),
OptBool.new('ENUM_SRV', [ true, 'Enumerate the most common SRV records', true]),
OptPath.new('WORDLIST', [ false, "Wordlist for domain name bruteforcing", ::File.join(Msf::Config.install_root, "data", "wordlists", "namelist.txt")]),
OptAddress.new('NS', [ false, "Specify the nameserver to use for queries (default is system DNS)" ]),
OptAddressRange.new('IPRANGE', [false, "The target address range or CIDR identifier"]),
OptBool.new('STOP_WLDCRD', [ true, 'Stops bruteforce enumeration if wildcard resolution is detected', false])
], self.class)
register_advanced_options(
[
OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received", 2]),
OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry", 2]),
OptInt.new('THREADS', [ true, "Number of threads to use for BRT and RVL", 1]),
OptBool.new('REPORT_A_RECORDS', [false, "Add hosts found via BRT and RVL to DB", true]),
OptBool.new('RVL_EXISTING_ONLY', [false, "Only perform lookups on hosts in DB", false]),
OptBool.new('TCP_DNS', [false, "Run queries over TCP", false]),
], self.class)
end
#---------------------------------------------------------------------------------
def switchdns(target)
if not datastore['NS'].nil?
print_status("Using DNS Server: #{datastore['NS']}")
@res.nameserver=(datastore['NS'])
@nsinuse = datastore['NS']
else
querysoa = @res.query(target, "SOA")
if (querysoa)
(querysoa.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr|
query1soa = @res.search(rr.mname)
if (query1soa and query1soa.answer[0])
print_status("Setting DNS Server to #{target} NS: #{query1soa.answer[0].address}")
@res.nameserver=(query1soa.answer[0].address)
@nsinuse = query1soa.answer[0].address
end
end
end
end
end
#---------------------------------------------------------------------------------
def wildcard(target)
addr = false
rendsub = rand(10000).to_s
query = @res.query("#{rendsub}.#{target}", "A")
if query.answer.length != 0
print_status("This domain has wildcards enabled!!")
query.answer.each do |rr|
print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME
addr = rr.address.to_s
end
end
return addr
end
def wildcard6(target)
addr = false
rendsub = rand(10000).to_s
query = @res.query("#{rendsub}.#{target}", "AAAA")
if query.answer.length != 0
print_status("This domain has wildcards enabled!!")
query.answer.each do |rr|
print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME
addr = rr.address.to_s
end
end
return addr
end
#---------------------------------------------------------------------------------
def genrcd(target)
print_status("Retrieving general DNS records")
query = @res.search(target)
if (query)
query.answer.each do |rr|
next unless rr.class == Net::DNS::RR::A
print_status("Domain: #{target} IP address: #{rr.address} Record: A ")
report_note(:host => @nsinuse.to_s,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{rr.address.to_s},#{target},A")
end
end
query = @res.query(target, "SOA")
if (query)
(query.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr|
query1 = @res.search(rr.mname)
if (query1)
query1.answer.each do |ip|
print_status("Start of Authority: #{rr.mname} IP address: #{ip.address} Record: SOA")
report_note(:host => @nsinuse.to_s,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{ip.address.to_s},#{rr.mname},SOA")
end
end
end
end
query = @res.query(target, "NS")
if (query)
(query.answer.select { |i| i.class == Net::DNS::RR::NS}).each do |rr|
query1 = @res.search(rr.nsdname)
if (query1)
query1.answer.each do |ip|
next unless ip.class == Net::DNS::RR::A
print_status("Name Server: #{rr.nsdname} IP address: #{ip.address} Record: NS")
report_note(:host => @nsinuse.to_s,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{ip.address.to_s},#{rr.nsdname},NS")
end
end
end
end
query = @res.query(target, "MX")
if (query)
(query.answer.select { |i| i.class == Net::DNS::RR::MX}).each do |rr|
print_status("Name: #{rr.exchange} Preference: #{rr.preference} Record: MX")
report_note(:host => @nsinuse.to_s,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{rr.exchange},MX")
end
end
query = @res.query(target, "TXT")
if (query)
query.answer.each do |rr|
print_status(rr.inspect)
print_status("Text: #{rr.inspect}")
report_note(:host => @nsinuse.to_s,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => rr.inspect)
end
end
end
#---------------------------------------------------------------------------------
def tldexpnd(targetdom,nssrv)
target = targetdom.scan(/(\S*)[.]\w*\z/).join
target.chomp!
if not nssrv.nil?
@res.nameserver=(nssrv)
@nsinuse = nssrv
end
i, a = 0, []
tlds = [
"com", "org", "net", "edu", "mil", "gov", "uk", "af", "al", "dz",
"as", "ad", "ao", "ai", "aq", "ag", "ar", "am", "aw", "ac","au",
"at", "az", "bs", "bh", "bd", "bb", "by", "be", "bz", "bj", "bm",
"bt", "bo", "ba", "bw", "bv", "br", "io", "bn", "bg", "bf", "bi",
"kh", "cm", "ca", "cv", "ky", "cf", "td", "cl", "cn", "cx", "cc",
"co", "km", "cd", "cg", "ck", "cr", "ci", "hr", "cu", "cy", "cz",
"dk", "dj", "dm", "do", "tp", "ec", "eg", "sv", "gq", "er", "ee",
"et", "fk", "fo", "fj", "fi", "fr", "gf", "pf", "tf", "ga", "gm",
"ge", "de", "gh", "gi", "gr", "gl", "gd", "gp", "gu", "gt", "gg",
"gn", "gw", "gy", "ht", "hm", "va", "hn", "hk", "hu", "is", "in",
"id", "ir", "iq", "ie", "im", "il", "it", "jm", "jp", "je", "jo",
"kz", "ke", "ki", "kp", "kr", "kw", "kg", "la", "lv", "lb", "ls",
"lr", "ly", "li", "lt", "lu", "mo", "mk", "mg", "mw", "my", "mv",
"ml", "mt", "mh", "mq", "mr", "mu", "yt", "mx", "fm", "md", "mc",
"mn", "ms", "ma", "mz", "mm", "na", "nr", "np", "nl", "an", "nc",
"nz", "ni", "ne", "ng", "nu", "nf", "mp", "no", "om", "pk", "pw",
"pa", "pg", "py", "pe", "ph", "pn", "pl", "pt", "pr", "qa", "re",
"ro", "ru", "rw", "kn", "lc", "vc", "ws", "sm", "st", "sa", "sn",
"sc", "sl", "sg", "sk", "si", "sb", "so", "za", "gz", "es", "lk",
"sh", "pm", "sd", "sr", "sj", "sz", "se", "ch", "sy", "tw", "tj",
"tz", "th", "tg", "tk", "to", "tt", "tn", "tr", "tm", "tc", "tv",
"ug", "ua", "ae", "gb", "us", "um", "uy", "uz", "vu", "ve", "vn",
"vg", "vi", "wf", "eh", "ye", "yu", "za", "zr", "zm", "zw", "int",
"gs", "info", "biz", "su", "name", "coop", "aero" ]
print_status("Performing Top Level Domain expansion using #{tlds.size} TLDs")
tlds.each do |tld|
query1 = @res.search("#{target}.#{tld}")
if (query1)
query1.answer.each do |rr|
print_status("Domain: #{target}.#{tld} Name: #{rr.name} IP address: #{rr.address} Record: A ") if rr.class == Net::DNS::RR::A
report_note(:host => @nsinuse.to_s,
:proto => 'udp',
:sname => 'dns',
:port => 53,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{rr.address.to_s},#{target}.#{tld},A") if rr.class == Net::DNS::RR::A
end
end
end
end
#-------------------------------------------------------------------------------
def split_wordlist(words)
length = (words.length / @threadnum).ceil
words = words.each_slice(length).to_a
end
#-------------------------------------------------------------------------------
def dnsbrute(target, wordlist, nssrv, wldcrd = nil)
print_status("Bruteforcing IPv4 addresses against domain #{target}")
# Read words, split list for threaded bruteforce
words = split_wordlist(
::File.read(wordlist).split("\n").uniq.map {|w| w.gsub(".#{target}", '')}
)
# chunk_size = words.length/@threadnum
# words = words.each_slice(chunk_size).to_a
print_status("Running with #{words.flatten.length} words in #{@threadnum} threads of #{words.first.length} each")
print_good("Ignoring #{wldcrd}") if wldcrd
# Give each thread a chunk of words to test
while !words.empty? do
if @dns_enum_threads.length < @threadnum
chunk = words.pop
@dns_enum_threads << Rex::ThreadFactory.spawn("DNS Brute Force #{target} #{@dns_enum_threads.length + 1}", false) do
chunk.each do |line|
if not nssrv.nil?
@res.nameserver=(nssrv)
@nsinuse = nssrv
end
query1 = @res.search("#{line.chomp}.#{target}")
if (query1)
query1.answer.each do |rr|
if rr.class == Net::DNS::RR::A and rr.address.to_s != wldcrd
print_status("Hostname: #{line.chomp}.#{target} IPv4 address: #{rr.address.to_s}")
report_note(:host => @nsinuse.to_s,
:proto => @res.use_tcp? ? 'tcp' : 'udp',
:sname => 'dns',
:port => 53,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{rr.address.to_s},#{line.chomp}.#{target},A")
if datastore['REPORT_A_RECORDS']
report_host(
:host => rr.address.to_s,
:name => "#{line.chomp}.#{target}".chomp('.'),
:update => :unique_data,
:state => Msf::HostState::Alive
)
end
next unless rr.class == Net::DNS::RR::CNAME
end
end
end
end
end
else
Rex::ThreadSafe.sleep(5)
end
end
end
#-------------------------------------------------------------------------------
def bruteipv6(target, wordlist, nssrv, wldcrd = nil)
print_status("Bruteforcing IPv6 addresses against domain #{target}")
# Read words, split list for threaded bruteforce
words = ::File.read(wordlist).split("\n").uniq.map {|w| w.gsub(".#{target}", '')}
chunk_size = words.length/@threadnum
words = words.each_slice(chunk_size).to_a
print_status("Running with #{words.flatten.length} words in #{@threadnum} threads of #{chunk_size} each")
print_good("Ignoring #{wldcrd}") if wldcrd
# Give each thread a chunk of words to test
while !words.empty? do
if @dns_enum_threads.length < @threadnum
chunk = words.pop
@dns_enum_threads << Rex::ThreadFactory.spawn("DNS Brute Force #{target} #{@dns_enum_threads.length + 1}", false) do
chunk.each do |line|
if not nssrv.nil?
@res.nameserver=(nssrv)
@nsinuse = nssrv
end
query1 = @res.search("#{line.chomp}.#{target}", "AAAA")
if (query1)
query1.answer.each do |rr|
if rr.class == Net::DNS::RR::AAAA and rr.address.to_s != wldcrd
print_status("Hostname: #{line.chomp}.#{target} IPv6 address: #{rr.address.to_s}")
report_note(:host => @nsinuse.to_s,
:proto => @res.use_tcp? ? 'tcp' : 'udp',
:sname => 'dns',
:port => 53,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{rr.address.to_s},#{line.chomp}.#{target},AAAA")
if datastore['REPORT_A_RECORDS']
report_host(
:host => rr.address.to_s,
:name => "#{line.chomp}.#{target}".chomp('.'),
:update => :unique_data,
:state => Msf::HostState::Alive
)
end
next unless rr.class == Net::DNS::RR::CNAME
end
end
end
end
end
else
Rex::ThreadSafe.sleep(5)
end
end
end
#-------------------------------------------------------------------------------
def reverselkp(iprange,nssrv)
print_status("Running reverse lookup against IP range #{iprange}")
if not nssrv.nil?
@res.nameserver = (nssrv)
@nsinuse = nssrv
end
if datastore['RVL_EXISTING_ONLY']
rng = Rex::Socket::RangeWalker.new(iprange)
ws = Mdm::Workspace.where(:name => self.workspace).first
lookup_hosts = ws.hosts.map(&:address).keep_if do |addr|
rng.include?(addr)
end
ar = Rex::Socket::RangeWalker.new(lookup_hosts)
else
ar = Rex::Socket::RangeWalker.new(iprange)
end
while (true)
# Spawn threads for each host
while (@dns_enum_threads.length < @threadnum)
ip = ar.next_ip
break if not ip
@dns_enum_threads << framework.threads.spawn("Module(#{self.refname})-#{ip} RVL", false, ip.dup) do |tip|
begin
query = @res.query(tip)
query.each_ptr do |addresstp|
print_status("Hostname: #{addresstp} IP address: #{tip.to_s}")
report_note(
:host => @nsinuse.to_s,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{addresstp},#{tip},A,RVL"
)
if datastore['REPORT_A_RECORDS']
report_host(
:host => tip.to_s,
:name => "#{addresstp}".chomp('.'),
:update => :unique_data,
)
end
end
rescue ::Interrupt
raise $!
rescue ::Rex::ConnectionError
rescue ::Exception => e
print_error("Error: #{tip}: #{e.message}")
elog("Error running against host #{tip}: #{e.message}\n#{e.backtrace.join("\n")}")
end
end
end
# Exit once we run out of hosts
if(@dns_enum_threads.length == 0)
break
end
@dns_enum_threads.first.join
@dns_enum_threads.delete_if { |t| not t.alive? }
end
end
#-------------------------------------------------------------------------------
#SRV Record Enumeration
def srvqry(dom,nssrv)
print_status("Enumerating SRV records for #{dom}")
i, a = 0, []
#Most common SRV Records
srvrcd = [
"_gc._tcp.","_kerberos._tcp.", "_kerberos._udp.","_ldap._tcp","_test._tcp.",
"_sips._tcp.","_sip._udp.","_sip._tcp.","_aix._tcp.","_aix._tcp.","_finger._tcp.",
"_ftp._tcp.","_http._tcp.","_nntp._tcp.","_telnet._tcp.","_whois._tcp.","_h323cs._tcp.",
"_h323cs._udp.","_h323be._tcp.","_h323be._udp.","_h323ls._tcp.","_h323ls._udp.",
"_sipinternal._tcp.","_sipinternaltls._tcp.","_sip._tls.","_sipfederationtls._tcp.",
"_jabber._tcp.","_xmpp-server._tcp.","_xmpp-client._tcp.","_imap._tcp.","_certificates._tcp.",
"_crls._tcp.","_pgpkeys._tcp.","_pgprevokations._tcp.","_cmp._tcp.","_svcp._tcp.","_crl._tcp.",
"_ocsp._tcp.","_PKIXREP._tcp.","_smtp._tcp.","_hkp._tcp.","_hkps._tcp.","_jabber._udp.",
"_xmpp-server._udp.","_xmpp-client._udp.","_jabber-client._tcp.","_jabber-client._udp."]
srvrcd.each do |srvt|
trg = "#{srvt}#{dom}"
query = @res.query(trg , Net::DNS::SRV)
if query
query.answer.each do |srv|
print_status("SRV Record: #{trg} Host: #{srv.host} Port: #{srv.port} Priority: #{srv.priority}") if srv.type != "CNAME"
end
end
end
end
#-------------------------------------------------------------------------------
#For Performing Zone Transfers
def axfr(target, nssrv)
print_status("Performing zone transfer against all nameservers in #{target}")
if not nssrv.nil?
@res.nameserver=(nssrv)
@nsinuse = nssrv
end
@res.tcp_timeout=15
query = @res.query(target, "NS")
if (query.answer.length != 0)
(query.answer.select { |i| i.class == Net::DNS::RR::NS}).each do |nsrcd|
print_status("Testing nameserver: #{nsrcd.nsdname}")
nssrvquery = @res.query(nsrcd.nsdname, "A")
if nssrvquery.answer.length == 0
nssrvip = Rex::Socket.gethostbyname(nsrcd.nsdname)[3].bytes.reduce {|a,b| [a,b].join(".")}
else
nssrvip = nssrvquery.answer[0].address.to_s
end
begin
@res.nameserver=(nssrvip)
@nsinuse = nssrvip
zone = []
zone = @res.axfr(target)
if zone.length != 0
print_status("Zone transfer successful")
report_note(:host => nssrvip,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => "Zone transfer successful")
#Prints each record according to its type
zone.each do |response|
response.answer.each do |rr|
begin
case rr.type
when "A"
print_status("Name: #{rr.name} IP address: #{rr.address} Record: A ")
report_note(:host => nssrvip,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{rr.address.to_s},#{rr.name},A")
when "SOA"
print_status("Name: #{rr.mname} Record: SOA")
report_note(:host => nssrvip,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{rr.name},SOA")
when "MX"
print_status("Name: #{rr.exchange} Preference: #{rr.preference} Record: MX")
report_note(:host => nssrvip,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{rr.exchange},MX")
when "CNAME"
print_status("Name: #{rr.cname} Record: CNAME")
report_note(:host => nssrvip,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{rr.cname},CNAME")
when "HINFO"
print_status("CPU: #{rr.cpu} OS: #{rr.os} Record: HINFO")
report_note(:host => nssrvip,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => "CPU:#{rr.cpu},OS:#{rr.os},HINFO")
when "AAAA"
print_status("IPv6 Address: #{rr.address} Record: AAAA")
report_note(:host => nssrvip,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{rr.address.to_s}, AAAA")
when "NS"
print_status("Name: #{rr.nsdname} Record: NS")
report_note(:host => nssrvip,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{rr.nsdname},NS")
when "TXT"
print_status("Text: #{rr.inspect}")
report_note(:host => nssrvip,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => rr.inspect)
when "SRV"
print_status("Host: #{rr.host} Port: #{rr.port} Priority: #{rr.priority} Record: SRV")
report_note(:host => nssrvip,
:proto => 'udp',
:sname => 'dns',
:port => 53 ,
:type => 'dns.enum',
:update => :unique_data,
:data => "#{rr.host},#{rr.port},#{rr.priority},SRV")
end
rescue ActiveRecord::RecordInvalid
#Do nothing. Probably tried to store :host => 127.0.0.1
end
end
end
else
print_error("Zone transfer failed (length was zero)")
end
rescue Exception => e
print_error("Error executing zone transfer: #{e.message}")
elog("Error executing zone transfer: #{e.message}\n#{e.backtrace.join("\n")}")
end
end
else
print_error("Could not resolve domain #{target}")
end
end
def run
@dns_enum_threads = []
@res = Net::DNS::Resolver.new()
# The following line requires rex_dns.rb so checking for poxies
if @res.methods.include?(:proxies)
@res.proxies=datastore['Proxies'] if datastore['Proxies']
end
# Prevent us from using system DNS by default - net/dns pulls OS settings
@res.nameservers = datastore['NS'].split(/\s|,/) if datastore['NS']
# If querying over TCP
if datastore['TCP_DNS']
vprint_status("Using DNS/TCP")
@res.use_tcp = true
end
@res.retry = datastore['RETRY'].to_i
@res.retry_interval = datastore['RETRY_INTERVAL'].to_i
@threadnum = datastore['THREADS'].to_i
wldcrd = wildcard(datastore['DOMAIN'])
wldcrd6 = wildcard6(datastore['DOMAIN'])
switchdns(datastore['DOMAIN'])
if(datastore['ENUM_STD'])
genrcd(datastore['DOMAIN'])
end
if(datastore['ENUM_TLD'])
tldexpnd(datastore['DOMAIN'],datastore['NS'])
end
if(datastore['ENUM_BRT'])
if wldcrd and datastore['STOP_WLDCRD']
print_error("Wildcard record found!")
else
dnsbrute(datastore['DOMAIN'],datastore['WORDLIST'],datastore['NS'],wldcrd)
end
end
if(datastore['ENUM_IP6'])
if wldcrd and datastore['STOP_WLDCRD']
print_status("Wildcard Record Found!")
else
bruteipv6(datastore['DOMAIN'],datastore['WORDLIST'],datastore['NS'],wldcrd6)
end
end
if(datastore['ENUM_AXFR'])
axfr(datastore['DOMAIN'],datastore['NS'])
end
if(datastore['ENUM_SRV'])
srvqry(datastore['DOMAIN'],datastore['NS'])
end
if(datastore['ENUM_RVL'] and datastore['IPRANGE'] and not datastore['IPRANGE'].empty?)
reverselkp(datastore['IPRANGE'],datastore['NS'])
end
# Do not let module finish while threads exist
while not @dns_enum_threads.empty? do
vprint_status("Waiting on #{@dns_enum_threads.length} threads to finish")
Rex::ThreadSafe.sleep(10)
end
end
end