Morgens, 08:00 in Deutschland. pieeep pieeep Eine SMS kommt rein, “Das Forum ist down!”. Aufgestanden, geguckt, joh, stimmt, und zwar seit ca. 04:55, wenn ich meinem Icinga glauben darf. munin zeigte mir folgendes Bild der Trauer:
Bestandsaufnahme
Was war los? Zuerst mal htop aufgerufen um zu schauen, ob irgendwelche Prozesse Amok liefen. Die auf diesem Server eingesetzten Software ist “aus Gründen”™ relativ alt und hat die blöde Tendenz, die MySQL in ein Deadlock zu prügeln, und dann geht gar nichts mehr.
Wider Erwarten war die Kiste völlig idle. Keine Apache-Prozesse im htop, keine MySQL-Prozesse. Hmmm.1
host:~$ ps -aef | grep -c apache2 389 host:~$
Ok, das entspricht den konfigurierten MaxClients plus ein bisschen Maintenance, ebenfalls mit dem Wort apache im Namen. D.h., maximale Anzahl an Serverprozessen oben, aber kein einziger zieht Last?
host:~$ netstat -an | grep :80 | grep EST ... grosse Liste ESTABLISHED connections snipped... host:~$
Oha! Hmmm, gibt es bestimmte IPs die besonders viele Connections offen halten?
host:~$ netstat -an | perl -lane '/:80/ && /ESTABLISHED/ && $F[4]=~/([^:]+):/ && print $1' | sort | uniq -c
147 1.2.3.4
120 2.3.4.5
...
host:~$
Ok, 147 offene Connects von einer einzelnen Adresse sind definitiv ungewoehnlich. Ein nslookup ergab den Exit eines Tor-Knotens. Die nachfolgende Adresse lieferte gar keinen Namen. Danach noch weitere Tor-Adressen.
tcpdump -n host 1.2.3.4 zeigte keinerlei Traffic. Die Verbindungen waren also vollständig idle.
Das ist die einfachste Möglichkeit, einen Webserver lahm zu legen. Einfach eine große Menge an Verbindungen öffnen und keinerlei Daten über diese austauschen. Ein vollständiger Webserver-Prozess ist pro Verbindung blockiert, und der fehlt dann für sinnvolle Anfragen. Eine große Anzahl an Idle-Verbindungen kann jeder Client offen halten, das kostet keinerlei Last oder Bandbreite.
Gegenmaßnahmen
Im ersten Schritt habe ich erstmal die Verbindungen zu diesen Adressen gekappt. Ich habe in der INPUT-Chain des iptables eine eigene sub-Chain zum Blacklisten von einzelnen Hosts.
host:~$ iptables -A blklist -s 1.2.3.4 -j DROP host:~$ apache2ctl restart host:~$
Der Restart ist notwendig, damit der Apache die bestehenden offenen Verbindungen dicht macht, da sie sonst erst in den Timeout laufen müssten.
Jetzt habe ich das oben stehende netstat-Kommando ein paar mal im Sekundenabstand abgesetzt, und es passierte was ich erwartet hatte. Sofort kamen von einer neuen Adresse wieder mehrere zig Verbindungen um den Webserver umgehend wieder zu blockieren.
Jetzt standen 2 Gegenmaßnahmen an.
- Limitieren der gleichzeitigen offenen Verbindungen von einer einzelnen IP-Adresse mittels iptables
- Aktivierung von mod-evasive im Apache
Punkt 1 ist am schnellsten umsetzbar:
iptables
host:~$ iptables -N ddos host:~$ iptables -A ddos -j LOG --log-prefix "DDOS: " host:~$ iptables -A ddos -j REJECT --reject-with icmp-host-unknown host:~$ iptables -I INPUT 3 -p tcp --syn --dport 80 -m connlimit --connlimit-above 8 --connlimit-mask 24 -j ddos host:~$
Damit wird
- eine neue Chain DDOS angelegt, die die Adresse als geblockt protokolliert und…
- …die gesamte C-Klasse des Clients mit Host unreachable blockt
- aus der INPUT-Chain wird bei mehr als 8 offenen Verbindungen beim nächsten syn-State diese Block-Chain DDOS angesprungen und weitere Verbindungen ausgesperrt.
Auf eine TARPIT habe ich hier bewusst verzichtet, um nicht valide User mit getunten Browsern die besonders viele Verbindungen öffnen, oder z.B. die Telekom-Proxies, langfristig auszusperren.
Apache mod-evasive
Mit mod-evasive hatte ich bisher auch noch keine Erfahrungen ausser dem Wissen, dass es existiert. Hierzu vielen Dank an all die 14-Jährigen, die “Howtos” auf ihren Websites posten, in denen Sie dieselben Infos posten, wie andere 14-Jährige auf deren Homepages sie ihre Anleitungen selber erst ergoogelt haben. Das führt nämlich dazu, dass man 5 Minuten seines Lebens verschwendet, bis man endlich eine Site findet, die die konfigurationsparameter dann auch tatsächlich mal beschreibt, statt wieder und wieder das Default-Set einfach in den Webserver zu ballern, ohne einen Hinweis (und vermutlich auch eine Ahnung), was der ganze Kram überhaupt bedeutet.
Um anderen diese 5min zu ersparen: Eine Parameterbeschreibung findet sich auf linode.com
As a public service, um dem geneigten User weitere 5min verschwendeter Lebenszeit zu ersparen: mod-evasive Homepage, denn 14-jährige Howto-Schreiber haben es i.d. Regel auch nicht nötig, auf die Original-Quelle zu verlinken.
Tatsächlich, unabhängig von obigem Rant über 14-jährige ist das Default-Set an Konfigurationsparametern einigermaßen sinnvoll, aber das weiss man eben nicht, bevor man es nicht verstanden hat.
mod-evasive ist noch in der Lage, Mails über ausgesperrte Adressen zu versenden, und externe Kommandos aufzurufen, z.B. um Adressen via iptables direkt beim syn zu blocken. Ich habe von beidem abgesehen.
Ergebnis
Die 1. Maßnahme hat bereits ausgereicht, um den Angriff abzuwehren. Die 2. belasse ich dennoch, einfach um mich nicht auf eine einzelne Technologie zu verlassen.
Bei einem groß angelegten Angriff wären diese Maßnahmen sicher nicht ausreichend gewesen. Bei dem halben Dutzend gleichzeitiger Clients (mit wechselnden Adressen) für diese Mini-DDOS haben sie aber bequem ausgereicht und der Server war wieder erreichbar und performte akzeptabel bis gut.
Es gibt natürlich Vermutungen über die Quelle des Angriffs. Der Angriff korrelliert zeitlich mit ausgesprochenen Sperren am Vortag, ein Beweis lässt sich hier aber nicht finden, aufgrund der Anonymisierung durch Tor und andere Dienste.
Fakt ist: Einen mittelgroßen root-Server kann man auch ohne Ausnutzung von Sicherheitslücken schon mit einer normalen DSL-Leitung relativ platt machen. Im Grunde ist es ein Wunder, dass so viele Jahre ohne Angriff vergangen sind. Ob ich diese Maßnahmen bei mir auch deploye, habe ich noch nicht entschieden. Mein Setup sieht durch den vorgeschalteten nginx etwas anders aus und ist nicht ganz so empfindlich gegenüber vielen offenen Idle-Connections. Aber eine Überlegung ist es sicher wert.
nstop
Als Nebenprodukt ist noch das Script nstop heraus gekommen. Ich habe das Script, dass ich während des Angriffs geschrieben habe, noch etwas aufgebohrt, und es tut bereits was es soll, ist aber noch verbesserungswürdig.
nstop ist ein ganz simples top für offene Netzwerkverbindungen. Es sortiert sie nach Anzahl und bietet zusätzlich eine kill-Möglichkeit.
Vielleicht nützt es ja mal jemandem. Patches welcome.
Mon Jun 25 00:09:26 2012 ID Count IP Hostname 0 5 176.9.148.6 behemoth.lamertz.net 1 3 173.194.35.144 muc03s01-in-f16.1e100.net 2 2 173.194.70.125 fa-in-f125.1e100.net 3 1 69.171.227.65 channel-hp-12-01-snc7.tfbnw.net 4 1 95.100.47.144 Cannot resolv 5 1 141.101.124.178 Cannot resolv 6 1 173.192.42.178 173.192.42.178-static.reverse.softlayer.com 7 1 173.194.35.4 mil01s16-in-f4.1e100.net 8 1 173.194.70.17 fa-in-f17.1e100.net 9 1 192.168.0.3 Cannot resolv 10 1 199.30.80.32 www.sfe.sv4.as53922.stumbleupon.net 11 1 199.47.217.149 sjc-not10.sjc.dropbox.com 12 1 199.59.148.139 r-199-59-148-139.twttr.com 13 1 199.59.149.232 r-199-59-149-232.twttr.com 14 1 209.85.148.100 fra07s07-in-f100.1e100.net 15 1 209.85.148.102 fra07s07-in-f102.1e100.net 16 1 209.85.148.138 fra07s07-in-f138.1e100.net 17 1 213.199.179.147 Cannot resolv s (5) - new refresh delay; k - kill connections of ID; q - quit
1 Alle Code-Snippets hier entsprechen nicht dem Live-Status sondern sind nachträglich aus dem Kopf zusammen editiert. Hostnamen, IP-Adressen und dergleichen sind anonymisiert.

