HTTP-Flood-Angriff: So funktioniert er, so prüfen Sie Ihre Abwehr

Serverschrank, getroffen von dichten parallelen identischen Lichtstrahlen – ein HTTP Flood aus lauter gültigen Anfragen

· 14 Min. Lesezeit

Ein HTTP-Flood-Angriff enthält nichts Fehlerhaftes. Jede einzelne Anfrage ist vollständiges, gültiges, sauber aufgebautes HTTP – genau der Datenverkehr, den Ihr Server beantworten soll. Einzeln betrachtet lässt sich keine davon von der Anfrage einer Kundin unterscheiden, die ihren Kontostand abruft. In der Summe ergeben sie mehr Arbeit, als Ihr System leisten kann.

Damit ist das die eine Angriffsklasse, in der Ihre vorderste Abwehr überhaupt ein Signal hat, an dem sie ansetzen kann. Das verschiebt die Frage. Sie lautet nicht mehr, ob der DDoS-Schutz den Verkehr sieht, sondern ob der Schwellenwert für die Anfragerate richtig gewählt ist, ob der getroffene Endpunkt cachefähig ist und ob das kleinste Limit in Ihrer Kette dort liegt, wo Sie es vermuten. Das sind alles Fragen der Konfiguration, und Konfigurationen veralten still.

Das Wichtigste in Kürze

  • Nichts daran ist ungültig. Die Last entsteht daraus, dass sehr viele Anfragen gleichzeitig in Bearbeitung bleiben. Es gibt keine Signatur, auf die eine Regel passt, und kein fehlerhaftes Feld, das man ablehnen könnte.
  • Der Schaden reicht weiter als bei jeder anderen Klasse. Er geht am Webserver vorbei bis zu den Anwendungs-Threads und von dort bis in den Datenbank-Verbindungspool, der meist nur wenige hundert Verbindungen fasst.
  • Am Netzrand fällt die Entscheidung. Rate Limiting, Scrubbing und Caching können diesen Angriff stoppen. Ob sie es bei Ihnen tun, hängt von Zahlen in einer Konfigurationsdatei ab.
  • Die Verbindungswiederverwendung verändert, was Sie prüfen. Werden Sockets wiederverwendet, konzentriert sich die Last auf die Verarbeitung der Anfragen. Schalten Sie sie ab, wandert ein Teil davon zurück auf den Verbindungsaufbau.
  • Die Erkennung scheitert an zu vielen Daten, nicht an zu wenigen. Das Log-Volumen wächst mit dem Anfragevolumen: Ingest-Limits werden erreicht, Ereignisse verworfen, und der Alarm, auf den es ankam, geht als einer von vierhundert ein.

Dieser Beitrag gehört zu einer Serie über die zehn Simulationstypen, die Obsidio durchführt – einer je Angriff. Sie setzen an ganz unterschiedlichen Stellen an, und genau deshalb braucht ein Testprogramm Abdeckung über die Kategorien hinweg statt Masse in einer einzigen. Wie sie zusammenspielen und wie Sie einen ersten Durchlauf aufbauen, steht in unserem Leitfaden dazu, wie Sie Ihren DDoS-Schutz testen.

Die zehn Simulationstypen

Ein Beitrag je Typ, gruppiert wie in der Plattform.

Angriffe auf der Anwendungsschicht (L7)

Langsame Angriffe

Verbindungserschöpfung

Protokollspezifisch & skriptgesteuert

Was ein HTTP-Flood-Angriff tatsächlich tut

Der HTTP-Flood von Obsidio lässt auf jedem Worker mehrere unabhängige Anfrageschleifen laufen. Eine Schleife setzt eine Anfrage ab, wartet auf die Antwort und schickt die nächste in dem Moment los, in dem die Antwort eintrifft. Keine Pause, keine Denkzeit, keine Zufallsstreuung: Methode, URL und Body gehen genau so hinaus, wie sie konfiguriert sind, jedes Mal.

Der Druck entsteht aus der Parallelität, nicht aus der Bandbreite. Zwanzig Schleifen pro Worker, über alle Worker eines Durchlaufs, halten sehr viele Anfragen im selben Augenblick in Bearbeitung, und jede belegt etwas auf Ihrer Seite: einen Platz in der Anfragewarteschlange, einen Anwendungs-Thread, eine Verbindung aus dem Pool, eine Abfrage, die die Datenbank planen und ausführen muss. Ein volumetrischer Angriff füllt Ihre Leitung; dieser hier verbraucht Ihre Kapazität, Arbeit zu leisten.

Stellen Sie sich zwanzig Leute vor, die gleichzeitig an einer Ladentheke stehen und in dem Moment, in dem sie bedient werden, erneut denselben Artikel verlangen. Das Personal kommt nie zur Ruhe.

Dass alle Anfragen identisch sind, hat eine Folge, die man benennen sollte: Ein vorgelagerter Cache darf sie beantworten. Genau das macht den Durchlauf aufschlussreich. Erreicht die immer gleiche Anfrage den Ursprungsserver nie, wissen Sie etwas Belastbares über Ihren Netzrand. Kommt sie jedes Mal durch, haben Sie einen Befund, an dem Sie arbeiten können.

Welche Ihrer Schutzmassnahmen das hätte abfangen müssen

Es lohnt sich, die ganze Kette abzugehen, die eine Anfrage durchläuft, denn die Antwort liegt selten bei der Massnahme, die man erwartet. Drei Gruppen zählen: was der Last vorgelagert ist, was auffängt, was durchkommt, und ob Ihnen überhaupt etwas meldet, dass es passiert ist.

Zone 1

Was fängt sie auf?

  • DDoS-Schutz und Scrubbing. Hier liegt ein echtes Signal vor, er sollte also handeln. Ob er es tut, hängt von Schwellenwerten ab, die gegen Ihren normalen Verkehr gesetzt wurden.
  • Firewall und WAF. Sie sehen vollständige, gültige Anfragen und finden nichts, worauf eine Regel passt. Sie helfen nur dort, wo sie zusätzlich eine Rate durchsetzen.
  • Rate Limiting. Hier fällt die Entscheidung am ehesten. Was als Schlüssel verwendet wird, welche Rate zulässig ist und welcher Burst geduldet wird, macht das ganze Ergebnis aus.
  • Bot-Erkennung und Challenges. Statischer Agent-String, kein Browserverhalten – eine Challenge müsste das abfangen. Offen ist, ob der getroffene Endpunkt überhaupt hinter einer steht.
  • CDN-Cache. Ein cachefähiger Pfad fängt fast alles davon ab. Ein Login-Formular oder ein API-Aufruf erreicht jedes Mal den Ursprungsserver.

Zone 2

Was bekommt die Last ab?

  • Load Balancer oder Reverse Proxy. Er leitet gültige Anfragen weiter, denn das ist seine Aufgabe. Sein Upstream-Pool wird zur ersten Obergrenze.
  • Verbindungsschicht. Hält gut stand. Sockets werden normal geöffnet und wieder geschlossen; bei aktivierter Wiederverwendung wickeln wenige Verbindungen viele Anfragen ab.
  • Webserver. Worker- und Thread-Pools laufen voll, und sobald sie voll sind, stauen sich neue Anfragen, statt abgewiesen zu werden.
  • Anwendung und Datenbank. Hier tut es weh. Anfragen verbrauchen CPU-Zeit, und jede Abfrage nimmt eine Verbindung aus einem Pool, der weit kleiner ist als die Anfragerate.
  • Auto-Scaling. Es sieht die Spitze und reagiert, anders als beim langsamen Angriff. Ob es schnell genug reagiert und ob zusätzliche App-Server an einem einzigen Datenbankpool etwas bringen, zeigt der Testlauf.

Zone 3

Würden Sie davon erfahren?

  • Dashboards. Die Anfragen pro Sekunde steigen sichtbar an. Das ist der Angriff, den Ihre Kurven gut abbilden.
  • Logging und Ihr SIEM. Hier kehrt sich das Problem um: Log-Zeilen vervielfachen sich mit dem Anfragevolumen, Ingest-Limits werden erreicht, Lizenzgrenzen überschritten, Ereignisse fallen dem Sampling zum Opfer – und ausgerechnet das Zeitfenster, das Sie am dringendsten brauchen, fehlt.
  • Alarmierung. Irgendetwas löst aus. Ob daraus ein einziger, verwertbarer Alarm wird oder mehrere hundert korrelierte, ist eine Frage der Feinabstimmung, die sich nur unter Last beantworten lässt.
  • Pikettdienst. Jemand wird alarmiert, vermutlich mehrfach. Ob der Alarm genug Information für eine Reaktion enthält, zeigt erst der Durchlauf.
  • Eine Prüfung von ausserhalb Ihres Netzes. Hier ist sie verlässlich. Sie klärt, ob Nutzer betroffen sind, während intern noch über die Messwerte gestritten wird.

Violett markiert, was nur ein Testlauf an Ihrer eigenen Infrastruktur klären kann. Alles Übrige ergibt sich daraus, wie der Angriff funktioniert, nicht daraus, wie gut Ihr Team konfiguriert hat.

Diese dritte Gruppe entdecken Teams am spätesten. Eine Massnahme, die greift, aber unsichtbar bleibt, ist ebenso ein Befund wie eine, die mehr meldet, als irgendjemand lesen kann.

Die Parameter – und der Kompromiss bei der Verbindungswiederverwendung

Fünf Einstellungen bestimmen den Durchlauf, und jede verlagert die Last an eine andere Stelle:

  • Parallele Anfragen pro Worker. Standard 20, einstellbar von 1 bis 100. Ein höherer Wert hebt parallele Last und erreichbare Anfragerate ungefähr linear an. Die Plattform rechnet mit fünf Anfragen pro Sekunde je Schleife, sofern das Ziel innerhalb von 200 ms antwortet.
  • Methode. Standard GET, daneben stehen POST, PUT, DELETE und HEAD zur Verfügung. Einen Body senden nur die Methoden ausser GET und HEAD; eine grosse Nutzlast verschiebt die Kosten also Richtung Upload-Bandbreite und Body-Parsing. HEAD nimmt die Kosten für den Antwort-Body auf beiden Seiten heraus.
  • Nutzlast. Standardmässig leer. Platzhalter wie {{uuid}} und Zufallstext in wählbarer Länge werden je Anfrage expandiert, sodass sich die Bodys unterscheiden können, während der Anfragepfad gleich bleibt.
  • Header. Statische Header auf jeder Anfrage. Standardmässig geht nur ein User-Agent mit, der die Simulation kenntlich macht; überschreiben lässt sich alles, und so kommen Auth-Tokens und Cookies hinein.
  • Verbindungswiederverwendung. Standardmässig eingeschaltet.

Der letzte Punkt verdient einen eigenen Absatz. Ist die Wiederverwendung eingeschaltet, geht der Socket nach jeder Antwort zurück in den Pool; die nächste Anfrage spart sich den TCP- und den TLS-Handshake, und die Last landet fast vollständig bei der Verarbeitung der Anfragen. Schalten Sie sie ab, öffnet jede Anfrage einen neuen Socket: Die erreichbare Rate sinkt, und ein Teil der Last wandert auf den Verbindungsaufbau – damit prüfen Sie etwas anderes.

Wie lange ein Socket im Pool überlebt, entscheidet Ihr Server und nicht der Client. nginx arbeitet standardmässig mit keepalive_timeout 75s und keepalive_requests 1000: Nach tausend Anfragen oder 75 Sekunden Leerlauf wird die Verbindung geschlossen, ganz gleich, was der Client bevorzugen würde. Wiederverwendung mit einem Server zu kombinieren, der mit Connection: close antwortet, ergibt einen Pool, der nach jeder Antwort abgerissen wird – Overhead auf beiden Seiten.

Wie sich das von GoldenEye und Browser Flood unterscheidet

Alle drei zählen zu den Floods auf der Anwendungsschicht, austauschbar sind sie nicht. GoldenEye hängt zufällige Query-String-Parameter an und wechselt den User-Agent, erreicht damit auch hinter einem CDN den Ursprungsserver und macht header-basierte Filter stumpf. Der HTTP-Flood macht bewusst das Gegenteil: identische Anfragen, damit Ihr Cache seine Arbeit tun darf – und Sie messen können, ob er sie tut. Browser Flood steuert auf jedem Worker einen echten Chromium-Tab und lädt alle Unterressourcen nach, die ein Browser anfordert.

Nehmen Sie den HTTP-Flood für die reine Verarbeitungskapazität, GoldenEye, um den Cache gezielt zu umgehen, und Browser Flood für realistische Nutzerlast.

Wie Sie sich gegen einen HTTP-Flood-Angriff verteidigen

Die Abwehr ist zum grössten Teil eine Rechenaufgabe. Vier Massnahmen leisten den grössten Teil der Arbeit:

  • Rate Limiting mit einem Burst, den Sie wirklich gewählt haben. Bei nginx definieren Sie mit limit_req_zone Schlüssel und Rate und wenden beides dann dort an, wo Sie es brauchen: limit_req zone=name burst=number. Der Parameter burst steht standardmässig auf 0, oberhalb der Rate wird also nichts geduldet; abgelehnte Anfragen bekommen über limit_req_status standardmässig ein 503. Wählen Sie beide Werte bewusst.
  • Eine Grenze für gleichzeitige Verbindungen neben der Ratengrenze. limit_conn begrenzt die gleichzeitigen Verbindungen je Schlüssel und deckt damit eine andere Bruchstelle ab als eine Anfragerate. Auch sie antwortet standardmässig mit einem 503.
  • Caching, das Sie bestätigt haben. nginx wird mit proxy_cache off ausgeliefert, Caching gibt es also nur dort, wo es jemand eingeschaltet hat; proxy_cache_lock steht ebenfalls standardmässig auf off, gleichzeitige Misses auf denselben Schlüssel gehen also einzeln zum Ursprungsserver. Unter einem Flood werden aus einem kalten Objekt so hunderte Anfragen an den Ursprungsserver. Schalten Sie den Lock ein, fallen sie zu einer einzigen zusammen.
  • Sie müssen die kleinste Zahl in Ihrer Kette kennen. nginx erlaubt standardmässig worker_connections 512 je Worker-Prozess. Apaches MaxRequestWorkers steht standardmässig auf 256 bei prefork und auf 400 bei den MPMs event und worker, also ServerLimit 16 mal ThreadsPerChild 25. pm.max_children von PHP-FPM hat gar keinen Standardwert, ist zwingend anzugeben und begrenzt die gleichzeitig bedienten Anfragen. max_connections von PostgreSQL liegt typischerweise bei 100. Ihre tatsächliche Kapazität ist die niedrigste dieser Zahlen – und sehr oft ist das die Datenbank.

Jeder dieser Werte steht in einer Datei, die irgendwann einmal jemand bearbeitet hat – und deshalb ist ein Blick in die Konfiguration ein schwächerer Nachweis als ein Durchlauf.

Was Sie selbst prüfen können – und wo das Ergebnis täuscht

Drei dieser Fragen können Sie heute ohne fremde Hilfe beantworten. Es lohnt sich zu wissen, welche das sind – und an welcher Stelle eine billige Prüfung in die Irre führt.

Lesen Sie die Konfiguration und rechnen Sie nach. Notieren Sie die Ratengrenze und ihren Schlüssel, die gecachten Pfade, die Worker-Obergrenze, den Prozess-Pool, das Verbindungslimit der Datenbank. Die kleinste Zahl ist Ihre Kapazität. Damit kennen Sie die Zahlen. Ob sich die Kette unter Last so verhält, wie die Rechnung nahelegt, wissen Sie damit noch nicht, denn Warteschlangen, Retries und Timeouts greifen auf eine Weise ineinander, die keine Konfigurationsdatei zeigt.

Prüfen Sie den Alarmweg für sich allein. Lösen Sie einen synthetischen Alarm aus und sehen Sie nach, ob er den richtigen Pikettdienst erreicht und ob jemand handelt. Damit prüfen Sie den Meldeweg, und Angriffsverkehr brauchen Sie dafür nicht. Über das Verhalten desselben Meldewegs bei echtem Volumen sagt das nichts.

Führen Sie einen Lasttest aus einer einzigen Quelle gegen eine Staging-Umgebung durch, mit Freigabe aus dem Change-Management, auf Infrastruktur, die Ihnen gehört. Das ist zulässig, und für Kapazitätskurven ist es wirklich nützlich. Genau hier liegt aber auch die Falle.

Eine einzelne Maschine lässt die Fragen der ersten Zone nicht nur offen – sie beantwortet mehrere davon falsch, und zwar beruhigend falsch. Eine Ratengrenze je Quelle fängt einen einzelnen Ursprung sofort ab, die Bot-Erkennung markiert einen einzelnen Client, der sich auffällig verhält, die Reputationsfilterung sortiert ihn aus – und der Netzrand sieht anschliessend tadellos aus.

Ein echter, verteilter Flood tritt anders auf. Tausende Adressen aus Privathaushalten senden jeweils eine unauffällig geringe, plausible Zahl von Anfragen, keine einzelne Quelle überschreitet einen Schwellenwert je Quelle, und die Summe landet trotzdem auf Ihrem Ursprungsserver. Ausgerechnet die Massnahmen, denen Sie am meisten vertrauen müssen, bewertet ein Test aus einer einzigen Quelle zu milde.

Bei der Erkennung zeigt sich dieselbe Schieflage. Ein kleiner Testlauf erzeugt nur wenig Log-Volumen und kann deshalb nicht zeigen, ob Ihr Ingest-Limit erreicht wird, ob Ereignisse verworfen werden oder ob die Person im Pikettdienst den entscheidenden Alarm zwischen den übrigen findet.

Wie Obsidio einen HTTP-Flood prüft

Obsidio führt das als kontrollierte, autorisierte Simulation gegen Infrastruktur durch, die Ihnen gehört. Die Domain-Inhaberschaft wird über einen DNS-TXT-Eintrag geprüft, bevor Verkehr fliesst; die Durchläufe fahren schrittweise hoch, und jeder Durchlauf lässt sich im Betrieb abbrechen. Sie legen Schleifen je Worker, Methode, Nutzlast und Verbindungswiederverwendung fest und beobachten dann Ihr System. Die Last kommt von über 100’000 weltweit verteilten echten Geräten, sodass Limits je Quelle und Reputationsfilterung so geprüft werden, wie ein verteilter Angriff sie prüft.

Drei Punkte, die Sie während des Durchlaufs im Auge behalten sollten:

  • Vergleichen Sie die Zählwerte entlang der Kette. Am Netzrand angenommene Anfragen, Anfragen, die den Ursprungsserver erreichen, erfolgreich abgeschlossene Anfragen. Wo diese Zahlen auseinanderlaufen, greift eine Massnahme – und die Differenz sagt mehr als jeder Einzelwert.
  • Beobachten Sie den Datenbankpool, nicht die CPU. Die CPU-Last der Anwendung kann noch erträglich aussehen, während Abfragen auf eine freie Verbindung warten. Pool-Auslastung und Warteschlangentiefe zeigen, wo die Obergrenze wirklich liegt.
  • Messen Sie von aussen. Serverseitige Messwerte zeigen unter Umständen nur eine massvolle Beeinträchtigung, während eine echte Anfrage von ausserhalb Ihres Netzes längst in den Timeout läuft.

Jede Anfrage läuft durch einen Response-Analyzer; der Bericht hält deshalb für jedes Zeitfenster fest, was am Netzrand geschehen ist: WAF-Antworten, Verhalten des Scrubbings, Ablehnungen durch das Rate Limiting, Captcha-Challenges. Obsidio misst das, weil es von aussen sichtbar ist. Ob Ihr SIEM das Ereignis aufgenommen hat und ob jemand alarmiert wurde, wissen nur Ihre eigenen Konsolen – lassen Sie deshalb während des Zeitfensters jemanden darauf schauen.

Stärke wird bewiesen, nicht versprochen. Ein HTTP-Flood sucht keinen Fehler in Ihrem Code. Er sucht die kleinste Zahl in Ihrem System – und ob sie in letzter Zeit jemand nachgerechnet hat.

Wo das in ein Testprogramm gehört

Ein HTTP-Flood ist fast das genaue Gegenteil von Slowloris. Der langsame Angriff sendet fast nichts, geht an volumenbasierten Abwehrmechanismen vorbei, bleibt am Verbindungspool des Webservers hängen und hinterlässt ausgerechnet in den Dashboards, in die Sie schauen würden, keine Spur. Der Flood dagegen ist laut, liefert Ihren Schutzmassnahmen am Netzrand ein klares Signal, reicht bis in die Datenbank – und begräbt den entscheidenden Befund unter Telemetriedaten. Wer gegen den einen härtet, weiss über den anderen wenig. Ein Programm aus lauter Floods lässt die langsame Klasse ungeprüft; eines aus lauter langsamen Angriffen findet die Kapazitätsgrenze nie.

Für regulierte Institute muss der Testlauf zudem einen Nachweis hinterlassen. Obsidio erzeugt kryptografisch attestierte, manipulationssichere Berichte, die FINMA, DORA und NIS2 zugeordnet sind und in Trusted Execution Environments entstehen – das Ergebnis eines Durchlaufs ist damit ein Beleg für die Akten und kein Screenshot, für dessen Echtheit Sie geradestehen müssen.

Um den Prüfumfang einer autorisierten HTTP-Flood-Simulation gegen Ihre eigene Infrastruktur festzulegen, sehen Sie sich die Obsidio-Plattform an oder nehmen Sie Kontakt mit dem Team auf. Unabhängig, neutral, überprüfbar: Resilienz, die Sie belegen können.

← Zurück zum Blog