DNS – raport miliona zapytań
Napisał: Patryk Krawaczyński
12/03/2016 w Bezpieczeństwo 2 komentarze. (artykuł nr 510, ilość słów: 1583)
P
o napisaniu podstaw bezpieczeństwa DNS zastanawiałem się, jaka jest skala problemu źle skonfigurowanych serwerów, które pozwalają na ściągnięcie całej strefy lub wykonywanie zapytań dowolnemu klientowi. Duch patryjotyzmu namawiał mnie na sprawdzenie TLD .pl, zarządzaną przez NASK. Niestety, jak zawsze polski dwór zakończył nadzieję na czwartej wymianie zdania. Dialog wyglądał tak:
– Czy istnieje możliwość otrzymania od Państwa listy wszystkich dotychczas zarejestrowanych domen .pl?
– NASK nie udostępnia tego typu informacji.
– A orientują się Państwo do jakiego organu można zwrócić się w tej sprawie?
– Te dane posiada tylko NASK.
Nie chciało mi się już próbować, czy aby jednak istnieje taka możliwość poprzez CZDS. Rozważałem już przerzucić się na zagraniczne banany, ale z pomocą przyszedł portal, który stanowi swoiste źródło wiedzy o ruchu w Internecie. Okazuje się, że Alexa bez żadnych zbędnych formalności udostępnia milion najczęściej odwiedzanych stron. To stanowi już jakąś próbkę, którą można poddać testom.
Lista domen jest w formacie CSV, więc idealnie nadaje się do obróbki za pomocą dowolnego skryptu. Po usunięciu liczb porządkowych (cat top-1m.csv | sed 's/[0-9]*,//' > top-1m.txt
) przyszedł czas na pierwszy test transferu domen (AXFR). W tym celu wykorzystałem już istniejący skrypt. Aby uzyskać przyśpieszenie testu podzieliłem plik na dwie równe części (split -l 500000 top-1m.txt
) i zmusiłem RPi2 do pracy w dzień i w nocy:
python3 axfr-test.py -i xaa -o zones.txt -l alexia.log -p 3 python3 axfr-test.py -i xab -o zones2.txt -l alexia2.log -p 3
Zanim przejdziemy do wyników tego testu spójrzmy z jaką ilością poszczególnych domen TLD (top 100) się mierzyliśmy. Usuńmy wszystkie znaki do ostatniej kropki (sed 's/^.*\././'
) i policzmy te same wstąpienia:
#!/usr/bin/env python import collections data = [line.rstrip('\n') for line in open("top-1m.txt")] stats = collections.Counter(data) for k, v in stats.iteritems(): print str(k)+","+str(v)
Dane zapisujemy ponownie w formacie CSV i importujemy je do arkuszu, sortujemy i konwertujemy do tabeli HTML. Oto wyniki:
Domena TLD: | Ilość wystąpień: |
---|---|
.com | 504490 |
.net | 51643 |
.ru | 49564 |
.org | 43697 |
.jp | 23532 |
.de | 23136 |
.in | 17400 |
.uk | 16611 |
.br | 16313 |
.it | 12062 |
.ir | 11622 |
.pl | 11581 |
.cn | 10761 |
.fr | 10481 |
.info | 10280 |
.au | 7758 |
.nl | 7514 |
.es | 6816 |
.gr | 6051 |
.co | 5515 |
.ua | 5160 |
.kr | 5021 |
.cz | 4798 |
.ca | 4592 |
.tw | 4591 |
.tr | 4405 |
.vn | 4382 |
.eu | 4196 |
.za | 4051 |
.ro | 3907 |
.tv | 3852 |
.mx | 3764 |
.id | 3640 |
.me | 3504 |
.edu | 3338 |
.biz | 3327 |
.se | 3178 |
.hu | 2916 |
.ch | 2526 |
.us | 2456 |
.ar | 2418 |
.be | 2346 |
.at | 2241 |
.dk | 2103 |
.xyz | 2074 |
.sk | 1978 |
.no | 1937 |
.io | 1873 |
.fi | 1678 |
.cc | 1657 |
.by | 1548 |
.pt | 1522 |
.my | 1517 |
.il | 1456 |
.pk | 1441 |
.hk | 1417 |
.sg | 1343 |
.kz | 1333 |
.cl | 1204 |
.az | 1186 |
.nz | 1142 |
.ie | 1139 |
.lt | 1102 |
.gov | 1098 |
.su | 1016 |
.th | 969 |
.xn--p1ai | 918 |
.bg | 900 |
.ae | 893 |
.pe | 854 |
.rs | 826 |
.hr | 772 |
.ng | 735 |
.eg | 727 |
.lv | 701 |
.ph | 675 |
.asia | 658 |
.mobi | 657 |
.sa | 645 |
.tk | 639 |
.club | 617 |
.ws | 599 |
.si | 590 |
.ee | 553 |
.pro | 521 |
.ma | 515 |
.to | 503 |
.uz | 495 |
.lk | 484 |
.pw | 482 |
.ve | 402 |
.ge | 398 |
.fm | 395 |
.xxx | 393 |
.bd | 380 |
.is | 328 |
.cat | 327 |
.am | 322 |
.nu | 316 |
.tn | 300 |
Pełną listę domen TLD można pobrać tutaj.
Z logów skryptu axfr-test.py do transferu plików stref wyciągnijmy ile zostało wykrytych cieknących domen oraz ile obsługujących je serwerów DNS:
root@erpi2:~# cat alexia.log alexia2.log > cumulative.log root@erpi2:~# grep -c "Success" cumulative.log > clean.txt root@erpi2:~# cat clean.txt | sed 's/.*@ //' | uniq | wc -l 105572 root@erpi2:~# grep "Success" cumulative.log | sed 's/ @.*//' | uniq | wc -l 61046
Liczba serwerów DNS, która pozwoliła mi na pełny transfer strefy wynosi 105.572 tysięcy (10.5%) – sprowadzając to do poziomu domen liczba ta spada do wartości 61.046 tysięcy (6.1%). Nie muszę chyba przypominać, że uzyskanie dostępu do całej strefy DNS danej domeny to tak, jak zadzwonienie do recepcji danej firmy i poproszenie sekretarki, aby podała numery telefonów do wszystkich działów – wymieniając przy tym jeszcze nazwy tych działów bo ich też nie znamy. Miła pani nie potwierdzając naszych uprawnień do tych informacji dodatkowo zdradza znam przy okazji, który dział jest nad jakim i od jakiego zależy. Nie trzeba skanować, zgadywać, knuć. Tylko planować uderzenie. Bez problemu w strefach mogłem znaleźć informacje o standardowych usługach i ich adresach:
– usługi wymiany plików (ftp, webupload, webdisk),
– wersje demonstracyjne webaplikacji dla potencjalnych klientów (demo-*),
– wersje developerskie usług (dev-*, test-*),
– systemy do monitoringu serwerów (munin, nagios, sentry),
– panele administracyjne (admin*, webmail, manager, cpanel, whm),
– systemy do dokumentacji (wiki, sphinx, rtd),
– systemy do code review oraz rozproszone systemy plików (git, svn, gerrit, fisheye),
– systemy do analizy logów (elasticsearch-head, kibana),
– rodzaje stosowanych komunikatorów (lync, hipchat, msn, xmpp, skype),
– i wiele innych mówiących same za siebie (jira, outlook, vpn, zimbra).
Ogólnie z transferu wszystkich podatnych domen zebrało się 186 MB danych gotowych do dalszej analizy. Jednak to nie wszystko. Jeśli spojrzymy na ten przypadek z poziomu serwera DNS to możemy uzyskać znacznie więcej informacji. Skoro odkryliśmy adres serwera umożliwiającego bezkarny transfer strefy jednej domeny to istnieje bardzo duże prawdopodobieństwo, że jego nieprawidłowa konfiguracja pozwala na to samo dla dowolnej innej domeny, którą obsługuje. Jak sprawdzić jakie inne domeny obsługuje dany serwer DNS? Wiele serwisów oferuje przeszukiwanie różnych swoich baz.
Przejdźmy do drugiej części testu, czyli wyszukaniu serwerów typu open resolver i sprawdźmy ile serwerów byśmy mogli wykorzystać np. do ataku dns amplification. W tym celu na podstawie AXFR-Test na szybko zmontowałem skrypt recurser.py:
#!/usr/bin/python3 import sys import dns.resolver def check_ns(domain): nslist = [] domain = domain.strip() try: nsq = dns.resolver.query(domain, "NS") for ns in nsq.rrset: nserver = str(ns)[:-1] if nserver is None or nserver == "": continue else: nslist.append(nserver) except Exception as e: pass if not nslist: pass else: print("---") print("Checking: " + domain + " with NS servers:", nslist) resolve_ns(nslist) def resolve_ns(nslist): iplist = [] rns = dns.resolver.Resolver() for ns in nslist: try: nsip = rns.query(ns, "A") for rdata in nsip: print("NS: " + ns + " have IP: " + rdata.address) iplist.append(rdata.address) except Exception as e: pass if not iplist: pass else: check_recurse(iplist) def check_recurse(iplist): for ip in iplist: try: rns = dns.resolver.Resolver(configure=False) rns.timeout = 2 rns.lifetime = 5 rns.nameservers = [ip] check = rns.query("nfsec.pl", "A") for rdata in check: print("Knocking to: " + ip + " = OpenResolver?") print(rdata) except dns.resolver.NoNameservers: print("Knocking to: " + ip + " = ClosedResolver!") except dns.resolver.NoAnswer: print("Knocking to: " + ip + " = SilenceOnTheWire!") except Exception as e: pass print("---") if __name__ == '__main__': if len(sys.argv) < = 1: print("Usage: " + sys.argv[0] + " domain.com") else: check_ns(sys.argv[1])
Obliczmy ile serwerów rozwiązało domenę nfsec.pl i zwróciło jej prawidłowy adres IP (wiele serwerów DNS odpowiada na zewnętrzne zapytania o inne domeny, ale zwraca adres IP serwera www typu catch-all w ten sposób przekierowując użytkownika na strony z podstawionymi treściami):
for i in `cat top-1m.txt`; do python3 recurser.py $i 2>&1 >> openresolvers.log; done grep -B 1 '37.187.104.217' openresolvers.log | grep -v "\--" > clean.txt grep 'OpenResolver?' clean.txt | uniq | wc -l 49519
Razem wyszło 49.519 tysięcy (4.95%) otwartych serwerów, co daje już mały botnet do przeprowadzania ataków. Skoro zebraliśmy już te wszystkie dane wygenerujmy listę top 100 najczęściej używanych serwerów DNS z wszystkich domen, które odpowiedziały na zapytania i zwróciły nam ich listę:
grep '\[' openresolvers.log | sed 's/.*\[/[/' > topdns.txt
#!/usr/bin/env python import collections import ast topdns = [] for line in open("topdns.txt"): line = ast.literal_eval(line) topdns.extend(line) stats = collections.Counter(topdns) for k, v in stats.iteritems(): print str(k)+","+str(v)
Analogicznie – dane za pomocą przekierowania strumienia zapisujemy ponownie w formacie CSV, importujemy je do arkuszu, sortujemy i konwertujemy do tabeli HTML. Oto wyniki:
Serwer DNS: | Ilość wystąpień: |
---|---|
f1g1ns1.dnspod.net | 10518 |
f1g1ns2.dnspod.net | 10516 |
ns2.bluehost.com | 9374 |
ns1.bluehost.com | 9373 |
dns4.registrar-servers.com | 7207 |
dns5.registrar-servers.com | 7206 |
dns3.registrar-servers.com | 7206 |
dns2.registrar-servers.com | 7206 |
dns1.registrar-servers.com | 7204 |
ns1.dreamhost.com | 6667 |
ns2.dreamhost.com | 6667 |
ns3.dreamhost.com | 6663 |
dns3.name-services.com | 5589 |
dns2.name-services.com | 5589 |
dns1.name-services.com | 5589 |
dns4.name-services.com | 5588 |
dns5.name-services.com | 5573 |
ns2.dns.ne.jp | 5339 |
ns1.dns.ne.jp | 5331 |
ns1.dnsmadeeasy.com | 4947 |
ns3.dnsmadeeasy.com | 4932 |
ns0.dnsmadeeasy.com | 4915 |
ns2.dnsmadeeasy.com | 4889 |
ns4.dnsmadeeasy.com | 4867 |
dns2.stabletransit.com | 4680 |
dns1.stabletransit.com | 4677 |
ns1.digitalocean.com | 4660 |
ns2.digitalocean.com | 4657 |
ns3.digitalocean.com | 4632 |
a.dns.gandi.net | 4584 |
b.dns.gandi.net | 4583 |
c.dns.gandi.net | 4583 |
02.dnsv.jp | 4403 |
01.dnsv.jp | 4403 |
ns1.mediatemple.net | 4401 |
ns2.mediatemple.net | 4399 |
ns.rackspace.com | 4272 |
ns2.rackspace.com | 4267 |
ns11.dnsmadeeasy.com | 4198 |
ns12.dnsmadeeasy.com | 4196 |
ns10.dnsmadeeasy.com | 4189 |
ns13.dnsmadeeasy.com | 4178 |
ns14.dnsmadeeasy.com | 4139 |
ns15.dnsmadeeasy.com | 4097 |
03.dnsv.jp | 3960 |
04.dnsv.jp | 3960 |
ns1.xserver.jp | 3856 |
ns3.xserver.jp | 3856 |
ns2.xserver.jp | 3856 |
dns.technorail.com | 3851 |
dns2.technorail.com | 3851 |
dns4.arubadns.cz | 3830 |
dns3.arubadns.net | 3830 |
ns5.xserver.jp | 3792 |
ns4.xserver.jp | 3791 |
ns1.linode.com | 3449 |
ns2.linode.com | 3446 |
ns3.linode.com | 3446 |
ns4.linode.com | 3441 |
ns5.linode.com | 3424 |
dns2.yandex.net | 3257 |
dns1.yandex.net | 3257 |
ns4-l2.nic.ru | 3128 |
ns2.beget.ru | 3087 |
ns1.beget.ru | 3087 |
ns2.beget.pro | 3016 |
ns1.beget.pro | 3016 |
ns8-l2.nic.ru | 3015 |
ns2.reg.ru | 3011 |
ns1.reg.ru | 3011 |
ns4-cloud.nic.ru | 2938 |
ns8-cloud.nic.ru | 2913 |
dns9.hichina.com | 2874 |
dns10.hichina.com | 2874 |
ns65.domaincontrol.com | 2828 |
ns66.domaincontrol.com | 2825 |
ns09.domaincontrol.com | 2781 |
ns10.domaincontrol.com | 2773 |
dns.fastdns24.com | 2710 |
dns3.fastdns24.eu | 2696 |
dns2.fastdns24.org | 2695 |
dns4.fastdns24.link | 2694 |
ns3-l2.nic.ru | 2682 |
ns3.timeweb.org | 2589 |
ns2.timeweb.ru | 2589 |
ns1.timeweb.ru | 2589 |
ns4.timeweb.org | 2588 |
dns.home.pl | 2508 |
dns2.home.pl | 2508 |
dns3.home.pl | 2507 |
ns51.domaincontrol.com | 2421 |
ns52.domaincontrol.com | 2411 |
ns02.domaincontrol.com | 2389 |
ns01.domaincontrol.com | 2389 |
ns2.namespace4you.de | 2272 |
ns.namespace4you.de | 2272 |
ns2.value-domain.com | 2264 |
ns1.value-domain.com | 2263 |
dns1.namecheaphosting.com | 2188 |
dns2.namecheaphosting.com | 2187 |
Pełną listę popularności serwerów znajdziemy tutaj.
Czy około od 5% do 10% “wadliwych” konfiguracji serwerów DNS na 1 milion domen to dużo, czy mało? Pozostawiam do oceny Czytelnikowi.
Więcej informacji: List of Internet top-level domains, How to Download a List of All Registered Domain Names
Bardzo dobry pomysł rozszerzający powyższy test zaproponował Błażej Miga: aby oprócz sprawdzania serwerów NS zwracanych oficjalnie dla domeny – sprawdzać dodatkowo serwer znajdujący się w rekordzie SOA, w którym znajdują się zupełnie inne / zapomniane serwery DNS.
Nowe źródło domen od Cisco Umbrella.