Scanning for open recursive DNS resolvers

A few days ago we unfortunately had some abuse reports regarding customers with DNS resolvers being abused in order to participate in a distributed denial of service attack.

Amongst other issues, DNS servers which are misconfigured to allow arbitrary hosts to do recursive queries through them can be used by attackers to launch an amplified attack on a forged source address.

I try to scan our address space reasonably often but I must admit I hadn’t done so for some time. I kicked off another scan and found one more customer with a misconfigured resolver, which has since been fixed.

After mentioning that I would do a scan I was asked how I do that.

I use a Perl script I’ve hacked together over the last couple of years. I took a few minutes to tidy it up and add a small amount of documentation (run it with --man to read that), so here it is in case anyone finds it useful:

Update: This code has now been moved to GitHub. If you have any comments, problems or improvements please do submit them as an issue and I will get to it much quicker. The gist below is now out of date so please don’t use it.

Using the default 100 concurrent queries it scans a /21 in about 80 seconds (YMMV depending upon how many hosts you have that firewall 53/UDP). That scales sort of linearly with how many you do, so using -q 200 for example will cut that down to about 40 seconds. It’s only a select loop though so it’ll use more CPU if you do that.

Two things I’ve noticed since:

  • It doesn’t handle failing to create a socket with bgsend so for example if you run up against your limit of file descriptors (commonly ~1024 on Linux) the whole thing will get stuck at 100% CPU.
  • One person reporting a similar situation (bgsend fails, stuck at 100% CPU) when they allowed it to try to send to a broadcast address. I haven’t been ale to replicate that one yet.

16 thoughts on “Scanning for open recursive DNS resolvers

  1. It should be noted that this script isn’t intelligent enough to understand if it actually received an address record back. For example (IPs edited):

    ./find_open_resolvers.pl –timeout 5 1.2.3.0/24
    # Wed Apr 17 12:59:59 2013| Launching queries against 1.2.3/24, 100 at a time…
    !!! Got answer from 1.2.3.40 – possible open resolver!
    !!! Got answer from 1.2.3.50 – possible open resolver!
    !!! Got answer from 1.2.3.93 – possible open resolver!
    !!! Got answer from 1.2.3.97 – possible open resolver!
    !!! Got answer from 1.2.3.102 – possible open resolver!
    !!! Got answer from 1.2.3.213 – possible open resolver!
    !!! Got answer from 1.2.3.220 – possible open resolver!
    # Wed Apr 17 13:00:34 2013| 256 IPs queried in 35s

    Hmm… let’s check:

    dig @1.2.3.97 google.hk

    ; <> DiG 9.8.4-rpz2+rl005.12-P1 <> @1.2.3.97 google.hk
    ; (1 server found)
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 11978
    ;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
    ;; WARNING: recursion requested but not available

    ;; QUESTION SECTION:
    ;google.hk. IN A

    ;; Query time: 1 msec
    ;; SERVER: 1.2.3.97#53(1.2.3.97)
    ;; WHEN: Wed Apr 17 06:01:36 2013
    ;; MSG SIZE rcvd: 27

    Which results in, amongst other things, ";; WARNING: recursion requested but not available"

    Also interesting that the script seems to be using GMT instead of system time…

    Anyway, it's still a good basic tool. Just needs to be used with a grain-of-salt when dealing with clients.

  2. To avoid false positive you could check query response (1), recursion(1) and query response code (NOERROR).

    if (defined $packet and $packet->answer){

    my $qr = $packet->header->qr;
    my $ra = $packet->header->ra;
    my $rcode = $packet->header->rcode;

    if ($qr == 1 && $ra == 1 && $rcode =~ /NOERROR/){
    print “$ip\n”;
    }

    $packet->print if ($verbose);
    } else {
    logger(“No answer from $ip”) if ($verbose);
    }

  3. @eric,

    That would be a trivial change.

    However, as it stands, the script expects a single IP address or range per invocation. You could wrap it in a simple shell loop that reads ranges from a file and calls the script once for each:

    $ cat ranges.txt
    1.2.3.4/24
    5.6.7.8/23

    $ while read iprange; do ./find_open_resolvers.pl $iprange; done < ./ranges.txt So no modifications needed as long as you don't need to do all IP ranges in parallel.

  4. thanks for the reply. The thing is i want your script to do scans on random ip addresses .what if i could pipe to the programme (./find_open_resolvers.pl) the output of a programme that generates ip addresses one by one,would it work. like this

    ./programme genertaing ips.pl | ./find_open_resolvers.pl

  5. @eric,

    While something like that could be made to work, it wouldn’t be very efficient because it would test only one IP address at a time, waiting for a complete test before proceeding to the next IP.

    The best way to do it would be to alter the script to have a mode that chooses random IPs from a range (without reusing ones it’s already tested) instead of sequential, and tests them in parallel like the existing code does.

    I struggle to think of a legitimate reason why you would want to test them at random so it’s not something I’m likely to spend time on I’m afraid.

  6. Hey am getting this error while running the scrip i have the module Net::IP installed. Still i see this error. Can you please help.

    root@nixtechnix [/home/webmaste/www/cgi-bin]# ./find-open-resolvers.pl –man
    Can’t locate Net/IP.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at ./find-open-resolvers.pl line 6.
    BEGIN failed–compilation aborted at ./find-open-resolvers.pl line 6.

    1. @Vishal,

      The indicates that you don’t have Net::IP installed in any of those directories. I suggest that you do a find for the file Net/IP.pm and see where it actually is. e.g.:

      $ find /usr -type f -name IP.pm
      /usr/share/perl5/Net/IP.pm
      $ perl -e 'print join("\n", @INC)'
      /etc/perl
      /usr/local/lib/perl/5.10.1
      /usr/local/share/perl/5.10.1
      /usr/lib/perl5
      /usr/share/perl5
      /usr/lib/perl/5.10
      /usr/share/perl/5.10
      /usr/local/lib/site_perl
      

      As you can see I have Net::IP in /usr/share/perl5 and that directory is in my @INC path. You need to ensure you have it and that it appears in one of the directories in your @INC path. If you have it but the directory isn’t in @INC, you can add the directory to @INC by use of “use lib …”

  7. Hi. i have tried to loop reading of the ranges and it hasnot worked.My bash script for looping is as below

    $ cat ranges.txt
    1.2.3.4/24
    5.6.7.8/23

    $ while read iprange; do ./find_open_resolvers.pl $range; done < ./ranges.txt

    what am i doing wrong ? i would like to read ranges from a file. also can you use xarg?

  8. @ErO:

    Sorry, there was a typo in my comment, which I have now fixed. I said:

    $ while read iprange; do ./find_open_resolvers.pl $range; done < ./ranges.txt When I should have said: $ while read iprange; do ./find_open_resolvers.pl $iprange; done < ./ranges.txt You could also do it like: for range in 1.2.3.4/24 5.6.7.8/23; do ./find_open_resolvers.pl $range; done

  9. hi,

    Does this script really work?i got it from the github repo which i am assuming its the latest but when i give it a cidr notation it still wants an ip range

Leave a Reply to Andy Cancel reply

Your email address will not be published. Required fields are marked *