Skip to content
This repository

Dns enum threaded bf #852

Closed
wants to merge 11 commits into from

8 participants

RageLtMan Brandon Perry wvu-r7 Tod Beardsley Carlos Perez Rob Fuller Juan Vazquez Tab Assassin
RageLtMan

Allow threading DNS brute force attempts, logging found hosts, and ignoring wildcard results in brute force.

RageLtMan added some commits
RageLtMan Add multi-threaded bruteforce mode and reporting
This commit allows splitting the DNS brute force process into
separate threads each running against a segment of the wordlist.
The wildcard method was modified to return the wildcard address if
one is found in order to exclude it from the brute force results.
Lastly, hosts found via brute force can be reported as new targets.
3ac6f66
RageLtMan clean up threaded bf and rvl 155d01d
RageLtMan Merge branch 'master' of https://github.com/rapid7/metasploit-framework
… into dns_enum_threaded_bf
a29df25
RageLtMan Only perform reverse lookups against DB hosts
This commit adds a boolean option to limit reverse lookups to
existing hosts. Reverse lookups are threaded anyway, and this
helps all the more with quick name lookups for known IPs.

Testing: populate DB with some hosts, run reverse lookup with
IPRANGE set to 0.0.0.0/0.
1160591
RageLtMan Module cleanup and additions
Allow reporting of found A records
Proper handling of brute forced domains (AAAA converted to A)
RVL call cleanup
95627d1
RageLtMan add dns enum rc script 455d38b
RageLtMan dns_enum rc fixup 6c840f9
RageLtMan chomp proper "." 36a1e8b
Brandon Perry

Hey, I know this has been open a while. I will test this this weekend. Sorry for the delay.

wvu-r7
Collaborator
wvu-r7 commented

@brandonprry: You gonna test this? :P

Tod Beardsley todb-r7 referenced this pull request in sempervictus/metasploit-framework
Merged

Retab/pr/852 #17

Juan Vazquez jvazquez-r7 commented on the diff
modules/auxiliary/gather/enum_dns.rb
@@ -542,5 +636,10 @@ def run
542 636 if(datastore['ENUM_RVL'] and datastore['IPRANGE'] and not datastore['IPRANGE'].empty?)
543 637 reverselkp(datastore['IPRANGE'],datastore['NS'])
544 638 end
  639 + # Do not let module finish while threads exist
  640 + while not @dns_enum_threads.empty? do
  641 + vprint_status("Waiting on #{@dns_enum_threads.length} threads to finish")
  642 + Rex::ThreadSafe.sleep(10)
2
Juan Vazquez Collaborator

oumgh really :? isn't there any other way of synchronization?

Juan Vazquez Collaborator

just asking

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Juan Vazquez jvazquez-r7 commented on the diff
modules/auxiliary/gather/enum_dns.rb
((31 lines not shown))
  249 + #-------------------------------------------------------------------------------
  250 + def dnsbrute(target, wordlist, nssrv, wldcrd = nil)
  251 + print_status("Bruteforcing IPv4 addresses against domain #{target}")
  252 + # Read words, split list for threaded bruteforce
  253 + words = split_wordlist(
  254 + ::File.read(wordlist).split("\n").uniq.map {|w| w.gsub(".#{target}", '')}
  255 + )
  256 + # chunk_size = words.length/@threadnum
  257 + # words = words.each_slice(chunk_size).to_a
  258 + print_status("Running with #{words.flatten.length} words in #{@threadnum} threads of #{words.first.length} each")
  259 + print_good("Ignoring #{wldcrd}") if wldcrd
  260 + # Give each thread a chunk of words to test
  261 + while !words.empty? do
  262 + if @dns_enum_threads.length < @threadnum
  263 + chunk = words.pop
  264 + @dns_enum_threads << Rex::ThreadFactory.spawn("DNS Brute Force #{target} #{@dns_enum_threads.length + 1}", false) do
1
Juan Vazquez Collaborator

I've the feeling these threads should be handled from the framework instance's thread manager

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Juan Vazquez jvazquez-r7 commented on the diff
modules/auxiliary/gather/enum_dns.rb
((77 lines not shown))
249 295 end
250 296 end
  297 + else
  298 + Rex::ThreadSafe.sleep(5)
1
Juan Vazquez Collaborator

really, why :? if it is for sync purposes, can't it be done in another way?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Juan Vazquez jvazquez-r7 commented on the diff
modules/auxiliary/gather/enum_dns.rb
((17 lines not shown))
240   - print_status("Hostname: #{line.chomp}.#{target} IP address: #{rr.address.to_s}")
241   - report_note(:host => @nsinuse.to_s,
242   - :proto => 'udp',
243   - :sname => 'dns',
244   - :port => 53 ,
245   - :type => 'dns.enum',
246   - :update => :unique_data,
247   - :data => "#{rr.address.to_s},#{line.chomp}.#{target},A")
248   - next unless rr.class == Net::DNS::RR::CNAME
  244 + def split_wordlist(words)
  245 + length = (words.length / @threadnum).ceil
  246 + words = words.each_slice(length).to_a
  247 + end
  248 +
  249 + #-------------------------------------------------------------------------------
  250 + def dnsbrute(target, wordlist, nssrv, wldcrd = nil)
1
Juan Vazquez Collaborator

Honestly, if code complexity could be reduced on dnsbrute and bruteipv6 it would help review a lot! Indeed would help a lot with code readability :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
RageLtMan

@jvazquez-r7:
Msf overrides some of Rex' internals when it boots, see https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/framework.rb#L91.
https://github.com/rapid7/metasploit-framework/blob/master/lib/rex.rb#L97 covers the sleep bit as well (see below for select). Overloading those at the Kernel makes msf look a lot more threadsafe than it is, and with the GIL it works the vast majority of the time.
Will try to clean up redundant code, but from a functional standpoint i use this daily so it does work...

Tod Beardsley
Owner

So, since this is the oldest living PR, I'd love to save it. Looks like @jvazquez-r7 had a bunch of comments, @sempervictus do you plan to address? If not we can shuffle this off to the unstable retirement home.

Carlos Perez

in fact I migrated all functionality to individual modules except the AXFR function by request of Jcran back then. This module can be retired, only thing missing is AXFR as a individual module.

Tod Beardsley
Owner

Retiring per request.

Tod Beardsley todb-r7 closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 11 unique commits by 3 authors.

Oct 02, 2012
RageLtMan Add multi-threaded bruteforce mode and reporting
This commit allows splitting the DNS brute force process into
separate threads each running against a segment of the wordlist.
The wildcard method was modified to return the wildcard address if
one is found in order to exclude it from the brute force results.
Lastly, hosts found via brute force can be reported as new targets.
3ac6f66
Oct 03, 2012
RageLtMan clean up threaded bf and rvl 155d01d
RageLtMan Merge branch 'master' of https://github.com/rapid7/metasploit-framework
… into dns_enum_threaded_bf
a29df25
Oct 04, 2012
RageLtMan Only perform reverse lookups against DB hosts
This commit adds a boolean option to limit reverse lookups to
existing hosts. Reverse lookups are threaded anyway, and this
helps all the more with quick name lookups for known IPs.

Testing: populate DB with some hosts, run reverse lookup with
IPRANGE set to 0.0.0.0/0.
1160591
Oct 08, 2012
RageLtMan Module cleanup and additions
Allow reporting of found A records
Proper handling of brute forced domains (AAAA converted to A)
RVL call cleanup
95627d1
Oct 09, 2012
RageLtMan add dns enum rc script 455d38b
RageLtMan dns_enum rc fixup 6c840f9
RageLtMan chomp proper "." 36a1e8b
Sep 05, 2013
Tab Assassin tabassassin Merge for retab 9386e72
Tab Assassin tabassassin Retab changes for PR #852 d19de1f
Sep 14, 2013
RageLtMan sempervictus Merge pull request #17 from tabassassin/retab/pr/852
Retab/pr/852
9067db1
This page is out of date. Refresh to see the latest.
219 modules/auxiliary/gather/enum_dns.rb
@@ -32,7 +32,7 @@ def initialize(info = {})
32 32 register_options(
33 33 [
34 34 OptString.new('DOMAIN', [ true, "The target domain name"]),
35   - OptBool.new('ENUM_AXFR', [ true, 'Initiate a zone transfer against each NS record', true]),
  35 + OptBool.new('ENUM_AXFR', [ true, 'Initiate a zone transfer against each NS record', false]),
36 36 OptBool.new('ENUM_TLD', [ true, 'Perform a TLD expansion by replacing the TLD with the IANA TLD list', false]),
37 37 OptBool.new('ENUM_STD', [ true, 'Enumerate standard record types (A,MX,NS,TXT and SOA)', true]),
38 38 OptBool.new('ENUM_BRT', [ true, 'Brute force subdomains and hostnames via the supplied wordlist', false]),
@@ -49,6 +49,9 @@ def initialize(info = {})
49 49 [
50 50 OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received", 2]),
51 51 OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry", 2]),
  52 + OptInt.new('THREADS', [ true, "Number of threads to use for BRT and RVL", 1]),
  53 + OptBool.new('REPORT_A_RECORDS', [false, "Add hosts found via BRT and RVL to DB", true]),
  54 + OptBool.new('RVL_EXISTING_ONLY', [false, "Only perform lookups on hosts in DB", false]),
52 55 OptBool.new('TCP_DNS', [false, "Run queries over TCP", false]),
53 56 ], self.class)
54 57 end
@@ -75,17 +78,31 @@ def switchdns(target)
75 78 end
76 79 #---------------------------------------------------------------------------------
77 80 def wildcard(target)
  81 + addr = false
78 82 rendsub = rand(10000).to_s
79 83 query = @res.query("#{rendsub}.#{target}", "A")
80 84 if query.answer.length != 0
81 85 print_status("This domain has wildcards enabled!!")
82 86 query.answer.each do |rr|
83 87 print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME
  88 + addr = rr.address.to_s
84 89 end
85   - return true
86   - else
87   - return false
88 90 end
  91 + return addr
  92 + end
  93 +
  94 + def wildcard6(target)
  95 + addr = false
  96 + rendsub = rand(10000).to_s
  97 + query = @res.query("#{rendsub}.#{target}", "AAAA")
  98 + if query.answer.length != 0
  99 + print_status("This domain has wildcards enabled!!")
  100 + query.answer.each do |rr|
  101 + print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME
  102 + addr = rr.address.to_s
  103 + end
  104 + end
  105 + return addr
89 106 end
90 107 #---------------------------------------------------------------------------------
91 108 def genrcd(target)
@@ -224,62 +241,113 @@ def tldexpnd(targetdom,nssrv)
224 241 end
225 242
226 243 #-------------------------------------------------------------------------------
227   - def dnsbrute(target, wordlist, nssrv)
228   - print_status("Running bruteforce against domain #{target}")
229   - arr = []
230   - i, a = 0, []
231   - ::File.open(wordlist, "rb").each_line do |line|
232   - if not nssrv.nil?
233   - @res.nameserver=(nssrv)
234   - @nsinuse = nssrv
235   - end
236   - query1 = @res.search("#{line.chomp}.#{target}")
237   - if (query1)
238   - query1.answer.each do |rr|
239   - if rr.class == Net::DNS::RR::A
240   - print_status("Hostname: #{line.chomp}.#{target} IP address: #{rr.address.to_s}")
241   - report_note(:host => @nsinuse.to_s,
242   - :proto => 'udp',
243   - :sname => 'dns',
244   - :port => 53 ,
245   - :type => 'dns.enum',
246   - :update => :unique_data,
247   - :data => "#{rr.address.to_s},#{line.chomp}.#{target},A")
248   - next unless rr.class == Net::DNS::RR::CNAME
  244 + def split_wordlist(words)
  245 + length = (words.length / @threadnum).ceil
  246 + words = words.each_slice(length).to_a
  247 + end
  248 +
  249 + #-------------------------------------------------------------------------------
  250 + def dnsbrute(target, wordlist, nssrv, wldcrd = nil)
  251 + print_status("Bruteforcing IPv4 addresses against domain #{target}")
  252 + # Read words, split list for threaded bruteforce
  253 + words = split_wordlist(
  254 + ::File.read(wordlist).split("\n").uniq.map {|w| w.gsub(".#{target}", '')}
  255 + )
  256 + # chunk_size = words.length/@threadnum
  257 + # words = words.each_slice(chunk_size).to_a
  258 + print_status("Running with #{words.flatten.length} words in #{@threadnum} threads of #{words.first.length} each")
  259 + print_good("Ignoring #{wldcrd}") if wldcrd
  260 + # Give each thread a chunk of words to test
  261 + while !words.empty? do
  262 + if @dns_enum_threads.length < @threadnum
  263 + chunk = words.pop
  264 + @dns_enum_threads << Rex::ThreadFactory.spawn("DNS Brute Force #{target} #{@dns_enum_threads.length + 1}", false) do
  265 + chunk.each do |line|
  266 + if not nssrv.nil?
  267 + @res.nameserver=(nssrv)
  268 + @nsinuse = nssrv
  269 + end
  270 + query1 = @res.search("#{line.chomp}.#{target}")
  271 + if (query1)
  272 + query1.answer.each do |rr|
  273 + if rr.class == Net::DNS::RR::A and rr.address.to_s != wldcrd
  274 + print_status("Hostname: #{line.chomp}.#{target} IPv4 address: #{rr.address.to_s}")
  275 + report_note(:host => @nsinuse.to_s,
  276 +
  277 + :proto => @res.use_tcp? ? 'tcp' : 'udp',
  278 + :sname => 'dns',
  279 + :port => 53,
  280 + :type => 'dns.enum',
  281 + :update => :unique_data,
  282 + :data => "#{rr.address.to_s},#{line.chomp}.#{target},A")
  283 + if datastore['REPORT_A_RECORDS']
  284 + report_host(
  285 + :host => rr.address.to_s,
  286 + :name => "#{line.chomp}.#{target}".chomp('.'),
  287 + :update => :unique_data,
  288 + :state => Msf::HostState::Alive
  289 + )
  290 + end
  291 + next unless rr.class == Net::DNS::RR::CNAME
  292 + end
  293 + end
  294 + end
249 295 end
250 296 end
  297 + else
  298 + Rex::ThreadSafe.sleep(5)
251 299 end
252 300 end
253 301 end
254 302
255 303 #-------------------------------------------------------------------------------
256   - def bruteipv6(target, wordlist, nssrv)
  304 + def bruteipv6(target, wordlist, nssrv, wldcrd = nil)
257 305 print_status("Bruteforcing IPv6 addresses against domain #{target}")
258   - arr = []
259   - i, a = 0, []
260   - arr = IO.readlines(wordlist)
261   - if not nssrv.nil?
262   - @res.nameserver=(nssrv)
263   - @nsinuse = nssrv
264   - end
265   - arr.each do |line|
266   - query1 = @res.search("#{line.chomp}.#{target}", "AAAA")
267   - if (query1)
268   - query1.answer.each do |rr|
269   - if rr.class == Net::DNS::RR::AAAA
270   - print_status("Hostname: #{line.chomp}.#{target} IPv6 Address: #{rr.address.to_s}")
271   - report_note(:host => @nsinuse.to_s,
272   - :proto => 'udp',
273   - :sname => 'dns',
274   - :port => 53 ,
275   - :type => 'dns.enum',
276   - :update => :unique_data,
277   - :data => "#{rr.address.to_s},#{line.chomp}.#{target},AAAA")
278   - next unless rr.class == Net::DNS::RR::CNAME
  306 + # Read words, split list for threaded bruteforce
  307 + words = ::File.read(wordlist).split("\n").uniq.map {|w| w.gsub(".#{target}", '')}
  308 + chunk_size = words.length/@threadnum
  309 + words = words.each_slice(chunk_size).to_a
  310 + print_status("Running with #{words.flatten.length} words in #{@threadnum} threads of #{chunk_size} each")
  311 + print_good("Ignoring #{wldcrd}") if wldcrd
  312 + # Give each thread a chunk of words to test
  313 + while !words.empty? do
  314 + if @dns_enum_threads.length < @threadnum
  315 + chunk = words.pop
  316 + @dns_enum_threads << Rex::ThreadFactory.spawn("DNS Brute Force #{target} #{@dns_enum_threads.length + 1}", false) do
  317 + chunk.each do |line|
  318 + if not nssrv.nil?
  319 + @res.nameserver=(nssrv)
  320 + @nsinuse = nssrv
  321 + end
  322 + query1 = @res.search("#{line.chomp}.#{target}", "AAAA")
  323 + if (query1)
  324 + query1.answer.each do |rr|
  325 + if rr.class == Net::DNS::RR::AAAA and rr.address.to_s != wldcrd
  326 + print_status("Hostname: #{line.chomp}.#{target} IPv6 address: #{rr.address.to_s}")
  327 + report_note(:host => @nsinuse.to_s,
  328 + :proto => @res.use_tcp? ? 'tcp' : 'udp',
  329 + :sname => 'dns',
  330 + :port => 53,
  331 + :type => 'dns.enum',
  332 + :update => :unique_data,
  333 + :data => "#{rr.address.to_s},#{line.chomp}.#{target},AAAA")
  334 + if datastore['REPORT_A_RECORDS']
  335 + report_host(
  336 + :host => rr.address.to_s,
  337 + :name => "#{line.chomp}.#{target}".chomp('.'),
  338 + :update => :unique_data,
  339 + :state => Msf::HostState::Alive
  340 + )
  341 + end
  342 + next unless rr.class == Net::DNS::RR::CNAME
  343 + end
  344 + end
  345 + end
279 346 end
280 347 end
  348 + else
  349 + Rex::ThreadSafe.sleep(5)
281 350 end
282   -
283 351 end
284 352 end
285 353
@@ -292,25 +360,42 @@ def reverselkp(iprange,nssrv)
292 360 @res.nameserver = (nssrv)
293 361 @nsinuse = nssrv
294 362 end
295   - ar = Rex::Socket::RangeWalker.new(iprange)
296   - tl = []
  363 + if datastore['RVL_EXISTING_ONLY']
  364 + rng = Rex::Socket::RangeWalker.new(iprange)
  365 + ws = Mdm::Workspace.where(:name => self.workspace).first
  366 + lookup_hosts = ws.hosts.map(&:address).keep_if do |addr|
  367 + rng.include?(addr)
  368 + end
  369 + ar = Rex::Socket::RangeWalker.new(lookup_hosts)
  370 + else
  371 + ar = Rex::Socket::RangeWalker.new(iprange)
  372 + end
297 373 while (true)
298 374 # Spawn threads for each host
299   - while (tl.length < @threadnum)
  375 + while (@dns_enum_threads.length < @threadnum)
300 376 ip = ar.next_ip
301 377 break if not ip
302   - tl << framework.threads.spawn("Module(#{self.refname})-#{ip}", false, ip.dup) do |tip|
  378 + @dns_enum_threads << framework.threads.spawn("Module(#{self.refname})-#{ip} RVL", false, ip.dup) do |tip|
303 379 begin
304 380 query = @res.query(tip)
305 381 query.each_ptr do |addresstp|
306 382 print_status("Hostname: #{addresstp} IP address: #{tip.to_s}")
307   - report_note(:host => @nsinuse.to_s,
  383 + report_note(
  384 + :host => @nsinuse.to_s,
308 385 :proto => 'udp',
309 386 :sname => 'dns',
310 387 :port => 53 ,
311 388 :type => 'dns.enum',
312 389 :update => :unique_data,
313   - :data => "#{addresstp},#{tip},A")
  390 + :data => "#{addresstp},#{tip},A,RVL"
  391 + )
  392 + if datastore['REPORT_A_RECORDS']
  393 + report_host(
  394 + :host => tip.to_s,
  395 + :name => "#{addresstp}".chomp('.'),
  396 + :update => :unique_data,
  397 + )
  398 + end
314 399 end
315 400 rescue ::Interrupt
316 401 raise $!
@@ -322,11 +407,11 @@ def reverselkp(iprange,nssrv)
322 407 end
323 408 end
324 409 # Exit once we run out of hosts
325   - if(tl.length == 0)
  410 + if(@dns_enum_threads.length == 0)
326 411 break
327 412 end
328   - tl.first.join
329   - tl.delete_if { |t| not t.alive? }
  413 + @dns_enum_threads.first.join
  414 + @dns_enum_threads.delete_if { |t| not t.alive? }
330 415 end
331 416 end
332 417 #-------------------------------------------------------------------------------
@@ -496,7 +581,15 @@ def axfr(target, nssrv)
496 581 end
497 582
498 583 def run
  584 + @dns_enum_threads = []
499 585 @res = Net::DNS::Resolver.new()
  586 + # The following line requires rex_dns.rb so checking for poxies
  587 + if @res.methods.include?(:proxies)
  588 + @res.proxies=datastore['Proxies'] if datastore['Proxies']
  589 + end
  590 + # Prevent us from using system DNS by default - net/dns pulls OS settings
  591 + @res.nameservers = datastore['NS'].split(/\s|,/) if datastore['NS']
  592 + # If querying over TCP
500 593 if datastore['TCP_DNS']
501 594 vprint_status("Using DNS/TCP")
502 595 @res.use_tcp = true
@@ -505,6 +598,7 @@ def run
505 598 @res.retry_interval = datastore['RETRY_INTERVAL'].to_i
506 599 @threadnum = datastore['THREADS'].to_i
507 600 wldcrd = wildcard(datastore['DOMAIN'])
  601 + wldcrd6 = wildcard6(datastore['DOMAIN'])
508 602 switchdns(datastore['DOMAIN'])
509 603
510 604 if(datastore['ENUM_STD'])
@@ -519,7 +613,7 @@ def run
519 613 if wldcrd and datastore['STOP_WLDCRD']
520 614 print_error("Wildcard record found!")
521 615 else
522   - dnsbrute(datastore['DOMAIN'],datastore['WORDLIST'],datastore['NS'])
  616 + dnsbrute(datastore['DOMAIN'],datastore['WORDLIST'],datastore['NS'],wldcrd)
523 617 end
524 618 end
525 619
@@ -527,7 +621,7 @@ def run
527 621 if wldcrd and datastore['STOP_WLDCRD']
528 622 print_status("Wildcard Record Found!")
529 623 else
530   - bruteipv6(datastore['DOMAIN'],datastore['WORDLIST'],datastore['NS'])
  624 + bruteipv6(datastore['DOMAIN'],datastore['WORDLIST'],datastore['NS'],wldcrd6)
531 625 end
532 626 end
533 627
@@ -542,5 +636,10 @@ def run
542 636 if(datastore['ENUM_RVL'] and datastore['IPRANGE'] and not datastore['IPRANGE'].empty?)
543 637 reverselkp(datastore['IPRANGE'],datastore['NS'])
544 638 end
  639 + # Do not let module finish while threads exist
  640 + while not @dns_enum_threads.empty? do
  641 + vprint_status("Waiting on #{@dns_enum_threads.length} threads to finish")
  642 + Rex::ThreadSafe.sleep(10)
  643 + end
545 644 end
546 645 end
81 scripts/resource/dns_enum.rc
... ... @@ -0,0 +1,81 @@
  1 +# dns_enum.rc
  2 +# Author: RageLtMan
  3 +
  4 +# This resource file can be used to enumerate workspace target DNS resources.
  5 +
  6 +<ruby>
  7 +if (framework.datastore['VERBOSE'] == "true") #we look in the global datastore for a global VERBOSE option and use it
  8 + verbose = 1 #true
  9 +else
  10 + verbose = 0
  11 +end
  12 +
  13 +# Test and see if we have a database connected
  14 +begin
  15 + framework.db.hosts
  16 +rescue ::ActiveRecord::ConnectionNotEstablished
  17 + print_error("Database connection isn't established")
  18 + return
  19 +end
  20 +
  21 +# Set max threadcount
  22 +threadcount = 16
  23 +# Configure current workspace
  24 +ws = framework.db.workspace
  25 +# Get DNS names
  26 +if framework.datastore['DNS_ENUM_DOMAIN']
  27 + # pull from global datastore if set
  28 + names = framework.datastore['DNS_ENUM_DOMAIN'].split(/,|\s/).map(&:strip).delete_if(&:empty?)
  29 +else
  30 + # Find all top primary domains and enumerate each
  31 + names = ws.hosts.map(&:name).compact.uniq.delete_if {
  32 + |i| Rex::Socket.is_ipv4?(i) or i.chomp('.') !~ /\w+\.\w+$/
  33 + }.map {
  34 + |n| n.scan(/\w+\.\w+$/).first
  35 + }.compact.map {|n| n.chomp('.')}.uniq
  36 +end
  37 +
  38 +if names.empty?
  39 + print_error("No domain names defined or found, set DNS_ENUM_DOMAIN")
  40 + return
  41 +else
  42 + print_good("Enumerating #{names.join(', ')}")
  43 +end
  44 +# Sort by IP
  45 +addrs = ws.hosts.map(&:address).sort {|a| IPAddr.new(a)}
  46 +
  47 +# Create module and merge with framework datastore
  48 +mod = framework.auxiliary.create('gather/enum_dns')
  49 +mod.datastore.merge(framework.datastore)
  50 +mod.datastore['ENUM_RVL'] = true
  51 +# Do reverse lookups for existing hosts only
  52 +mod.datastore['RVL_EXISTING_ONLY'] = true
  53 +
  54 +# The following should work but fails for some reason
  55 +#self.output.print_raw("#{addrs.first}-#{addrs.last}")
  56 +#mod.datastore['IPRANGE'] = "#{addrs.first}-#{addrs.last}"
  57 +# Workaround for above
  58 +mod.datastore['IPRANGE'] = '0.0.0.0/0'
  59 +
  60 +# Pull settings from datastore or set to true
  61 +mod.datastore['ENUM_BRT'] ||= true
  62 +mod.datastore['REPORT_A_RECORDS'] ||= true
  63 +names.each do |name|
  64 + mod.datastore['DOMAIN'] = name
  65 + # Assign reasonable fraction of max threads to module
  66 + mod.datastore['THREADS'] = framework.datastore['DNS_ENUM_THREADS'] || threadcount/names.length
  67 + # DEBUG: self.output.print_raw("using #{name}\n")
  68 + if verbose
  69 + mod.run_simple(
  70 + 'LocalOutput' => self.output,
  71 + 'RunAsJob' => true
  72 + )
  73 + else
  74 + mod.run_simple(
  75 + 'LocalOutput' => nil,
  76 + 'RunAsJob' => true
  77 + )
  78 + end
  79 +end
  80 +
  81 +</ruby>

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.