Skip to content

Commit 03d1523

Browse files
committed
Land rapid7#6611, add native DNS to Rex, MSF mixin, sample modules
2 parents aae77fc + afaf832 commit 03d1523

File tree

16 files changed

+2061
-123
lines changed

16 files changed

+2061
-123
lines changed

lib/msf/core/exploit/dns.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# -*- coding: binary -*-
2+
require 'msf/core'
3+
require 'rex/proto/dns'
4+
5+
6+
module Msf
7+
8+
###
9+
#
10+
# This namespace exposes methods for interacting with and providing services
11+
#
12+
###
13+
module Exploit::Remote::DNS
14+
15+
end
16+
end
17+
18+
require 'msf/core/exploit/dns/common'
19+
require 'msf/core/exploit/dns/client'
20+
require 'msf/core/exploit/dns/server'

lib/msf/core/exploit/dns/client.rb

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# -*- coding: binary -*-
2+
require 'msf/core'
3+
require 'rex/proto/dns'
4+
5+
6+
module Msf
7+
8+
###
9+
#
10+
# This module exposes methods for querying a remote DNS service
11+
#
12+
###
13+
module Exploit::Remote::DNS
14+
module Client
15+
16+
include Common
17+
include Exploit::Remote::Udp
18+
include Exploit::Remote::Tcp
19+
20+
#
21+
# Initializes an exploit module that interacts with a DNS server.
22+
#
23+
def initialize(info = {})
24+
super
25+
26+
deregister_options('RHOST')
27+
register_options(
28+
[
29+
Opt::RPORT(53),
30+
Opt::Proxies,
31+
OptString.new('DOMAIN', [ false, "The target domain name"]),
32+
OptString.new('NS', [ false, "Specify the nameservers to use for queries, space separated" ]),
33+
OptString.new('SEARCHLIST', [ false, "DNS domain search list, comma separated"]),
34+
OptInt.new('THREADS', [true, "Number of threads to use in threaded queries", 1])
35+
], Exploit::Remote::DNS::Client
36+
)
37+
38+
register_advanced_options(
39+
[
40+
OptString.new('DnsClientDefaultNS', [ false, "Specify the default to use for queries, space separated", '8.8.8.8 8.8.4.4' ]),
41+
OptInt.new('DnsClientRetry', [ false, "Number of times to try to resolve a record if no response is received", 2]),
42+
OptInt.new('DnsClientRetryInterval', [ false, "Number of seconds to wait before doing a retry", 2]),
43+
OptBool.new('DnsClientReportARecords', [false, "Add hosts found via BRT and RVL to DB", true]),
44+
OptBool.new('DnsClientRVLExistingOnly', [false, "Only perform lookups on hosts in DB", true]),
45+
OptBool.new('DnsClientTcpDns', [false, "Run queries over TCP", false]),
46+
OptPath.new('DnsClientResolvconf', [true, "Resolvconf formatted configuration file to use for Resolver", "/dev/null"])
47+
], Exploit::Remote::DNS::Client
48+
)
49+
50+
register_autofilter_ports([ 53 ]) if respond_to?(:register_autofilter_ports)
51+
register_autofilter_services(%W{ dns }) if respond_to?(:register_autofilter_services)
52+
end
53+
54+
55+
#
56+
# Convenience wrapper around Resolver's query method - send DNS request
57+
#
58+
# @param domain [String] Domain for which to request a record
59+
# @param type [String] Type of record to request for domain
60+
#
61+
# @return [Dnsruby::RR] DNS response
62+
def query(domain = datastore['DOMAIN'], type = 'A')
63+
client.query(domain, type)
64+
end
65+
66+
#
67+
# Performs a set of asynchronous lookups for an array of domain,type pairs
68+
#
69+
# @param queries [Array] Set of domain,type pairs to pass into #query
70+
# @param threadmax [Fixnum] Max number of running threads at a time
71+
# @param block [Proc] Code block to execute with the query result
72+
#
73+
# @return [Array] Resulting set of responses or responses processed by passed blocks
74+
def query_async(queries = [], threadmax = datastore['THREADS'], &block)
75+
running = []
76+
while !queries.empty?
77+
domain, type = queries.shift
78+
running << framework.threads.spawn("Module(#{self.refname})-#{domain} #{type}", false) do |qat|
79+
if block
80+
block.call(query(domain,type))
81+
else
82+
query(domain,type)
83+
end
84+
end
85+
while running.select(&:alive?).count >= threadmax
86+
Rex::ThreadSafe.sleep(1)
87+
end
88+
end
89+
return running.join
90+
end
91+
92+
#
93+
# Switch DNS forwarders in resolver with thread safety
94+
#
95+
# @param ns [Array, String] List of (or single) nameservers to use
96+
def set_nameserver(ns = [])
97+
if ns.respond_to?(:split)
98+
ns = [ns]
99+
end
100+
@lock.synchronize do
101+
@dns_resolver.nameserver = ns.flatten
102+
end
103+
end
104+
105+
#
106+
# Switch nameservers to use explicit NS or SOA for target
107+
#
108+
# @param domain [String] Domain for which to find SOA
109+
def switchdns(domain)
110+
if datastore['NS'].blank?
111+
resp_soa = client.query(target, "SOA")
112+
if (resp_soa)
113+
(resp_soa.answer.select { |i| i.is_a?(Dnsruby::RR::SOA)}).each do |rr|
114+
resp_1_soa = client.search(rr.mname)
115+
if (resp_1_soa and resp_1_soa.answer[0])
116+
set_nameserver(resp_1_soa.answer.map(&:address).compact.map(&:to_s))
117+
print_status("Set DNS Server to #{target} NS: #{client.nameserver.join(', ')}")
118+
break
119+
end
120+
end
121+
end
122+
else
123+
vprint_status("Using DNS Server: #{client.nameserver.join(', ')}")
124+
client.nameserver = process_nameservers
125+
end
126+
end
127+
128+
#
129+
# Detect if target has wildcards enabled for a record type
130+
#
131+
# @param target [String] Domain to test
132+
# @param type [String] Record type to test
133+
#
134+
# @return [String] Address which is returned for wildcard requests
135+
def wildcard(domain, type = "A")
136+
addr = false
137+
rendsub = rand(10000).to_s
138+
response = query("#{rendsub}.#{target}", type)
139+
if response.answer.length != 0
140+
vprint_status("This domain has wildcards enabled!!")
141+
response.answer.each do |rr|
142+
print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Dnsruby::RR::CNAME
143+
addr = rr.address.to_s
144+
end
145+
end
146+
return addr
147+
end
148+
149+
#
150+
# Create and configure Resolver object
151+
#
152+
def setup_resolver
153+
options.validate(datastore) # This is a hack, DS values should not be Strings prior to this
154+
config = {
155+
:config_file => datastore['DnsClientResolvconf'],
156+
:nameservers => process_nameservers,
157+
:port => datastore['RPORT'],
158+
:retry_number => datastore['DnsClientRetry'].to_i,
159+
:retry_interval => datastore['DnsClientRetryInterval'].to_i,
160+
:use_tcp => datastore['DnsClientTcpDns'],
161+
:context => {'Msf' => framework, 'MsfExploit' => self}
162+
}
163+
if datastore['SEARCHLIST']
164+
if datastore['SEARCHLIST'].split(',').all? do |search|
165+
search.match(MATCH_HOSTNAME)
166+
end
167+
config[:search_list] = datastore['SEARCHLIST'].split(',')
168+
else
169+
raise 'Domain search list must consist of valid domains'
170+
end
171+
end
172+
if datastore['CHOST']
173+
config[:source_address] = IPAddr.new(datastore['CHOST'].to_s)
174+
end
175+
if datastore['CPORT']
176+
config[:source_port] = datastore['CPORT'] unless datastore['CPORT'] == 0
177+
end
178+
if datastore['Proxies']
179+
vprint_status("Using DNS/TCP resolution for proxy config")
180+
config[:use_tcp] = true
181+
config[:proxies] = datastore['Proxies']
182+
end
183+
@dns_resolver_lock = Mutex.new unless @dns_resolver_lock
184+
@dns_resolver = Rex::Proto::DNS::Resolver.new(config)
185+
end
186+
187+
#
188+
# Convenience method for DNS resolver as client
189+
# Executes setup_resolver if none exists
190+
#
191+
def client
192+
setup_resolver unless @dns_resolver
193+
@dns_resolver
194+
end
195+
196+
#
197+
# Sets the resolver's nameservers
198+
# Uses explicitly defined NS option if set
199+
# Uses RHOSTS if not explicitly defined
200+
def process_nameservers
201+
if datastore['NS'].blank?
202+
nameservers = datastore['DnsClientDefaultNS'].split(/\s|,/)
203+
else
204+
nameservers = datastore['NS'].split(/\s|,/)
205+
end
206+
207+
invalid = nameservers.select { |ns| !Rex::Socket.dotted_ip?(ns) }
208+
if !invalid.empty?
209+
raise "Nameservers must be IP addresses. The following were invalid: #{invalid.join(", ")}"
210+
end
211+
212+
nameservers
213+
end
214+
215+
end
216+
end
217+
end

lib/msf/core/exploit/dns/common.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# -*- coding: binary -*-
2+
require 'msf/core'
3+
require 'rex/proto/dns'
4+
5+
6+
module Msf
7+
8+
###
9+
#
10+
# This module exposes methods for querying a remote DNS service
11+
#
12+
###
13+
module Exploit::Remote::DNS
14+
module Common
15+
16+
MATCH_HOSTNAME = Rex::Proto::DNS::Constants::MATCH_HOSTNAME
17+
18+
Packet = Rex::Proto::DNS::Packet
19+
20+
end
21+
end
22+
end

0 commit comments

Comments
 (0)