Tout d'abord, je sais que c'est une vieille question mais ...
Je gère mon propre serveur DNS non récursif faisant autorité depuis des décennies, mais je n'ai jamais été victime d'attaques DDoS basées sur DNS - jusqu'à présent, lorsque je suis passé à un nouveau FAI. Des milliers de requêtes DNS usurpées ont inondé mes journaux et je me suis vraiment ennuyé - pas tant sur l'impact sur mon serveur, plutôt sur le fait qu'il encombrait mes journaux et le sentiment inconfortable d'être abusé. Il semble que l'attaquant essaie d'utiliser mon DNS dans une « attaque de serveur de noms faisant autorité ».
J'ai donc pensé que, même si je limitais les requêtes récursives à mon réseau interne (en refusant tous les autres), je préférais passer mes cycles de processeur à faire correspondre les chaînes dans iptables plutôt qu'à renvoyer des réponses négatives aux adresses IP usurpées (moins d'encombrement dans mes journaux, moins trafic réseau et un niveau de satisfaction plus élevé).
J'ai commencé par faire comme tout le monde semble le faire , savoir quels noms de domaine sont interrogés et créé une correspondance de chaîne sur ce domaine avec un DROP cible. Mais je me suis vite rendu compte que je me retrouverais avec une énorme quantité de règles, chacune consommant des cycles CPU. Alors que faire? Étant donné que je n'exécute pas de serveur de noms récursif, j'ai pensé que je pourrais faire la correspondance sur les zones réelles pour lesquelles je fais autorité et supprimer tout le reste.
Ma politique par défaut dans iptables est ACCEPT, si votre politique est DROP, vous devrez probablement faire quelques ajustements si vous souhaitez utiliser la solution suivante.
Je garde ma configuration de zone dans un fichier séparé (/etc/bind/named.conf.local), utilisons ceci comme exemple:
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;
};
Notez le commentaire «// Private» sur mes deux premières zones, je m'en sers dans le script suivant pour les exclure de la liste des zones valides.
#!/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";
Exécutez le script ci-dessus avec le fichier de configuration de zone comme 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
Enregistrez la sortie dans un script, dirigez-la vers un shell ou copiez-collez-la dans votre terminal pour créer la nouvelle chaîne et commencer à filtrer toutes les requêtes DNS non valides.
exécutez / sbin / iptables -L DNSvalidate -nvx
pour voir les compteurs de paquets (et d'octets) sur chaque règle de la nouvelle chaîne (vous voudrez peut-être déplacer la zone avec la plupart des paquets en haut de la liste pour la rendre plus efficace).
En espérant que quelqu'un puisse trouver cela utile :)