Skip to content

Commit 21f3335

Browse files
committed
Fully integrated Rex-socket-friendly DNS
1 parent a7c4b29 commit 21f3335

File tree

3 files changed

+110
-33
lines changed

3 files changed

+110
-33
lines changed

lib/msf/ui/console/command_dispatcher/dns.rb

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ class DNS
1414
['-s', '--session'] => [true, 'Force the DNS request to occur over a particular channel (override routing rules)' ],
1515
)
1616

17+
@@remove_opts = Rex::Parser::Arguments.new(
18+
['-i'] => [true, 'Index to remove']
19+
)
20+
1721
def initialize(driver)
1822
super
1923
end
@@ -109,9 +113,8 @@ def add_dns(*args)
109113
end
110114
case opt
111115
when '--rule', '-r'
112-
if val.nil?
113-
raise ::ArgumentError.new('No rule specified')
114-
end
116+
raise ::ArgumentError.new('No rule specified') if val.nil?
117+
raise ::ArgumentError.new("Invalid rule: #{val}") unless valid_rule(val)
115118

116119
rules << val
117120
when '--session', '-s'
@@ -143,26 +146,50 @@ def add_dns(*args)
143146
end
144147
end
145148

149+
comm_obj = nil
150+
146151
unless comm.nil?
147152
raise ::ArgumentError.new("Not a valid number: #{comm}") unless comm =~ /^\d+$/
148153
comm_int = comm.to_i
149154
raise ::ArgumentError.new("Session does not exist: #{comm}") unless driver.framework.sessions.include?(comm_int)
150-
155+
comm_obj = driver.framework.sessions[comm_int]
151156
end
152157

153158
# Split each DNS server entry up into a separate entry
154159
servers.each do |server|
155-
driver.framework.dns_resolver.add_nameserver(rules, server, comm_int)
160+
driver.framework.dns_resolver.add_nameserver(rules, server, comm_obj)
156161
end
157162
end
158163

164+
#
165+
# Is the given wildcard DNS entry valid?
166+
def valid_rule(rule)
167+
rule =~ /^(\*\.)?([a-z\d][a-z\d-]*[a-z\d]\.)+[a-z]+$/
168+
end
169+
170+
#
171+
# Remove all matching user-configured DNS entries
159172
def remove_dns(*args)
173+
remove_ids = []
174+
@@remove_opts.parse(args) do |opt, idx, val|
175+
case opt
176+
when '-i'
177+
raise ::ArgumentError.new("Not a valid number: #{val}") unless val =~ /^\d+$/
178+
remove_ids << val.to_i
179+
end
180+
end
181+
182+
driver.framework.dns_resolver.remove_ids(remove_ids)
160183
end
161184

185+
#
186+
# Delete all user-configured DNS settings
162187
def purge_dns
163188
driver.framework.dns_resolver.purge
164189
end
165190

191+
#
192+
# Display the user-configured DNS settings
166193
def print_dns
167194
results = driver.framework.dns_resolver.nameserver_entries
168195
columns = ['ID','Rule(s)', 'DNS Server(s)', 'Comm channel']
@@ -175,19 +202,21 @@ def print_dns
175202

176203
private
177204

205+
#
206+
# Get user-friendly text for displaying the session that this entry would go through
178207
def prettify_comm(comm, dns_server)
179208
if comm.nil?
180209
channel = Rex::Socket::SwitchBoard.best_comm(dns_server)
181210
if channel.nil?
182-
comm_text = nil
211+
nil
183212
else
184-
comm_text = "Session #{channel.sid} (auto)"
213+
"Session #{channel.sid} (route)"
185214
end
186215
else
187-
if driver.framework.sessions.include?(comm)
188-
comm_text = "Session #{comm}"
216+
if comm.alive
217+
"Session #{comm.sid}"
189218
else
190-
comm_text = "Broken session (#{comm})"
219+
"Closed session (#{comm.sid})"
191220
end
192221
end
193222
end

lib/rex/proto/dns/custom_nameserver_provider.rb

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ def add_nameserver(wildcard_rules, dns_server, comm)
3333
end
3434
end
3535

36+
#
37+
# Remove entries with the given IDs
38+
# Ignore entries that are not found
39+
def remove_ids(ids)
40+
ids.each do |id|
41+
self.entries_with_rules.delete_if {|entry| entry[:id] == id}
42+
self.entries_without_rules.delete_if {|entry| entry[:id] == id}
43+
end
44+
end
45+
3646
#
3747
# The custom nameserver entries that have been configured
3848
# @return [Array<Array>] An array containing two elements: The entries with rules, and the entries without rules
@@ -45,25 +55,47 @@ def purge
4555
end
4656

4757
def nameservers_for_packet(packet)
48-
name = packet.question.qName
49-
dns_servers = []
58+
# Leaky abstraction: a packet could have multiple question entries,
59+
# and each of these could have different nameservers, or travel via
60+
# different comm channels. We can't allow DNS leaks, so for now, we
61+
# will throw an error here.
62+
results_from_all_questions = []
63+
packet.question.each do |question|
64+
name = question.qname.to_s
65+
dns_servers = []
5066

51-
self.entries_with_rules.each do |entry|
52-
entry[:wildcard_rules].each do |rule|
53-
if matches(name, rule)
54-
dns_servers.concat([entry[:dns_server], entry[:comm]])
55-
break
67+
self.entries_with_rules.each do |entry|
68+
entry[:wildcard_rules].each do |rule|
69+
socket_options = {}
70+
socket_options['Comm'] = entry[:comm] unless entry[:comm].nil?
71+
if matches(name, rule)
72+
dns_servers.append([entry[:dns_server], socket_options])
73+
break
74+
end
75+
end
76+
end
77+
78+
# Only look at the rule-less entries if no rules were found (avoids DNS leaks)
79+
if dns_servers.empty?
80+
self.entries_without_rules.each do |entry|
81+
socket_options = {}
82+
socket_options['Comm'] = entry[:comm] unless entry[:comm].nil?
83+
dns_servers.append([entry[:dns_server], socket_options])
5684
end
5785
end
58-
end
5986

60-
# Only look at the rule-less entries if no rules were found (avoids DNS leaks)
61-
if dns_servers.empty?
62-
self.entries_without_rules.each do |entry|
63-
dns_servers.concat([entry[:dns_server], entry[:comm]])
87+
if dns_servers.empty?
88+
# Fall back to default nameservers
89+
dns_servers = super
6490
end
91+
results_from_all_questions << dns_servers.uniq
92+
end
93+
results_from_all_questions.uniq!
94+
if results_from_all_questions.size != 1
95+
raise ResolverError.new('Inconsistent nameserver entries attempted to be sent in the one packet')
6596
end
66-
dns_servers.uniq!
97+
98+
results_from_all_questions[0]
6799
end
68100

69101
def self.extended(mod)
@@ -73,7 +105,11 @@ def self.extended(mod)
73105
private
74106

75107
def matches(domain, pattern)
76-
true
108+
if pattern.start_with?('*.')
109+
domain.downcase.end_with?(pattern[1..-1].downcase)
110+
else
111+
domain.casecmp?(pattern)
112+
end
77113
end
78114

79115
attr_accessor :entries_with_rules # Set of custom nameserver entries that specify a rule

lib/rex/proto/dns/resolver.rb

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ module DNS
1414
class Resolver < Net::DNS::Resolver
1515

1616
Defaults = {
17-
:config_file => "/dev/null", # default can lead to info leaks
17+
:config_file => "/etc/resolv.conf",
1818
:log_file => "/dev/null", # formerly $stdout, should be tied in with our loggers
1919
:port => 53,
2020
:searchlist => [],
@@ -141,7 +141,8 @@ def send(argument, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)
141141
packet = Rex::Proto::DNS::Packet.encode_drb(net_packet)
142142
end
143143

144-
if nameservers_for_packet(packet).size == 0
144+
nameservers = nameservers_for_packet(packet)
145+
if nameservers.size == 0
145146
raise ResolverError, "No nameservers specified!"
146147
end
147148

@@ -210,6 +211,7 @@ def send_tcp(packet,packet_data,prox = @config[:proxies])
210211
socket = nil
211212
@config[:tcp_timeout].timeout do
212213
catch(:next_ns) do
214+
suffix = ''
213215
begin
214216
config = {
215217
'PeerHost' => ns.to_s,
@@ -219,7 +221,12 @@ def send_tcp(packet,packet_data,prox = @config[:proxies])
219221
'Comm' => @config[:comm]
220222
}
221223
config.update(socket_options)
224+
unless config['Comm'].nil? || config['Comm'].alive?
225+
@logger.warn("Session #{config['Comm'].sid} not active, and cannot be used to resolve DNS")
226+
throw :next_ns
227+
end
222228

229+
suffix = " over session #{@config['Comm'].sid}" unless @config['Comm'].nil?
223230
if @config[:source_port] > 0
224231
config['LocalPort'] = @config[:source_port]
225232
end
@@ -228,11 +235,11 @@ def send_tcp(packet,packet_data,prox = @config[:proxies])
228235
end
229236
socket = Rex::Socket::Tcp.create(config)
230237
rescue
231-
@logger.warn "TCP Socket could not be established to #{ns}:#{@config[:port]} #{@config[:proxies]}"
238+
@logger.warn "TCP Socket could not be established to #{ns}:#{@config[:port]} #{@config[:proxies]}#{suffix}"
232239
throw :next_ns
233240
end
234241
next unless socket #
235-
@logger.info "Contacting nameserver #{ns} port #{@config[:port]}"
242+
@logger.info "Contacting nameserver #{ns} port #{@config[:port]}#{suffix}"
236243
socket.write(length+packet_data)
237244
got_something = false
238245
loop do
@@ -241,15 +248,15 @@ def send_tcp(packet,packet_data,prox = @config[:proxies])
241248
begin
242249
ans = socket.recv(2)
243250
rescue Errno::ECONNRESET
244-
@logger.warn "TCP Socket got Errno::ECONNRESET from #{ns}:#{@config[:port]} #{@config[:proxies]}"
251+
@logger.warn "TCP Socket got Errno::ECONNRESET from #{ns}:#{@config[:port]} #{@config[:proxies]}#{suffix}"
245252
attempts -= 1
246253
retry if attempts > 0
247254
end
248255
if ans.size == 0
249256
if got_something
250257
break #Proper exit from loop
251258
else
252-
@logger.warn "Connection reset to nameserver #{ns}, trying next."
259+
@logger.warn "Connection reset to nameserver #{ns}#{suffix}, trying next."
253260
throw :next_ns
254261
end
255262
end
@@ -259,7 +266,7 @@ def send_tcp(packet,packet_data,prox = @config[:proxies])
259266
@logger.info "Receiving #{len} bytes..."
260267

261268
if len.nil? or len == 0
262-
@logger.warn "Receiving 0 length packet from nameserver #{ns}, trying next."
269+
@logger.warn "Receiving 0 length packet from nameserver #{ns}#{suffix}, trying next."
263270
throw :next_ns
264271
end
265272

@@ -270,7 +277,7 @@ def send_tcp(packet,packet_data,prox = @config[:proxies])
270277
end
271278

272279
unless buffer.size == len
273-
@logger.warn "Malformed packet from nameserver #{ns}, trying next."
280+
@logger.warn "Malformed packet from nameserver #{ns}#{suffix}, trying next."
274281
throw :next_ns
275282
end
276283
if block_given?
@@ -282,7 +289,7 @@ def send_tcp(packet,packet_data,prox = @config[:proxies])
282289
end
283290
end
284291
rescue Timeout::Error
285-
@logger.warn "Nameserver #{ns} not responding within TCP timeout, trying next one"
292+
@logger.warn "Nameserver #{ns}#{suffix} not responding within TCP timeout, trying next one"
286293
next
287294
ensure
288295
socket.close if socket
@@ -313,6 +320,11 @@ def send_udp(packet,packet_data)
313320
'Comm' => @config[:comm]
314321
}
315322
config.update(socket_options)
323+
unless config['Comm'].nil? || config['Comm'].alive?
324+
@logger.warn("Session #{config['Comm'].sid} not active, and cannot be used to resolve DNS")
325+
throw :next_ns
326+
end
327+
316328
if @config[:source_port] > 0
317329
config['LocalPort'] = @config[:source_port]
318330
end

0 commit comments

Comments
 (0)