Назад | Перейти на главную страницу

bind: blackhole для недопустимых рекурсивных запросов?

У меня есть общедоступный сервер имен, так как он авторитетный сервер имен для пары доменов.

В настоящее время сервер залит поддельным типом ANY запросы на isc.org, ripe.net и т. д. (это известная распределенная DoS-атака).

На сервере работает BIND и allow-recursion установить в мою локальную сеть, чтобы эти запросы отклонялись. В таких случаях сервер отвечает просто authority и additional разделы, относящиеся к корневым серверам.

Могу ли я настроить BIND так, чтобы он полностью игнорировал эти запросы, вообще не отправляя ответ?

Столкнувшись с той же проблемой, я решил игнорировать все рекурсивные запросы. Все резолверы отправляют нерекурсивный запрос, когда хотят использовать мой сервер в качестве авторитетного сервера. Только неправильно настроенные клиенты и злоумышленники в моем случае используют рекурсивные запросы.

К сожалению, я не нашел способа позволить BIND сделать это, но в случае, если iptables вам подходит, я использовал

iptables -t raw -I PREROUTING -i eth0 -p udp --destination-port 53 \
    -m string --algo kmp --from 30 \
    --hex-string "|01000001000000000000|" -j DROP

Я бы попробовал:

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

Ответы, отсылающие клиентов к корневым серверам, контролируются зоной «перенаправления». Это должно сказать, что он не отвечает на них.

На это намекают в документации Bind9: http://ftp.isc.org/isc/bind9/cur/9.9/doc/arm/Bv9ARM.ch06.html#id2592674

Вы можете заменить "none" с вашей локальной подсетью.

Если у вас уже есть zone "." декларацию, просто добавьте allow-query "none"; к нему.

В общем, я бы посоветовал:

Включите журналы привязки и запишите IP-адреса, которые получают отклоненный ответ. Установите программу fail2ban, добавьте действие blackhole: http://pastebin.com/k4BxrAeG (поместите правило в файл в /etc/fail2ban/actions.d)

Создайте файл фильтра привязки в /etc/fail2ban/filter.d с чем-то вроде этого (требуется отладка!)

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

Отредактируйте файл fail2ban.conf, добавьте раздел:

[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

Надеюсь, это поможет!

Основная идея позволяет привязке классифицировать ответ DNS как Refused, а затем использовать iptables для преобразования Refused в автоматически игнорируемый.

Отказ - это простая часть в разделе опций named.conf:

allow-recursion { none;};

Или, конечно, ваши любимые ACL для локальных исключений ...

Следующая безумная магия iptables, при необходимости измените или удалите "-o eth0". Эта команда предполагает стандартный 20-байтовый заголовок уровня IPv4 до UDP.

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

Это ключи в поле флагов ответа DNS со следующими установленными битами

  • Ответ DNS
  • Рекурсивный запрос
  • Код ответа отклонен

Замечено сообщение журнала, в котором выполняется привязка в отладке «ошибка отправки ответа: узел недоступен», когда правило соответствует правилу, чтобы иметь некоторую обратную связь для тестирования.

Должен признать, что это все несколько бессмысленное занятие. Если нет усиления, злоумышленник может так же легко отразить TCP SYN. В конечном счете, DNS просто не работает, кроме использования TCP или развертывания файлов cookie DNS Eastlake.

Вы пытались заблокировать строку isc.org или заблокировать для нее шестнадцатеричную строку?

Это сработало для меня:

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

Эта атака называется усиленным отказом в обслуживании. Вы должны правильно настроить привязку, но этот трафик в первую очередь не должен попадать в вашу привязку. Заблокируйте его на первом сетевом устройстве, которое может это сделать в вашей сети. У меня была такая же проблема, и я решил ее с помощью правила глухого фырканья:

alert udp $ EXTERNAL_NET любой -> $ HOME_NET 53 (msg: «PROTOCOL-DNS чрезмерные запросы типа ЛЮБОЙ - потенциальный DoS»; byte_test: 1,! &, 0xF8,2; content: «| 00 00 FF 00 01 |»; Detection_filter: отслеживать by_src, count 30, секунд 30; метаданные: DNS службы; ссылка: url, foxpa.ws / 2010/07/21 / thwarting-the-isc-org-dns-ddos /; classtype: try-dos; sid : 21817; рев: 4;)

Во-первых, я знаю, что это старый вопрос, но ...

Я управляю своим собственным авторитетным нерекурсивным DNS-сервером в течение десятилетий, но никогда не был жертвой DDoS-атак на основе DNS - до сих пор, когда я переключился на нового провайдера. Тысячи поддельных DNS-запросов наводнили мои журналы, и я был действительно раздражен - не столько из-за воздействия на мой сервер, сколько из-за того, что он загромождал мои журналы, и из-за неприятного ощущения злоупотребления. Кажется, что злоумышленник пытается использовать мой DNS в «Атака на авторитетный сервер имен».

Поэтому я решил, что, хотя я ограничиваю рекурсивные запросы к своей внутренней сети (отрицая все остальные), я предпочитаю тратить циклы процессора на сопоставление строк в iptables, чем отправлять отрицательные ответы на поддельные IP-адреса (меньше беспорядка в моих журналах, меньше сетевой трафик и более высокий уровень моего удовлетворения).

Я начал с того, что все остальные, кажется, делают, узнайте, какие доменные имена запрашиваются, и создайте совпадение строки в этом домене с целевым DROP. Но вскоре я понял, что в итоге получу огромное количество правил, каждое из которых потребляет циклы процессора. Так что делать? Поскольку я не использую рекурсивный сервер имен, я подумал, что могу выполнить сопоставление в фактических зонах, для которых я уполномочен, и отбросить все остальное.

Моя политика по умолчанию в iptables - ACCEPT, если ваша политика - DROP, вам, вероятно, потребуется внести некоторые изменения, если вы хотите использовать следующее решение.

Я храню конфигурацию своей зоны в отдельном файле (/etc/bind/ named.conf.local), давайте воспользуемся этим в качестве примера:

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;
};

Обратите внимание на комментарий «// Private» к моим первым двум зонам, я использую его в следующем скрипте, чтобы исключить их из списка допустимых зон.

#!/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";

Запустите приведенный выше сценарий с файлом конфигурации зоны в качестве аргумента.

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

Сохраните вывод в скрипт, перенаправьте его в оболочку или скопируйте и вставьте в свой терминал, чтобы создать новую цепочку и начать фильтровать все недопустимые запросы DNS.

бегать / sbin / iptables -L DNSvalidate -nvx чтобы увидеть счетчики пакетов (и байтов) для каждого правила в новой цепочке (вы можете переместить зону с большинством пакетов в верхнюю часть списка, чтобы сделать ее более эффективной).

В надежде, что кому-то это пригодится :)

Как и вы, мне не нравится, когда мой сервер участвует в DDOS, даже если он отвечает небольшими 30-байтовыми пакетами. Кроме того, по некоторым причинам я не могу использовать iptables для блокировки поддельных запросов. Уязвимость сервера была исправлена ​​3 месяца назад, но ботнет по-прежнему отправляет на сервер поддельные запросы.

Итак, в результате вы можете использовать этот простой патч, чтобы не отправлять ОТКАЗАННЫЙ ответ (запросы будут отброшены):

--- 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

... в результате вы получите красивые журналы, например:

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

И не будет ответа «ОТКАЗАНО», если запрос пришел из неавторизованной сети / для неподдерживаемого домена.