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::UrlMagic

Utils::UrlMagic::Max_http_timeout

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?, #landing_location, #make_absolute, #normalize_url, #open_page, #redirect_location, #response_code, #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_domain_root_by_ccsld, #get_domain_root_by_cctld, #get_domain_root_by_tlds, #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