CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/dns/resolver.rb
Views: 11704
1
# -*- coding: binary -*-
2
3
require 'net/dns/resolver'
4
require 'dnsruby'
5
6
module Rex
7
module Proto
8
module DNS
9
10
##
11
# Provides Rex::Sockets compatible version of Net::DNS::Resolver
12
# Modified to work with Dnsruby::Messages, their resolvers are too heavy
13
##
14
class Resolver < Net::DNS::Resolver
15
16
Defaults = {
17
:config_file => nil,
18
:log_file => File::NULL, # formerly $stdout, should be tied in with our loggers
19
:port => 53,
20
:searchlist => [],
21
:nameservers => [],
22
:domain => "",
23
:source_port => 0,
24
:source_address => IPAddr.new("0.0.0.0"),
25
:retry_interval => 5,
26
:retry_number => 4,
27
:recursive => true,
28
:defname => true,
29
:dns_search => true,
30
:use_tcp => false,
31
:ignore_truncated => false,
32
:packet_size => 512,
33
:tcp_timeout => TcpTimeout.new(5),
34
:udp_timeout => UdpTimeout.new(5),
35
:context => {},
36
:comm => nil,
37
:static_hosts => {}
38
}
39
40
attr_accessor :context, :comm, :static_hostnames
41
#
42
# Provide override for initializer to use local Defaults constant
43
#
44
# @param config [Hash] Configuration options as consumed by parent class
45
def initialize(config = {})
46
raise ResolverArgumentError, "Argument has to be Hash" unless config.kind_of? Hash
47
# config.key_downcase!
48
@config = Defaults.merge config
49
@config[:config_file] ||= self.class.default_config_file
50
@raw = false
51
# New logger facility
52
@logger = Logger.new(@config[:log_file])
53
@logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
54
55
#------------------------------------------------------------
56
# Resolver configuration will be set in order from:
57
# 1) initialize arguments
58
# 2) ENV variables
59
# 3) config file
60
# 4) defaults (and /etc/resolv.conf for config)
61
#------------------------------------------------------------
62
63
#------------------------------------------------------------
64
# Parsing config file
65
#------------------------------------------------------------
66
parse_config_file
67
68
#------------------------------------------------------------
69
# Parsing ENV variables
70
#------------------------------------------------------------
71
parse_environment_variables
72
73
#------------------------------------------------------------
74
# Parsing arguments
75
#------------------------------------------------------------
76
comm = config.delete(:comm)
77
context = config.delete(:context)
78
static_hosts = config.delete(:static_hosts)
79
config.each do |key,val|
80
next if key == :log_file or key == :config_file
81
begin
82
eval "self.#{key.to_s} = val"
83
rescue NoMethodError
84
raise ResolverArgumentError, "Option #{key} not valid"
85
end
86
end
87
88
self.static_hostnames = StaticHostnames.new(hostnames: static_hosts)
89
begin
90
self.static_hostnames.parse_hosts_file
91
rescue StandardError => e
92
@logger.error 'Failed to parse the hosts file, ignoring it'
93
# if the hosts file is corrupted, just use a default instance with any specified hostnames
94
self.static_hostnames = StaticHostnames.new(hostnames: static_hosts)
95
end
96
end
97
#
98
# Provides current proxy setting if configured
99
#
100
# @return [String] Current proxy configuration
101
def proxies
102
@config[:proxies].inspect if @config[:proxies]
103
end
104
105
#
106
# Configure proxy setting and additional timeout
107
#
108
# @param prox [String] SOCKS proxy connection string
109
# @param timeout_added [Fixnum] Added TCP timeout to account for proxy
110
def proxies=(prox, timeout_added = 250)
111
return if prox.nil?
112
if prox.is_a?(String) and prox.strip =~ /^socks/i
113
@config[:proxies] = prox.strip
114
@config[:use_tcp] = true
115
self.tcp_timeout = self.tcp_timeout.to_s.to_i + timeout_added
116
@logger.info "SOCKS proxy set, using TCP, increasing timeout"
117
else
118
raise ResolverError, "Only socks proxies supported"
119
end
120
end
121
122
#
123
# Find the nameservers to use for a given DNS request
124
# @param _dns_message [Dnsruby::Message] The DNS message to be sent
125
#
126
# @return [Array<Array>] A list of nameservers, each with Rex::Socket options
127
#
128
def upstream_resolvers_for_packet(_dns_message)
129
@config[:nameservers].map do |ns|
130
UpstreamResolver.create_dns_server(ns.to_s)
131
end
132
end
133
134
def upstream_resolvers_for_query(name, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)
135
name, type, cls = preprocess_query_arguments(name, type, cls)
136
net_packet = make_query_packet(name, type, cls)
137
# This returns a Net::DNS::Packet. Convert to Dnsruby::Message for consistency
138
packet = Rex::Proto::DNS::Packet.encode_drb(net_packet)
139
upstream_resolvers_for_packet(packet)
140
end
141
142
#
143
# Send DNS request over appropriate transport and process response
144
#
145
# @param argument [Object] An object holding the DNS message to be processed.
146
# @param type [Fixnum] Type of record to look up
147
# @param cls [Fixnum] Class of question to look up
148
# @return [Dnsruby::Message] DNS response
149
#
150
def send(argument, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)
151
case argument
152
when Dnsruby::Message
153
packet = argument
154
when Net::DNS::Packet, Resolv::DNS::Message
155
packet = Rex::Proto::DNS::Packet.encode_drb(argument)
156
else
157
net_packet = make_query_packet(argument,type,cls)
158
# This returns a Net::DNS::Packet. Convert to Dnsruby::Message for consistency
159
packet = Rex::Proto::DNS::Packet.encode_drb(net_packet)
160
end
161
162
163
upstream_resolvers = upstream_resolvers_for_packet(packet)
164
if upstream_resolvers.empty?
165
raise ResolverError, "No upstream resolvers specified!"
166
end
167
168
ans = nil
169
upstream_resolvers.each do |upstream_resolver|
170
case upstream_resolver.type
171
when UpstreamResolver::Type::BLACK_HOLE
172
ans = resolve_via_black_hole(upstream_resolver, packet, type, cls)
173
when UpstreamResolver::Type::DNS_SERVER
174
ans = resolve_via_dns_server(upstream_resolver, packet, type, cls)
175
when UpstreamResolver::Type::STATIC
176
ans = resolve_via_static(upstream_resolver, packet, type, cls)
177
when UpstreamResolver::Type::SYSTEM
178
ans = resolve_via_system(upstream_resolver, packet, type, cls)
179
end
180
181
break if (ans and ans[0].length > 0)
182
end
183
184
unless (ans and ans[0].length > 0)
185
@logger.fatal "No response from upstream resolvers: aborting"
186
raise NoResponseError
187
end
188
189
# response = Net::DNS::Packet.parse(ans[0],ans[1])
190
response = Dnsruby::Message.decode(ans[0])
191
192
if response.header.tc and not ignore_truncated?
193
@logger.warn "Packet truncated, retrying using TCP"
194
self.use_tcp = true
195
begin
196
return send(argument,type,cls)
197
ensure
198
self.use_tcp = false
199
end
200
end
201
202
response
203
end
204
205
#
206
# Send request over TCP
207
#
208
# @param packet [Net::DNS::Packet] Packet associated with packet_data
209
# @param packet_data [String] Data segment of DNS request packet
210
# @param nameservers [Array<[String,Hash]>] List of nameservers to use for this request, and their associated socket options
211
# @param prox [String] Proxy configuration for TCP socket
212
#
213
# @return ans [String] Raw DNS reply
214
def send_tcp(packet, packet_data, nameservers, prox = @config[:proxies])
215
ans = nil
216
length = [packet_data.size].pack("n")
217
nameservers.each do |ns, socket_options|
218
socket = nil
219
config = {
220
'PeerHost' => ns.to_s,
221
'PeerPort' => @config[:port].to_i,
222
'Proxies' => prox,
223
'Context' => @config[:context],
224
'Comm' => @config[:comm],
225
'Timeout' => @config[:tcp_timeout]
226
}
227
config.update(socket_options)
228
unless config['Comm'].nil? || config['Comm'].alive?
229
@logger.warn("Session #{config['Comm'].sid} not active, and cannot be used to resolve DNS")
230
next
231
end
232
233
suffix = " over session #{@config['Comm'].sid}" unless @config['Comm'].nil?
234
if @config[:source_port] > 0
235
config['LocalPort'] = @config[:source_port]
236
end
237
if @config[:source_host].to_s != '0.0.0.0'
238
config['LocalHost'] = @config[:source_host] unless @config[:source_host].nil?
239
end
240
begin
241
suffix = ''
242
begin
243
socket = Rex::Socket::Tcp.create(config)
244
rescue
245
@logger.warn "TCP Socket could not be established to #{ns}:#{@config[:port]} #{@config[:proxies]}#{suffix}"
246
next
247
end
248
next unless socket #
249
@logger.info "Contacting nameserver #{ns} port #{@config[:port]}#{suffix}"
250
socket.write(length+packet_data)
251
got_something = false
252
loop do
253
buffer = ""
254
attempts = 3
255
begin
256
ans = socket.recv(2)
257
rescue Errno::ECONNRESET
258
@logger.warn "TCP Socket got Errno::ECONNRESET from #{ns}:#{@config[:port]} #{@config[:proxies]}#{suffix}"
259
attempts -= 1
260
retry if attempts > 0
261
end
262
if ans.size == 0
263
if got_something
264
break #Proper exit from loop
265
else
266
@logger.warn "Connection reset to nameserver #{ns}#{suffix}, trying next."
267
throw :next_ns
268
end
269
end
270
got_something = true
271
len = ans.unpack("n")[0]
272
273
@logger.info "Receiving #{len} bytes..."
274
275
if len.nil? or len == 0
276
@logger.warn "Receiving 0 length packet from nameserver #{ns}#{suffix}, trying next."
277
throw :next_ns
278
end
279
280
while (buffer.size < len)
281
left = len - buffer.size
282
temp,from = socket.recvfrom(left)
283
buffer += temp
284
end
285
286
unless buffer.size == len
287
@logger.warn "Malformed packet from nameserver #{ns}#{suffix}, trying next."
288
throw :next_ns
289
end
290
if block_given?
291
yield [buffer,["",@config[:port],ns.to_s,ns.to_s]]
292
else
293
return [buffer,["",@config[:port],ns.to_s,ns.to_s]]
294
end
295
end
296
rescue Timeout::Error
297
@logger.warn "Nameserver #{ns}#{suffix} not responding within TCP timeout, trying next one"
298
next
299
ensure
300
socket.close if socket
301
end
302
end
303
return nil
304
end
305
306
#
307
# Send request over UDP
308
#
309
# @param packet [Net::DNS::Packet] Packet associated with packet_data
310
# @param packet_data [String] Data segment of DNS request packet
311
# @param nameservers [Array<[String,Hash]>] List of nameservers to use for this request, and their associated socket options
312
#
313
# @return ans [String] Raw DNS reply
314
def send_udp(packet,packet_data, nameservers)
315
ans = nil
316
nameservers.each do |ns, socket_options|
317
begin
318
config = {
319
'PeerHost' => ns.to_s,
320
'PeerPort' => @config[:port].to_i,
321
'Context' => @config[:context],
322
'Comm' => @config[:comm],
323
'Timeout' => @config[:udp_timeout]
324
}
325
config.update(socket_options)
326
unless config['Comm'].nil? || config['Comm'].alive?
327
@logger.warn("Session #{config['Comm'].sid} not active, and cannot be used to resolve DNS")
328
next
329
end
330
331
if @config[:source_port] > 0
332
config['LocalPort'] = @config[:source_port]
333
end
334
if @config[:source_host] != IPAddr.new('0.0.0.0')
335
config['LocalHost'] = @config[:source_host] unless @config[:source_host].nil?
336
end
337
socket = Rex::Socket::Udp.create(config)
338
rescue
339
@logger.warn "UDP Socket could not be established to #{ns}:#{@config[:port]}"
340
next
341
end
342
@logger.info "Contacting nameserver #{ns} port #{@config[:port]}"
343
#socket.sendto(packet_data, ns.to_s, @config[:port].to_i, 0)
344
socket.write(packet_data)
345
ans = socket.recvfrom(@config[:packet_size])
346
break if ans
347
rescue Timeout::Error
348
@logger.warn "Nameserver #{ns} not responding within UDP timeout, trying next one"
349
next
350
end
351
ans
352
end
353
354
355
#
356
# Perform search using the configured searchlist and resolvers
357
#
358
# @param name
359
# @param type [Fixnum] Type of record to look up
360
# @param cls [Fixnum] Class of question to look up
361
#
362
# @return ans [Dnsruby::Message] DNS Response
363
def search(name, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)
364
return query(name,type,cls) if name.class == IPAddr
365
# If the name contains at least one dot then try it as is first.
366
if name.include? "."
367
@logger.debug "Search(#{name},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"
368
ans = query(name,type,cls)
369
return ans if ans.header.ancount > 0
370
end
371
# If the name doesn't end in a dot then apply the search list.
372
if name !~ /\.$/ and @config[:dns_search]
373
@config[:searchlist].each do |domain|
374
newname = name + "." + domain
375
@logger.debug "Search(#{newname},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"
376
ans = query(newname,type,cls)
377
return ans if ans.header.ancount > 0
378
end
379
end
380
# Finally, if the name has no dots then try it as is.
381
@logger.debug "Search(#{name},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"
382
return query(name+".",type,cls)
383
end
384
385
#
386
# Perform query with default domain validation
387
#
388
# @param name
389
# @param type [Fixnum] Type of record to look up
390
# @param cls [Fixnum] Class of question to look up
391
#
392
# @return ans [Dnsruby::Message] DNS Response
393
def query(name, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)
394
name, type, cls = preprocess_query_arguments(name, type, cls)
395
@logger.debug "Query(#{name},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"
396
send(name,type,cls)
397
end
398
399
def self.default_config_file
400
%w[
401
/etc/resolv.conf
402
/data/data/com.termux/files/usr/etc/resolv.conf
403
].find do |path|
404
File.file?(path) && File.readable?(path)
405
end
406
end
407
408
private
409
410
def preprocess_query_arguments(name, type, cls)
411
return [name, type, cls] if name.class == IPAddr
412
413
# If the name doesn't contain any dots then append the default domain.
414
if name !~ /\./ and name !~ /:/ and @config[:defname]
415
name += "." + @config[:domain]
416
end
417
[name, type, cls]
418
end
419
420
def resolve_via_dns_server(upstream_resolver, packet, type, _cls)
421
method = self.use_tcp? ? :send_tcp : :send_udp
422
423
# Store packet_data for performance improvements,
424
# so methods don't keep on calling Packet#encode
425
packet_data = packet.encode
426
packet_size = packet_data.size
427
428
# Choose whether use TCP, UDP
429
if packet_size > @config[:packet_size] # Must use TCP
430
@logger.info "Sending #{packet_size} bytes using TCP due to size"
431
method = :send_tcp
432
else # Packet size is inside the boundaries
433
if use_tcp? or !(proxies.nil? or proxies.empty?) # User requested TCP
434
@logger.info "Sending #{packet_size} bytes using TCP due to tcp flag"
435
method = :send_tcp
436
elsif !supports_udp?(upstream_resolver)
437
@logger.info "Sending #{packet_size} bytes using TCP due to the presence of a non-UDP-compatible comm channel"
438
method = :send_tcp
439
else # Finally use UDP
440
@logger.info "Sending #{packet_size} bytes using UDP"
441
method = :send_udp unless method == :send_tcp
442
end
443
end
444
445
if type == Dnsruby::Types::AXFR
446
@logger.warn "AXFR query, switching to TCP" unless method == :send_tcp
447
method = :send_tcp
448
end
449
450
nameserver = [upstream_resolver.destination, upstream_resolver.socket_options]
451
ans = self.__send__(method, packet, packet_data, [nameserver])
452
453
if (ans and ans[0].length > 0)
454
@logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}"
455
end
456
457
ans
458
end
459
460
def resolve_via_black_hole(upstream_resolver, packet, type, cls)
461
# do not just return nil because that will cause the next resolver to be used
462
@logger.info "No response from upstream resolvers: black-hole"
463
raise NoResponseError
464
end
465
466
def resolve_via_static(upstream_resolver, packet, type, cls)
467
simple_name_lookup(upstream_resolver, packet, type, cls) do |name, _family|
468
static_hostnames.get(name, type)
469
end
470
end
471
472
def resolve_via_system(upstream_resolver, packet, type, cls)
473
# This system resolver will use host operating systems `getaddrinfo` (or equivalent function) to perform name
474
# resolution. This is primarily useful if that functionality is hooked or modified by an external application such
475
# as proxychains. This handler though can only process A and AAAA requests.
476
simple_name_lookup(upstream_resolver, packet, type, cls) do |name, family|
477
addrinfos = ::Addrinfo.getaddrinfo(name, 0, family, ::Socket::SOCK_STREAM)
478
addrinfos.map(&:ip_address)
479
end
480
end
481
482
def simple_name_lookup(upstream_resolver, packet, type, cls, &block)
483
return nil unless cls == Dnsruby::Classes::IN
484
485
# todo: make sure this will work if the packet has multiple questions, figure out how that's handled
486
name = packet.question.first.qname.to_s
487
case type
488
when Dnsruby::Types::A
489
family = ::Socket::AF_INET
490
when Dnsruby::Types::AAAA
491
family = ::Socket::AF_INET6
492
else
493
return nil
494
end
495
496
ip_addresses = nil
497
begin
498
ip_addresses = block.call(name, family)
499
rescue StandardError => e
500
@logger.error("The #{upstream_resolver.type} name lookup block failed for #{name}")
501
end
502
return nil unless ip_addresses && !ip_addresses.empty?
503
504
message = Dnsruby::Message.new
505
message.add_question(name, type, cls)
506
ip_addresses.each do |ip_address|
507
message.add_answer(Dnsruby::RR.new_from_hash(
508
name: name,
509
type: type,
510
ttl: 0,
511
address: ip_address.to_s
512
))
513
end
514
[message.encode]
515
end
516
517
def supports_udp?(upstream_resolver)
518
return false unless upstream_resolver.type == UpstreamResolver::Type::DNS_SERVER
519
520
comm = upstream_resolver.socket_options.fetch('Comm') { @config[:comm] || Rex::Socket::SwitchBoard.best_comm(upstream_resolver.destination) }
521
return false if comm && !comm.supports_udp?
522
523
true
524
end
525
end # Resolver
526
527
end
528
end
529
end
530
531