Erste Ergebnisse vom DNS Dampening
Software für DNS Dampening zu entwickeln, ist ein iterativer Prozeß. Ob die Annahmen dann auch im laufenden Code die erwarteten Ergebnisse liefern, ist nur durch Testen unter realen Bedingungen zu ermitteln.
Release often, release early
Die ersten überraschenden Auswirkungungen meiner Patchveröffentlichung waren:
- Ich wurde frühzeitig auf konzeptionelle Fehler hingewiesen. Im Grundsatz stimme ich den Ausführungen ja zu, bin aber noch beim Sammeln eigener Erfahrungen. Habt also Geduld mit meinem Starrsinn.
- Man zeigte mir einen katastrophalen Programmierfehler in der Heapverwaltung, den ich sicher nur nach Tagen selbst gefunden hätte.
- Es gab Zuspruch und Hinweise, was jetzt (in diesem frühen Stadium) schon besser zu machen geht. Das hilft mir sehr.
- Ich habe viel über interne Prozesse bei uns hier gelernt. Mehr als ich wissen wollte.
Danke dafür.
Hätte ich später veröffentlicht, hätte ich vermutlich andere Wege eingeschlagen und mein Code wäre mir zu schade zum Wegwerfen gewesen. Wahrscheinlich hätte ich aufgegeben.
Erste Beobachtungen
Nach dem Rollout auf der betroffenen Maschine, schaute alles erstmal prima aus. Die Last ging runter, ich war zufrieden.
Es gab allerdings einige Eigentümlichkeiten, so wanderten die IPv6 fähigen Resolver von DTAG, HE und SiXXS ins Dampening. Genauere Informationen fehlten noch. Also wurde der Patch um ein Debugging erweitert.
--- bind-9.9.1-P3/bin/named/query.c 2012-08-24 06:43:09.000000000 +0200 +++ bind-9.9.1-P3-dampening/bin/named/query.c 2012-09-25 22:37:26.000000000 +0 @@ -7146,7 +7148,7 @@ } static inline void - log_query(ns_client_t *client, unsigned int flags, unsigned int extflags) { + log_query(ns_client_t *client, unsigned int flags, unsigned int extflags, unsigned int penalty) { char namebuf[DNS_NAME_FORMATSIZE]; char typename[DNS_RDATATYPE_FORMATSIZE]; char classname[DNS_RDATACLASS_FORMATSIZE]; @@ -7165,7 +7167,7 @@ isc_netaddr_format(&client->destaddr, onbuf, sizeof(onbuf)); ns_client_log(client, NS_LOGCATEGORY_QUERIES, NS_LOGMODULE_QUERY, - level, "query: %s %s %s %s%s%s%s%s%s (%s)", namebuf, + level, "query: %s %s %s %s%s%s%s%s%s (%s) %u", namebuf, classname, typename, WANTRECURSION(client) ? "+" : "-", (client->signer != NULL) ? "S": "", (client->opt != NULL) ? "E" : "", @@ -7173,7 +7175,7 @@ "T" : "", ((extflags & DNS_MESSAGEEXTFLAG_DO) != 0) ? "D" : "", ((flags & DNS_MESSAGEFLAG_CD) != 0) ? "C" : "", - onbuf); + onbuf, penalty); } static inline void @@ -7228,6 +7230,7 @@ unsigned int saved_extflags = client->extflags; unsigned int saved_flags = client->message->flags; isc_boolean_t want_ad; + unsigned int penalty; CTRACE("ns_query_start"); @@ -7282,6 +7285,14 @@ } /* + * Update the penalty and report the current state + */ + if (dampening_query(client, &penalty) == DAMPENING_SUPPRESS) { + query_next(client, DNS_R_DROP); + return; + } + + /* * Get the question name. */ result = dns_message_firstname(message, DNS_SECTION_QUESTION); @@ -7306,7 +7317,7 @@ } if (ns_g_server->log_queries) - log_query(client, saved_flags, saved_extflags); + log_query(client, saved_flags, saved_extflags, penalty); /* * Check for multiple question queries, since edns1 is dead.
In dampening_query den Wert zu speichern, ist dann trivial.
Auffällig war, daß das Arbeiten am System insgesamt zäh wurde. Die Bash fragt immer den Hostname für den Prompt ab, also mal strace -f hostname -f aufgerufen. Es gibt erhebliche Timeouts bei der Namensauflösung über Localhost. Also mal auf die Schnelle etwas der Art Strafpunkte über Zeit hingerotzt, sorry.
Zuerst einmal die auffälligen Spikes am späten Abend:
- Gegen 22:57 hatte ich ein tcpdump explizit mit Namensauflösung laufen lassen, um zu verifizieren, daß die Namensauflösung überhaupt noch funktioniert.
- Die breiten Balken gegen 22:05 und 23:43 sind Brute Force Angriffe via SSH. Die SSH prüft die Konsistenz von PTR und A(AAA) vor dem Anmeldeversuch.
- Um Mitternacht rennt das System ungebremst ins Dampening.
Zuerst die Balken etwas breiter aufgelöst (nur das Zeitfenster zwischen 23:02:50 und 23:07:00):
Es ist sehr schön zu sehen, wie die Strafpunkte sich aufsummieren und wieder genullt werden. Dampening funktioniert, wenn auch irgendwie invers. Möglicherweise fällt der Eintrag immer wieder aus der Tabelle heraus. Das geht so.
Ein Blick auf den Vorfall um Mitternacht offenbart, daß Testscripte die interne Konsistenz der gehosteten Zonen prüfen: Stimmen Vor- und Rückwärtseinträge, ist der Nameserver auch in der Parentzone gelistet? Ja, es kommen binnen weniger Minuten tatsächlich 40000 Anfragen zusammen. Das geht so nicht.
Die offenkundige Lösung besteht darin, die lokal erreichbaren Netze vom Dampening auszunehmen. Denn wer Spoofing für diese Adressen annimmt, ist nicht besser als die Angreifer, die anderswo die Verletzung von BCP38 ausnutzen.
Ein anderer Fall war ein Client, der es mit einigen wenigen Anfragen in 20 Minuten auf über 1000 Strafpunkte schaffte. Den Graphen mußte ich lange ansehen, bis ich die beiden Linien einzeichnen und mich an den Kopf fassen konnte:
Anstatt die Punkte verfallen zu lassen (wie die grüne Linie zeigt) stiegen die Punktzahlen expotentiell. Und tatsächlich war mir im Code das notwendige Minuszeichen im Exponenten durch die Lappen gegangen. Was für ein grober Programmierfehler!
Allerdings war der Fehler auch sehr nützlich: So konnte ich testen, wie sich das System verhält, wenn alle IPs als Angriff zu betrachten ist. Glücklicherweise hatte ich mich schon auf feste Ressourcennutzung festgelegt. Andernfalls wäre die Kiste geplatzt.
Bugfix in Produktion
Nach diesen ersten Fehlerbehebungen fühlt sich das System gut an und die Statistiken sind vielversprechend:
Nach einer Anlernphase verfallen die Punkte in Wellen und die Clients werden wieder freigegeben. Da die Angriffsraten pro Ziel nicht gleichbleibend sind, verlaufen sich die Wellen nach und nach.
Obwohl die Punkte aktuell pro IP gesammelt werden, werden sie doch pro Anfragetyp unterschiedlich behandelt. Die Frage ist also, ob normale Anfragen durch die Angriffe mit ANY Pakteten in Mitleidernschaft gezogen wurden. Dazu mal die Strafpunkt über die Zeit pro Querytype:
Keine der normalen Anfragen wird von einer IP gestellt, die ins Dampening gerutscht ist! Der Normalbetrieb ist ungestört. Im Vergleich dazu die Strafpunkt unter Berücksichtigung der Angriffe:
Angriffe werden weiterhin binnen 40 Pakete erkannt und geblockt. Man sieht sehr schön die Stufen, die durch die Wiederholungen eines ANY-Paketes mit gleicher ID und großer Antwort. Die Datenrate gestattet wieder einen normalen Betrieb.
Abgefallen ist eine Statistik der Anfragetypen des DNS im praktischen Betrieb.
165976 A 56926 ANY 46890 AAAA 36527 PTR 11336 MX 8025 SOA 4958 DNSKEY 2974 NS 2786 SRV 2293 TXT 587 A6 568 SPF 543 DLV 170 DS 105 CNAME 4 HINFO 4 AXFR 3 TLSA 2 NAPTR 2 RRSIG 1 NSEC
Erfreulich ist die Verbreitung von TLSA. Weniger erfreulich sind die Anfragen nach A6. Die DLV Anfragen sind normal, weil ich eine solche betreibe. Aber auch das Verhältnis von IPv6 uind IPv4 Anfragen ist Grund zur Freude.
Offene Baustellen
Das System ist erstmal grundsätzlich einsatzbereit. Aber es gibt genug zu tun:
- Ist der eingesetzte Algorithmus optimal? Sollte man nicht besser mit Ringpuffer für die Alterung und Hashtable für die Suche arbeiten?
- Es fehlen die Konfigurierbarkeit, ACLs etc.
- Was passiert, wenn ein Angriff mit der IP eines Resolvers eines großen ISP stattfindet? Sind dann die Zonen für die Kunden des ISP unerreichbar?
- Wie verhält sich der Ansatz im Vergleich zu anderen Ansätzen? Wo liegen die Vor- und Nachteile der einzelnen Lösungen?