Class: Wmap::DnsBruter

Inherits:
Object
  • Object
show all
Includes:
Utils
Defined in:
lib/wmap/dns_bruter.rb

Overview

Class to discover valid hosts through either zone transfer or DNS brute-force methods

Constant Summary

Constants included from Utils::DomainRoot

Utils::DomainRoot::File_ccsld, Utils::DomainRoot::File_cctld, Utils::DomainRoot::File_gtld, Utils::DomainRoot::File_tld

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utils

#cidr_2_ips, #file_2_hash, #file_2_list, #get_nameserver, #get_nameservers, #host_2_ip, #host_2_ips, #is_cidr?, #is_fqdn?, #is_ip?, #list_2_file, #reverse_dns_lookup, #sort_ips, #valid_dns_record?, #zone_transferable?

Methods included from Utils::Logger

#wlog

Methods included from Utils::UrlMagic

#create_absolute_url_from_base, #create_absolute_url_from_context, #host_2_url, #is_site?, #is_ssl?, #is_url?, #make_absolute, #normalize_url, #url_2_host, #url_2_path, #url_2_port, #url_2_site, #urls_on_same_domain?

Methods included from Utils::DomainRoot

#get_domain_root, #get_sub_domain, #is_domain_root?, #print_ccsld, #print_cctld, #print_gtld

Constructor Details

#initialize(params = {}) ⇒ DnsBruter

Set default instance variables



20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/wmap/dns_bruter.rb', line 20

def initialize (params = {})
  # Change to your brute-force dictionary file here if necessary
  @data_dir=params.fetch(:data_dir, File.dirname(__FILE__)+'/../../data/')
  Dir.mkdir(@data_dir) unless Dir.exist?(@data_dir)
  @file_hosts = @data_dir + 'hosts'
  @file_hosts_dict = File.dirname(__FILE__)+'/../../dicts/hostnames-dict.txt'

  @verbose=params.fetch(:verbose, false)
  @discovered_hosts_from_dns_bruter=Hash.new
  @hosts_dict=params.fetch(:hosts_dict, @file_hosts_dict)
  @max_parallel=params.fetch(:max_parallel, 30)
  @fail_domain_cnt=Hash.new
end

Instance Attribute Details

#data_dirObject

Returns the value of attribute data_dir.



16
17
18
# File 'lib/wmap/dns_bruter.rb', line 16

def data_dir
  @data_dir
end

#discovered_hosts_from_dns_bruterObject (readonly)

Returns the value of attribute discovered_hosts_from_dns_bruter.



17
18
19
# File 'lib/wmap/dns_bruter.rb', line 17

def discovered_hosts_from_dns_bruter
  @discovered_hosts_from_dns_bruter
end

#fail_domain_cntObject (readonly)

Returns the value of attribute fail_domain_cnt.



17
18
19
# File 'lib/wmap/dns_bruter.rb', line 17

def fail_domain_cnt
  @fail_domain_cnt
end

#hosts_dictObject

Returns the value of attribute hosts_dict.



16
17
18
# File 'lib/wmap/dns_bruter.rb', line 16

def hosts_dict
  @hosts_dict
end

#max_parallelObject

Returns the value of attribute max_parallel.



16
17
18
# File 'lib/wmap/dns_bruter.rb', line 16

def max_parallel
  @max_parallel
end

#verboseObject

Returns the value of attribute verbose.



16
17
18
# File 'lib/wmap/dns_bruter.rb', line 16

def verbose
  @verbose
end

Instance Method Details

#brute_all(num = @max_parallel) ⇒ Object

Parallel DNS brute-force all existing domains



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/wmap/dns_bruter.rb', line 247

def brute_all(num=@max_parallel)
  puts "Start the parallel brute-forcing all domains with maximum child processes: #{num}"
  begin
    hosts=Array.new
    my_dis=Wmap::HostTracker.instance
    my_dis.data_dir=@data_dir
    known_domains=my_dis.dump_root_domains
    hosts=dns_brute_domains(num, known_domains)
    my_dis.adds(hosts)
    my_dis.save!
    my_dis=nil
    hosts
  rescue Exception => ee
    puts "Exception on method #{__method__}: #{ee}" if @verbose
  end
end

#brute_force_dns(host) ⇒ Object

Return a list of valid hosts by brute-forcing the name servers



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/wmap/dns_bruter.rb', line 161

def brute_force_dns (host)
  puts "Start dictionary attacks on the DNS server for: #{host}" if @verbose
  begin
    host=host.strip
    valid_hosts = Array.new
    my_host_tracker = Wmap::HostTracker.instance
    my_host_tracker.data_dir=@data_dir
    # build the host dictionary for the brute force method
    dict = Array.new
    if File.exists?(@hosts_dict)
      dict = file_2_list(@hosts_dict)
    elsif File.exists?(@file_hosts)
      dict = my_host_tracker.top_hostname(200)
      my_host_tracker.list_2_file(dict,@hosts_dict)
    else
      abort "Error: Non-existing common hosts dictionary file - #{@host_dict} or hosts file #{@file_hosts}. Please check your file path and name setting again."
    end
    domain=String.new
    unless is_root_domain?(host) or my_host_tracker.sub_domain_known?(host)
      my_hosts=hostname_mutation(host).map {|x| x.split('.')[0]}
      dict+=my_hosts unless my_hosts.empty?
    end
    if is_domain?(host) or my_host_tracker.sub_domain_known?(host)
      domain=host
    elsif
      array_h=host.split('.')
      array_h.shift
      domain=array_h.join('.')
      puts "Domain for #{host}: #{domain}" if @verbose
    end
    dict+=[host.split(".")[0],""]
    puts "Choose Brute-force Dictionary: #{dict}" if @verbose
    cnt=0
    dict.each do |x|
      # 10/09/2013 add logic to skip brute-forcing the domain in case of experiencing more than 2 Dnsruby::ServFail conditions
      if @fail_domain_cnt.key?(domain)
        if @fail_domain_cnt[domain]>2
          puts "Error! Multiple ServFail conditions detected in method #{__method__}. Now skip remaining works on: #{sub_domain}" if @verbose
          return valid_hosts
        end
      end
      cnt=cnt+1
      if x.nil?
        next
      elsif x.empty?
        host=domain
      else
        host=[x,".",domain].join.downcase
      end
      valid_hosts.push(host) if valid_dns_record?(host)
      # Logic to detecting the bluff if the DNS server return hostname we threw to it
      if cnt==10 && valid_hosts.size>=10
        valid_hosts=[host]
        puts "Brute force method fail, as the DNS server response to every host-name threw at it!"
        break
      end
    end
    puts "Found DNS records on domain #{host}: #{valid_hosts}" if @verbose
    @discovered_hosts_from_dns_bruter[host] = valid_hosts
    my_host_tracker = nil
    return valid_hosts.uniq
  rescue Exception => ee
    puts "Exception on method #{__method__}: #{ee}" if @verbose
  end
end

#dns_brute_domains(targets, num = @max_parallel) ⇒ Object

Parallel DNS brute-forcer operating on the trusted domains - by utilizing fork manager to spawn multiple child processes on multiple sub_domain domains from the local hosts table simultaneously



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/wmap/dns_bruter.rb', line 228

def dns_brute_domains(targets,num=@max_parallel)
  puts "Start the parallel brute-forcing with multiple child processes: #{num}"
  begin
    hosts=Array.new
    # Sliced to chunks of 1,000 domains for each process time, to avoid potential overflow of large array ?
    puts "Brute-forcing the following domain: #{targets}" if @verbose
    targets.each_slice(1000).to_a.map do |slice|
      hosts_new=dns_brute_workers(slice,num)
      hosts << hosts_new
    end
    puts "Parallel bruting result: #{hosts.flatten}" if @verbose
    return hosts.flatten
  rescue Exception => ee
    puts "Exception on method #{__method__}: #{ee}" if @verbose
    return hosts.flatten
  end
end

#dns_brute_file(file_target, num = @max_parallel) ⇒ Object

Parallel DNS brute-forcer operating on target domain file - by utilizing fork manager to spawn multiple child processes on multiple domains simultaneously



89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/wmap/dns_bruter.rb', line 89

def dns_brute_file(file_target,num=@max_parallel)
  puts "Start the parallel brute-forcing with multiple child processes on target file #{file_target}: #{num}"
  begin
    hosts=Array.new
    targets=file_2_list(file_target)
    hosts=dns_brute_workers(targets,num)
    return hosts
  rescue Exception => ee
    puts "Exception on method #{__method__}: #{ee}" if @verbose
    return hosts
  end
end

#dns_brute_worker(host) ⇒ Object Also known as: query, brute

Main worker to perform the brute-forcing on an Internet domain



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/wmap/dns_bruter.rb', line 35

def dns_brute_worker(host)
  puts "Start DNS brute forcer on: #{host}"
  results=Hash.new
  domain=get_domain_root(host)
  begin
    host=host.strip.downcase
    raise "Invalid internet host format: #{host}" unless is_fqdn?(host)
    domain=get_domain_root(host)
    # If we can do the zone transfer, then the brute-force process can be skipped.
    if zone_transferable?(domain)
      hosts=zone_transfer(domain)
    else
      hosts=brute_force_dns(host)
    end
    results[domain]=hosts
    puts "Finish discovery on #{host}: #{results}"
    return results
  rescue Exception=>ee
    puts "Exception on method #{__method__}: #{ee}" if @verbose
    return results
  end
end

#dns_brute_workers(list, num = @max_parallel) ⇒ Object Also known as: queries, brutes

Parallel DNS brute-forcer operating on target domain list - by utilizing fork manager to spawn multiple child processes on multiple domains simultaneously



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/wmap/dns_bruter.rb', line 61

def dns_brute_workers(list,num=@max_parallel)
  puts "Start the parallel engine one the domain list: #{list} \nMaximum brute-forcing session: #{num} "
  begin
    targets=list.uniq.keep_if { |x| is_fqdn?(x) }
    results=Hash.new
    Parallel.map(targets, :in_processes => num) { |target|
      dns_brute_worker(target)
    }.each do |process|
      if process.nil?
        next
      elsif process.empty?
        #do nothing in case of thrown an empty array
      else
        #domain=get_domain_root(process.first).downcase
        results.merge!(process)
      end
    end
    puts "Parallel DNS brute-force results: #{results}" if @verbose
    @discovered_hosts_from_dns_bruter.merge!(results)
    return results
  rescue Exception => ee
    puts "Exception on method #{__method__}: #{ee}" if @verbose
  end
end

#get_vulnerable_ns(domain) ⇒ Object

Test the DNS server if zone transfer is allowed. If allowed, save the found hosts into the class variable.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/wmap/dns_bruter.rb', line 140

def get_vulnerable_ns(domain)
  puts "Identify the vulnerable DNS servers if zone transfer is allowed."
  domain=domain.strip.downcase
  vuln=Array.new
  begin
    nameservers = get_nameservers(domain)
    nameservers.each do |nssrv|
      zt = Dnsruby::ZoneTransfer.new
      zt.server=nssrv unless nssrv.empty?
      records = zt.transfer(domain)
      unless records==nil
        vuln.push(nssrv)
      end
    end
    return vuln
  rescue Exception=>ee
    puts "Exception on method #{__method__}: #{ee}" if @verbose
  end
end

#hostname_mutation(host) ⇒ Object Also known as: mutation

Return a list of hosts in the mutation form from the original, i.e. “ww1.example.com” => [“ww1,example.com”,“ww2.example.com”,…]



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/wmap/dns_bruter.rb', line 265

def hostname_mutation(host)
  puts "Start host mutation emulation on: #{host}" if @verbose
  begin
    hosts=Array.new
    host=host.strip.downcase
    raise "Invalid host format: #{host}" unless is_fqdn?(host)
    unless is_domain_root?(host)
      hostname=host.split('.')[0]
      hosts.push(host)
      case hostname
      when /\d+/
        #first form of mutation, i.e. "ww1" => ["ww1","ww2",...]
        hostname.scan(/\d+/).map do |x|
          y=x.to_i
          5.times do |i|
              z=y+i+1
              w=(y-i-1).abs
              mut1=host.sub_domain(x,z.to_s)
              mut2=host.sub_domain(x,w.to_s)
              hosts.push(mut1,mut2)
          end
        end
      else
        puts "No mutation found for: #{host}" if @verbose
      end
    end
    puts "Host mutation found: #{hosts.uniq}" if @verbose
    return hosts.uniq
  rescue Exception => ee
    puts "Exception on method #{__method__}: #{ee}" if @verbose
    return hosts  # fail-safe
  end
end

Print summary report of found hosts from the brute force attacks



301
302
303
304
305
306
307
308
309
# File 'lib/wmap/dns_bruter.rb', line 301

def print_discovered_hosts_from_bruter
  puts "\nSummary Report of the Discovered Hosts:"
  @discovered_hosts_from_dns_bruter.each do |domain,hosts|
    puts "Domain: #{domain}"
    puts "Found hosts:"
    puts @discovered_hosts_from_dns_bruter[domain]['hosts']
  end
  puts "End of the summary"
end

#zone_transfer(domain) ⇒ Object

Perform zone transfer on a domain, return found host entries in an array



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/wmap/dns_bruter.rb', line 103

def zone_transfer(domain)
  puts "Perform zone transfer on zone: #{domain}"
  domain=domain.downcase
  nameservers = get_nameservers(domain)
  hosts=Array.new
  puts "Retrieved name servers: #{nameservers}" if @verbose
  nameservers.each do |nssrv|
    begin
      puts "Attempt zone transfer on name server: #{nssrv}"
      if nssrv.nil?
        abort "Method input variable error: no name server found!" if @verbose
        next
      end
      zt = Dnsruby::ZoneTransfer.new
      zt.server=nssrv unless nssrv.empty?
      records = zt.transfer(domain)
      if records==nil
        puts "Zone transfer failed for zone #{domain} on: #{nssrv}"
        next
      else
        puts "Zone transfer successfully for zone #{domain} on the name server: #{nssrv}"
        records = records.delete_if {|x| not x.to_s=~/(\s+|\t+)IN/ }
        records.each  { |line| puts line.to_s } if @verbose
        hosts=records.collect {|x| x.to_s.split(/\.(\s+|\t+)/).first}
        hosts=hosts.sort!.uniq!
        puts "Found hosts: #{hosts}" if @verbose
        @discovered_hosts_from_dns_bruter[domain] = hosts
        return hosts
      end
    rescue Exception=>ee
      puts "Exception on method #{__method__}: #{ee}" if @verbose
    end
  end
  return hosts
end