Primeiro, eu sei que essa é uma pergunta antiga, mas ...
Estou executando meu próprio servidor DNS autoritativo e não recursivo há décadas, mas nunca fui vítima de nenhum ataque DDoS baseado em DNS - até agora, quando mudei para um novo ISP. Milhares de consultas DNS falsificadas inundaram meus logs e fiquei muito irritado - não tanto pelo impacto no meu servidor, mas pelo fato de ele atrapalhar meus logs e pela sensação desconfortável de ser abusado. Parece que o invasor tenta usar meu DNS em um " ataque do servidor de nomes autoritário ".
Então, imaginei que, embora eu limite consultas recursivas à minha rede interna (negando todas as outras), prefiro gastar meus ciclos de CPU na correspondência de cadeias de caracteres em tabelas de ip do que enviando respostas negativas aos endereços IP falsificados (menos confusão nos meus logs, menos tráfego de rede e um nível mais alto de satisfação).
Comecei fazendo o que todo mundo parece fazer , descubra quais nomes de domínio são consultados e criei uma correspondência de string nesse domínio com uma DROP de destino. Mas logo percebi que acabaria com uma enorme quantidade de regras, cada uma delas consumindo ciclos de CPU. Então o que fazer? Como não executo um servidor de nomes recursivo, achei que poderia fazer a correspondência nas zonas reais para as quais sou autoritário e largar todo o resto.
Minha política padrão no iptables é ACEITAR, se sua política for DROP, você provavelmente precisará fazer alguns ajustes se desejar usar a seguinte solução.
Eu mantenho minha configuração de zona em um arquivo separado (/etc/bind/named.conf.local), vamos usar isso como um exemplo:
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;
};
Observe o comentário “// Privado” nas minhas duas primeiras zonas, eu uso isso no script a seguir para excluí-las da lista de zonas válidas.
#!/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";
Execute o script acima com o arquivo de configuração da zona como argumento.
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
Salve a saída em um script, coloque-o em um shell ou copie e cole no seu terminal para criar a nova cadeia e começar a filtrar todas as consultas DNS inválidas.
execute / sbin / iptables -L DNSvalidate -nvx
para ver os contadores de pacotes (e bytes) em cada regra da nova cadeia (você pode mover a zona com a maioria dos pacotes para o topo da lista para torná-la mais eficiente).
Na esperança de que alguém possa achar isso útil :)