14

I have a name server that's publicly accessible since it is the authoritative name server for a couple of domains.

Currently the server is flooded with faked type ANY requests for isc.org, ripe.net and similar (that's a known distributed DoS attack).

The server runs BIND and has allow-recursion set to my LAN so that these requests are rejected. In such cases the server responds just with authority and additional sections referring the root servers.

Can I configure BIND so that it completely ignores these requests, without sending a response at all?

Udo G
  • 443
  • 4
  • 9
  • 20

8 Answers8

5

Faced with the same problem, I chose to ignore all recursive requests. All resolvers do send a non-recursive query when they want to use my server as an authoritative server. Only misconfigured clients and attackers, in my own case, use recursive queries.

Unfortunately I haven't found a way to let BIND do that, but in case iptables is good enough for you, I used

iptables -t raw -I PREROUTING -i eth0 -p udp --destination-port 53 \
    -m string --algo kmp --from 30 \
    --hex-string "|01000001000000000000|" -j DROP
pino42
  • 915
  • 5
  • 11
  • Nope, that rule blocks also authoritative-type requests (at least on my machine). Apparently it blocks all kinds of DNS requests. – Udo G Nov 05 '12 at 10:16
  • I double checked and I'm using exactly that rule. Here's a cut-and-paste from the live server. Command: `iptables -t raw -S PREROUTING`. Output: `-P PREROUTING ACCEPT`, followed by `-A PREROUTING -i eth0 -p udp -m udp --dport 53 -m string --hex-string "|01000001000000000000|" --algo kmp --from 30 --to 65535 -j DROP`. I tested that it is working correctly with `host -ar exampledomain.com dns-server.example.net`. Of course it did not work correctly until I added the `-r` option. – pino42 Nov 05 '12 at 11:33
  • Okay, the `-r` option makes the difference. I personally don't like that simple `host` queries do not work anymore and this can be very confusing. This is probably a valid (best so far) answer nonetheless and I'll give you the bounty, since it's about to expire, even if I will continue using my own approach by filtering OUTPUT. – Udo G Nov 05 '12 at 17:21
  • Thanks! If I bump across a better solution, I'll make sure to post it. I agree with you: this one is a hack. A working one, but still a hack. – pino42 Nov 05 '12 at 17:49
2

I would try:

zone "." {
  type redirect;
  allow-query "none";
}

The responses referring clients to the root servers is controlled by the "redirect" zone. This should tell it not to reply to those.

That's hinted at in the Bind9 docs: http://ftp.isc.org/isc/bind9/cur/9.9/doc/arm/Bv9ARM.ch06.html#id2592674

You may with to replace "none" with your local subnet.

If you already have a zone "." declaration, simply add allow-query "none"; to it.

freiheit
  • 14,544
  • 1
  • 47
  • 69
  • I have a `zone "." { type hint; file "/etc/bind/db.root"; };` declaration with db.root listing the root servers. Removing that declaration stops anwers for foreign domains but the server is nonetheless responding with a "server failure" and thus can still be used for DoS. – Udo G Oct 30 '12 at 10:06
  • @UdoG: Have you tried adding `allow-query "none";` to the `zone "."` config? – freiheit Oct 30 '12 at 15:33
  • Its seems like this is only saving upstream bandwidth though which should be plentiful. With your fix the attacker has already used up your servers downstream bandwidth and processing power – TheLQ Nov 03 '12 at 08:57
  • @TheLQ: The question refers to this being a DDoS attack. The common DNS-based DDoS attack is to send DNS queries with the IP forged of the target. Since the DNS reply packet is larger than the query, it provides a multiplier. If your server doesn't respond with a significantly larger packet, you've eliminated the reason they're using your server in the attack. – freiheit Nov 03 '12 at 16:23
  • @UdoG: The server failure packet is only 31 to 32, while the referral to the root servers was probably several hundred bytes. If your server's reply is the same size as the query, or only a tiny bit larger, your server is useless in a DNS DDoS attack, since the attackers will consume as much bandwidth as they get you to send to their target. I tested against a number of likely well-configured authoritative name servers (such as google's), and they replied with "recursion requested but not available". – freiheit Nov 03 '12 at 16:35
  • @freiheit: true, but I've already got complaints that my servers are "attacking" other servers due to these failure replies. Maybe those were automated abuse reports (the mail text seems to suggest that) but I don't want to file a obligatory statement each time... – Udo G Nov 05 '12 at 09:54
  • @freiheit: `allow-query "none";` still causes a `SERVFAIL` – Udo G Nov 05 '12 at 10:06
1

Like with you, I dislike when my server involved in DDOS, even it responds with small 30-byte packets. Also, for some reasons I unable to use iptables to block spoofed requests. Server vulnerability was fixed 3 months ago, but botnet still send spoofed requests to server.

So, as result, you can use this simple patch to not send REFUSED response (requests will be dropped):

--- bind9-9.9.5.dfsg/bin/named/query.c.orig        Thu Aug  6 21:56:57 2020
+++ bind9-9.9.5.dfsg/bin/named/query.c     Thu Aug  6 22:08:15 2020
@@ -1038,7 +1038,7 @@
                                         sizeof(msg));
                        ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
                                      NS_LOGMODULE_QUERY, ISC_LOG_INFO,
-                                     "%s denied", msg);
+                                     "%s dropped", msg);
                }
                /*
                 * We've now evaluated the view's query ACL, and
@@ -5809,8 +5809,9 @@
                        } else
                                inc_stats(client, dns_nsstatscounter_authrej);
                        if (!PARTIALANSWER(client))
-                               QUERY_ERROR(DNS_R_REFUSED);
-               } else
+               //              QUERY_ERROR(DNS_R_REFUSED);
+                               QUERY_ERROR(DNS_R_DROP);
+               } else
                        QUERY_ERROR(DNS_R_SERVFAIL);
                goto cleanup;
        }
# diff -u query.c.orig query.c

... as result, you get nice logs which like this one:

Aug  6 21:39:29 topor named[2652]: client 78.180.51.241#43072 (.): query (cache) './ANY/IN' dropped
Aug  6 21:40:00 topor last message repeated 1485 times

And there will be no 'REFUSED' reply if query came from unauthorized network / for unsupported domain.

Pavel
  • 11
  • 1
1

Have you tried to block string isc.org or block the hex string for it?

This worked for me:

iptables -A INPUT -p udp -m string --hex-string "|03697363036f726700|" --algo bm -j DROP

Hex
  • 1,949
  • 11
  • 17
  • Wouldn't it be better to identify the hex strings for all domains that the server should be responding to, do the above to allow those, and drop all other udp/53 trafic? – freiheit Oct 28 '12 at 19:02
  • I'm currently already blocking UDP responses that are referring to the root servers: `iptables -A OUTPUT -p udp -m string -hex-string "|726f6f742d73657276657273|" –algo bm –to 65535 -j DROP` but I'd really prefer a solution that is just based on BIND configuration, if that's possible at all. – Udo G Oct 30 '12 at 09:52
  • this is weak. you can generate whatever sting you wish as domain. we are facing that problem right now and it's not the way to block it via static name `'bnrexex.www.sf97.net/A/IN' 'whzpkacpxpiuycm.www.tpa.net.cn/A/IN'` – 3h4x Mar 17 '14 at 14:16
1

Generally, i would suggest:

Turn on bind logs and record ips that gets rejected answer. Install fail2ban program, add blackhole action: http://pastebin.com/k4BxrAeG (put rule in file in /etc/fail2ban/actions.d)

Create bind filter file in /etc/fail2ban/filter.d with something like this (needs debugging!)

[Definition]
failregex = ^.* security: info: client #<HOST>: query \(cache\) .* denied

Edit fail2ban.conf, add section:

[bindban]

enabled  = true
filter   = bind
# "bantime" is the number of seconds that a host is banned.
bantime  = 6000
# A host is banned if it has generated "maxretry" during the last "findtime"
# seconds.
findtime  = 60
# "maxretry" is the number of failures before a host get banned.
maxretry = 150
action   = blackhole
logpath  = /var/log/named.log

Hope this will help!

Andrei Mikhaltsov
  • 3,027
  • 1
  • 23
  • 31
1

Basic idea let bind classify DNS response as Refused then use iptables to convert Refused into silently ignored.

Refused is the easy part in named.conf options section:

allow-recursion { none;};

Or of course your favorite ACLs for local exceptions...

Next crazy iptables magic, adjust or remove "-o eth0" as needed. This command assumes standard 20 byte IPv4 layer header prior to UDP.

iptables -A OUTPUT -o eth0 -p udp --sport 53 -m string --from 30 --to 32 --hex-string "|8105|" --algo bm -j DROP

This keys on the flags field of DNS response with the following bits set

  • DNS Response
  • Recursive query
  • Reply code refused

Noticed log message running bind in debug "error sending response: host unreachable" when the rule matches to have some feedback for testing.

Must admit this is all a somewhat pointless exercise. If there is no amplification an attacker could just as easily reflect TCP SYN. Ultimately DNS is broke simply no viable solution other than using TCP or deployment of Eastlake's DNS cookies.

quanta
  • 51,413
  • 19
  • 159
  • 217
disk eater
  • 111
  • 1
  • 3
0

This attack is called Amplified Denial of Service. You should configure bind properly but that traffic shouldn't get to your bind in the first place. Block it on first network device that is capable of doing it in your network. I had same problem and dealt with it with deafult snort rule:

alert udp $EXTERNAL_NET any -> $HOME_NET 53 (msg:"PROTOCOL-DNS excessive queries of type ANY - potential DoS"; byte_test:1,!&,0xF8,2; content:"|00 00 FF 00 01|"; detection_filter:track by_src, count 30, seconds 30; metadata:service dns; reference:url,foxpa.ws/2010/07/21/thwarting-the-isc-org-dns-ddos/; classtype:attempted-dos; sid:21817; rev:4;)

3h4x
  • 511
  • 4
  • 7
0

First, I know this is an old question but...

I've been running my own authoritative, non recursive, DNS server for decades, but have never been a victim in any DNS based DDoS attacks – until now, when I switched to a new ISP. Thousands of spoofed DNS queries flooded my logs and I got really annoyed – not so much about the impact on my server, rather the fact it cluttered my logs and the uncomfortable feeling of being abused. It seems that the attacker tries to use my DNS in a “Authoritative Name Server attack”.

So I figured that, even though I limit recursive queries to my internal network (denying all other), I rather spend my CPU cycles on string matching in iptables than sending back negative responses to the spoofed IP addresses (less clutter in my logs, less network traffic and a higher satisfaction level of my own).

I started off by doing as everybody else seems to do, find out which domain names is queried and created a string match on that domain with a target DROP. But I soon realized that I would end up with a huge amount of rules, each of them consuming CPU cycles. So, what to do? Since I do not run a recursive name server I figured that I might do the matching on the actual zones I'm authoritative for and drop everything else.

My default policy in iptables is ACCEPT, if your policy is DROP you probably need to make some adjustments if you would like to use the following solution.

I keep my zone configuration in a separate file (/etc/bind/named.conf.local), let's use this as an example:

zone "1.168.192.in-addr.arpa" { // Private
        type master;
        allow-query { 192.168.1.0/24; 127.0.0.1; };
        allow-transfer { 127.0.0.1; };
        file "/etc/bind/db.192.168.1";
};

zone "home.example.net" { // Private
        type master;
        allow-query { 192.168.1.0/24; 127.0.0.1; };
        allow-transfer { 127.0.0.1; };
        file "/etc/bind/pri/db.home.example.net";
};

zone "example.net" {
        type master;
        file "/etc/bind/pri/db.example.net";
        allow-transfer { 127.0.0.1; 8.8.8.8; };
};

zone "example.com" {
        type slave;
        masters { 8.8.8.8; };
        file "sec.example.com";
        allow-transfer { 127.0.0.1; };
        notify no;
};

zone "subdomain.of.example.nu" {
        type slave;
        masters { 8.8.8.8; };
        file "sec.subdomain.of.example.nu";
        allow-transfer { 127.0.0.1; };
        notify no;
};

Note the “// Private” comment on my first two zones, I make use of this in the following script to exclude them from the list of valid zones.

#!/usr/bin/perl
# zone2iptables - Richard Lithvall, april 2014
#
# Since we want to match not only example.net, but also (for example)
# www.example.net we need to set a reasonable maximum value for a domain
# name in our zones - 100 character should be more that enough for most people
# and 255 is the absolute maximum allowed in rfc1034.
# Set it to 0 (zero) if you would like the script to fetch each zone (axfr)
# to get the actual max value.
$maxLengthOfQueryName=255;
$externalInterface="eth1";

print "# first time you run this, you will get error on the 3 first commands.\n";
print "# It's here to make it safe/possible to periodically run this script.\n";
print "/sbin/iptables -D INPUT -i $externalInterface -p udp --dport 53 -j DNSvalidate\n";
print "/sbin/iptables -F DNSvalidate\n";
print "/sbin/iptables -X DNSvalidate\n";
print "#\n";
print "# now, create the chain (again)\n";
print "/sbin/iptables -N DNSvalidate\n";
print "# and populate it with your zones\n";
while(<>){
        if(/^zone\s+"(.+)"\s+\{$/){
                $zone=$1;
                if($maxLengthOfQueryName){
                        $max=$maxLengthOfQueryName;
                } else {
                        open(DIG,"dig -t axfr +nocmd +nostats $zone |");
                        $max=0;
                        while(<DIG>){
                                if(/^(.+?)\.\s/){
                                        $max=(length($1)>$max)?length($1):$max;
                                }
                        }
                        close(DIG);
                }
                printf("iptables -A DNSvalidate -m string --from 40 --to %d --hex-string \"",($max+42));
                foreach $subdomain (split('\.',$zone)){
                        printf("|%02X|%s",length($subdomain),$subdomain);
                }
                print("|00|\" --algo bm -j RETURN -m comment --comment \"$zone\"\n");
        }
}
print "# and end the new chain with a drop\n";
print "/sbin/iptables -A DNSvalidate -j DROP\n";
print "# And, at last, make the new chain active (on UDP/53)\n";
print "/sbin/iptables -A INPUT -i $externalInterface -p udp --dport 53 -j DNSvalidate\n";

Run the above script with the zone configuration file as argument.

root:~/tmp/# ./zone2iptables.pl /etc/bind/named.conf.local 
# first time you run this, you will get error on the 3 first commands.
# It's here to make it safe/possible to periodically run this script.
/sbin/iptables -D INPUT -i eth1 -p udp --dport 53 -j DNSvalidate
/sbin/iptables -F DNSvalidate
/sbin/iptables -X DNSvalidate
#
# now, create the chain (again)
/sbin/iptables -N DNSvalidate
# and populate it with your zones
iptables -A DNSvalidate -m string --from 40 --to 297 --hex-string "|07|example|03|net|00|" --algo bm -j RETURN -m comment --comment "example.net"
iptables -A DNSvalidate -m string --from 40 --to 297 --hex-string "|07|example|03|com|00|" --algo bm -j RETURN -m comment --comment "example.com"
iptables -A DNSvalidate -m string --from 40 --to 297 --hex-string "|09|subdomain|02|of|07|example|02|nu|00|" --algo bm -j RETURN -m comment --comment "subdomain.of.example.nu"
# and end the new chain with a drop
/sbin/iptables -A DNSvalidate -j DROP
# And, at last, make the new chain active (on UDP/53)
/sbin/iptables -A INPUT -i eth1 -p udp --dport 53 -j DNSvalidate

Save the output to a script, pipe it to a shell or copy and paste it in your terminal to create the new chain and start filter out all invalid DNS queries.

run /sbin/iptables -L DNSvalidate -nvx to see packet (and byte) counters on each rule in the new chain (you might want to move the zone with most packets to the top of the list to make it more efficient).

In hope that someone might find this useful :)

rich
  • 1