Po pierwsze wiem, że to stare pytanie, ale ...
Od dziesięcioleci prowadzę własny autorytatywny, nierekurencyjny serwer DNS, ale nigdy nie padłem ofiarą ataków DDoS opartych na DNS - do tej pory, kiedy przestawiłem się na nowego dostawcę usług internetowych. Tysiące sfałszowanych zapytań DNS zalało moje logi i bardzo mnie to zirytowało - nie tyle na temat wpływu na mój serwer, co raczej zaśmiecenie moich logów i niewygodne uczucie bycia wykorzystywanym. Wygląda na to, że atakujący próbuje użyć mojego DNS w „ autorytatywnym ataku na serwer nazw ”.
Pomyślałem więc, że chociaż ograniczam zapytania rekurencyjne do mojej sieci wewnętrznej (zaprzeczając wszystkim innym), wolę spędzać cykle procesora na dopasowywaniu ciągów znaków w iptables niż wysyłaniu negatywnych odpowiedzi na sfałszowane adresy IP (mniej bałaganu w moich logach, mniej ruch w sieci i mój własny wyższy poziom satysfakcji).
Zacząłem od zrobienia tego, co wszyscy inni robią , aby dowiedzieć się, które nazwy domen są wyszukiwane, i utworzyłem ciąg znaków w tej domenie z docelowym DROP. Ale wkrótce zdałem sobie sprawę, że skończy się z ogromną ilością reguł, z których każda pochłania cykle procesora. Co więc robić? Ponieważ nie prowadzę rekurencyjnego serwera nazw, pomyślałem, że mogę dopasować rzeczywiste strefy, dla których jestem autorytatywny, i porzucić wszystko inne.
Moją domyślną polityką w iptables jest AKCEPTUJ, jeśli twoją polisą jest DROP, prawdopodobnie musisz wprowadzić pewne zmiany, jeśli chcesz skorzystać z następującego rozwiązania.
Trzymam konfigurację strefy w osobnym pliku (/etc/bind/named.conf.local), wykorzystajmy to jako przykład:
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;
};
Zwróć uwagę na komentarz „// Private” do moich pierwszych dwóch stref. Korzystam z tego w poniższym skrypcie, aby wykluczyć je z listy prawidłowych stref.
#!/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";
Uruchom powyższy skrypt z plikiem konfiguracji strefy jako argumentem.
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
Zapisz dane wyjściowe w skrypcie, potokuj je do powłoki lub skopiuj i wklej w terminalu, aby utworzyć nowy łańcuch i rozpocząć filtrowanie wszystkich nieprawidłowych zapytań DNS.
uruchom / sbin / iptables -L DNSvalidate -nvx,
aby zobaczyć liczniki pakietów (i bajtów) dla każdej reguły w nowym łańcuchu (możesz chcieć przenieść strefę z większością pakietów na górę listy, aby była bardziej wydajna).
Mam nadzieję, że ktoś może uznać to za przydatne :)