c65c03722c
Dnsruby provides advanced options like DNSSEC in its data format and is a current and well supported library. The infrastructure services - resolver, server, etc, were designed for a standalone configuration, and carry entirely too much weight and redundancy to implement for this context. Instead of porting over their native resolver, update the Net::DNS subclassed Rex Resolver to use Dnsruby data formats and method calls. Update the Msf namespace infrastructure mixins and native server module with new method calls and workarounds for some instance variables having only readers without writers. Implement the Rex ServerManager to start and stop the DNS service adding relevant alias methods to the Rex::Proto::DNS::Server class. Rex services are designed to be modular and lightweight, as well as implement the sockets, threads, and other low-level interfaces. Dnsruby's operations classes implement their own threading and socket semantics, and do not fit with the modular mixin workflow used throughout Framework. So while the updated resolver can be seen as adding rubber to the tire fire, converting to dnsruby's native classes for resolvers, servers, and caches, would be more like adding oxy acetylene and heavy metals. Testing: Internal tests for resolution of different record types locally and over pivot sessions.
218 lines
6.9 KiB
Ruby
218 lines
6.9 KiB
Ruby
# -*- coding: binary -*-
|
|
require 'msf/core'
|
|
require 'rex/proto/dns'
|
|
|
|
|
|
module Msf
|
|
|
|
###
|
|
#
|
|
# This module exposes methods for querying a remote DNS service
|
|
#
|
|
###
|
|
module Exploit::Remote::DNS
|
|
module Client
|
|
|
|
include Common
|
|
include Exploit::Remote::Udp
|
|
include Exploit::Remote::Tcp
|
|
|
|
#
|
|
# Initializes an exploit module that interacts with a DNS server.
|
|
#
|
|
def initialize(info = {})
|
|
super
|
|
|
|
deregister_options('RHOST')
|
|
register_options(
|
|
[
|
|
Opt::RPORT(53),
|
|
Opt::Proxies,
|
|
OptString.new('DOMAIN', [ false, "The target domain name"]),
|
|
OptString.new('NS', [ false, "Specify the nameservers to use for queries, space separated" ]),
|
|
OptString.new('SEARCHLIST', [ false, "DNS domain search list, comma separated"]),
|
|
OptInt.new('THREADS', [true, "Number of threads to use in threaded queries", 1])
|
|
], Exploit::Remote::DNS::Client
|
|
)
|
|
|
|
register_advanced_options(
|
|
[
|
|
OptString.new('DnsClientDefaultNS', [ false, "Specify the default to use for queries, space separated", '8.8.8.8 8.8.4.4' ]),
|
|
OptInt.new('DnsClientRetry', [ false, "Number of times to try to resolve a record if no response is received", 2]),
|
|
OptInt.new('DnsClientRetryInterval', [ false, "Number of seconds to wait before doing a retry", 2]),
|
|
OptBool.new('DnsClientReportARecords', [false, "Add hosts found via BRT and RVL to DB", true]),
|
|
OptBool.new('DnsClientRVLExistingOnly', [false, "Only perform lookups on hosts in DB", true]),
|
|
OptBool.new('DnsClientTcpDns', [false, "Run queries over TCP", false]),
|
|
OptPath.new('DnsClientResolvconf', [true, "Resolvconf formatted configuration file to use for Resolver", "/dev/null"])
|
|
], Exploit::Remote::DNS::Client
|
|
)
|
|
|
|
register_autofilter_ports([ 53 ]) if respond_to?(:register_autofilter_ports)
|
|
register_autofilter_services(%W{ dns }) if respond_to?(:register_autofilter_services)
|
|
end
|
|
|
|
|
|
#
|
|
# Convenience wrapper around Resolver's query method - send DNS request
|
|
#
|
|
# @param domain [String] Domain for which to request a record
|
|
# @param type [String] Type of record to request for domain
|
|
#
|
|
# @return [Dnsruby::RR] DNS response
|
|
def query(domain = datastore['DOMAIN'], type = 'A')
|
|
client.query(domain, type)
|
|
end
|
|
|
|
#
|
|
# Performs a set of asynchronous lookups for an array of domain,type pairs
|
|
#
|
|
# @param queries [Array] Set of domain,type pairs to pass into #query
|
|
# @param threadmax [Fixnum] Max number of running threads at a time
|
|
# @param block [Proc] Code block to execute with the query result
|
|
#
|
|
# @return [Array] Resulting set of responses or responses processed by passed blocks
|
|
def query_async(queries = [], threadmax = datastore['THREADS'], &block)
|
|
running = []
|
|
while !queries.empty?
|
|
domain, type = queries.shift
|
|
running << framework.threads.spawn("Module(#{self.refname})-#{domain} #{type}", false) do |qat|
|
|
if block
|
|
block.call(query(domain,type))
|
|
else
|
|
query(domain,type)
|
|
end
|
|
end
|
|
while running.select(&:alive?).count >= threadmax
|
|
Rex::ThreadSafe.sleep(1)
|
|
end
|
|
end
|
|
return running.join
|
|
end
|
|
|
|
#
|
|
# Switch DNS forwarders in resolver with thread safety
|
|
#
|
|
# @param ns [Array, String] List of (or single) nameservers to use
|
|
def set_nameserver(ns = [])
|
|
if ns.respond_to?(:split)
|
|
ns = [ns]
|
|
end
|
|
@lock.synchronize do
|
|
@dns_resolver.nameserver = ns.flatten
|
|
end
|
|
end
|
|
|
|
#
|
|
# Switch nameservers to use explicit NS or SOA for target
|
|
#
|
|
# @param domain [String] Domain for which to find SOA
|
|
def switchdns(domain)
|
|
if datastore['NS'].blank?
|
|
resp_soa = client.query(target, "SOA")
|
|
if (resp_soa)
|
|
(resp_soa.answer.select { |i| i.is_a?(Dnsruby::RR::SOA)}).each do |rr|
|
|
resp_1_soa = client.search(rr.mname)
|
|
if (resp_1_soa and resp_1_soa.answer[0])
|
|
set_nameserver(resp_1_soa.answer.map(&:address).compact.map(&:to_s))
|
|
print_status("Set DNS Server to #{target} NS: #{client.nameserver.join(', ')}")
|
|
break
|
|
end
|
|
end
|
|
end
|
|
else
|
|
vprint_status("Using DNS Server: #{client.nameserver.join(', ')}")
|
|
client.nameserver = process_nameservers
|
|
end
|
|
end
|
|
|
|
#
|
|
# Detect if target has wildcards enabled for a record type
|
|
#
|
|
# @param target [String] Domain to test
|
|
# @param type [String] Record type to test
|
|
#
|
|
# @return [String] Address which is returned for wildcard requests
|
|
def wildcard(domain, type = "A")
|
|
addr = false
|
|
rendsub = rand(10000).to_s
|
|
response = query("#{rendsub}.#{target}", type)
|
|
if response.answer.length != 0
|
|
vprint_status("This domain has wildcards enabled!!")
|
|
response.answer.each do |rr|
|
|
print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Dnsruby::RR::CNAME
|
|
addr = rr.address.to_s
|
|
end
|
|
end
|
|
return addr
|
|
end
|
|
|
|
#
|
|
# Create and configure Resolver object
|
|
#
|
|
def setup_resolver
|
|
options.validate(datastore) # This is a hack, DS values should not be Strings prior to this
|
|
config = {
|
|
:config_file => datastore['DnsClientResolvconf'],
|
|
:nameservers => process_nameservers,
|
|
:port => datastore['RPORT'],
|
|
:retry_number => datastore['DnsClientRetry'].to_i,
|
|
:retry_interval => datastore['DnsClientRetryInterval'].to_i,
|
|
:use_tcp => datastore['DnsClientTcpDns'],
|
|
:context => {'Msf' => framework, 'MsfExploit' => self}
|
|
}
|
|
if datastore['SEARCHLIST']
|
|
if datastore['SEARCHLIST'].split(',').all? do |search|
|
|
search.match(MATCH_HOSTNAME)
|
|
end
|
|
config[:search_list] = datastore['SEARCHLIST'].split(',')
|
|
else
|
|
raise 'Domain search list must consist of valid domains'
|
|
end
|
|
end
|
|
if datastore['CHOST']
|
|
config[:source_address] = IPAddr.new(datastore['CHOST'].to_s)
|
|
end
|
|
if datastore['CPORT']
|
|
config[:source_port] = datastore['CPORT'] unless datastore['CPORT'] == 0
|
|
end
|
|
if datastore['Proxies']
|
|
vprint_status("Using DNS/TCP resolution for proxy config")
|
|
config[:use_tcp] = true
|
|
config[:proxies] = datastore['Proxies']
|
|
end
|
|
@dns_resolver_lock = Mutex.new unless @dns_resolver_lock
|
|
@dns_resolver = Rex::Proto::DNS::Resolver.new(config)
|
|
end
|
|
|
|
#
|
|
# Convenience method for DNS resolver as client
|
|
# Executes setup_resolver if none exists
|
|
#
|
|
def client
|
|
setup_resolver unless @dns_resolver
|
|
@dns_resolver
|
|
end
|
|
|
|
#
|
|
# Sets the resolver's nameservers
|
|
# Uses explicitly defined NS option if set
|
|
# Uses RHOSTS if not explicitly defined
|
|
def process_nameservers
|
|
if datastore['NS'].blank?
|
|
nameservers = datastore['DnsClientDefaultNS'].split(/\s|,/)
|
|
else
|
|
nameservers = datastore['NS'].split(/\s|,/)
|
|
end
|
|
|
|
invalid = nameservers.select { |ns| !Rex::Socket.dotted_ip?(ns) }
|
|
if !invalid.empty?
|
|
raise "Nameservers must be IP addresses. The following were invalid: #{invalid.join(", ")}"
|
|
end
|
|
|
|
nameservers
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|