TCP-Connection-Flood: Wie er wirkt und wie Sie Ihre Abwehr testen

Patchpanel, auf dem jeder Port belegt und beleuchtet ist – eine vollständig erschöpfte Verbindungstabelle

· 15 Min. Lesezeit

Ein TCP-Connection-Flood sendet zu keinem Zeitpunkt Daten. Er baut Verbindungen auf, lässt den Handshake abschliessen und schweigt danach so lange, wie der Socket bestehen darf. Es gibt keine Anfrage, die man prüfen könnte, keine Nutzlast zum Filtern und keine Signatur zum Abgleichen – oberhalb der Transportschicht geschieht schlicht nichts.

Deshalb ist er unangenehm abzuwehren und fällt kaum auf. Die Bandbreite bleibt flach, die Anfragen pro Sekunde bleiben flach. Was sich füllt, sind Zähler, die die meisten Teams nie in ein Dashboard aufgenommen haben: Einträge in der Verbindungstabelle, File Descriptors, Slots im Connection Tracking und die Warteschlange der Verbindungen, die auf ihre Annahme warten. Der Dienst antwortet nicht mehr, während jede Grafik, die gerade jemand im Blick hat, unauffällig aussieht.

Das Wichtigste in Kürze

  • Gesendet wird nie etwas. Kein HTTP, kein TLS, keine Nutzlast, keine Verhandlung über den Handshake hinaus. Ein Socket im Leerlauf erzeugt keinen Datenverkehr; es gibt also nichts zu prüfen und nichts, worauf eine volumenbasierte Abwehr anspringen könnte.
  • Erschöpft werden Zähler, nicht Kapazität. Einträge in der Verbindungstabelle, File Descriptors, Slots im Connection Tracking, Socket-Limits pro Quelle und die Accept-Queue. Keiner dieser Werte erscheint in einer Grafik zu Bandbreite oder Anfragerate.
  • Eine WAF ist keine Firewall, und hier entscheidet dieser Unterschied über den Ausgang. Eine WAF prüft HTTP und bekommt diesen Verkehr gar nicht zu sehen. Eine Netzwerk-Firewall setzt an der Schicht an, auf der das möglich ist.
  • Pro Socket ist er der billigste Angriff in unserem Katalog. Auf keiner der beiden Seiten fällt Rechenaufwand für Kryptografie an, anders als bei einem TLS Flood.
  • Ein Test aus einer einzigen Quelle bescheinigt Ihnen, dass alles in Ordnung sei. Ein Verbindungslimit pro IP stoppt eine Maschine binnen Sekunden und richtet gegen eine verteilte Flut wenig aus; die billige Prüfung fällt also genau dort positiv aus, wo das echte Risiko liegt.

Dieser Beitrag gehört zu einer Serie über alle zehn Simulationstypen, die Obsidio durchführt. Jeder von ihnen legt eine andere Bruchstelle frei, und deshalb braucht ein Testprogramm Abdeckung über die Kategorien hinweg statt Volumen in einer einzigen. Wie sie zusammenspielen, steht in unserem Leitfaden dazu, wie Sie Ihren DDoS-Schutz testen.

Die zehn Simulationstypen

Je ein Beitrag, gruppiert wie in der Plattform.

Verbindungserschöpfung

Protokollspezifisch & skriptgesteuert

Was bei einem TCP-Connection-Flood tatsächlich passiert

Jeder parallele Slot baut eine einzelne TCP-Verbindung auf, lässt den Handshake abschliessen und hält den Socket im Leerlauf, bis ein konfigurierter Timeout abläuft. Danach schliesst er ihn und öffnet den nächsten. Darüber liegt weder eine HTTP- noch eine TLS-Schicht, und in keiner Richtung werden Anwendungsdaten geschrieben. Bei einem TCP-Flood dieser Art geht es um Belegung und nicht um Paketvolumen, und die gehaltenen Verbindungen sind vollständig aufgebaut, nicht halb offen.

Verbraucht werden dabei endliche Plätze. Jede aufgebaute Verbindung belegt einen Eintrag in der Verbindungstabelle des Servers, einen offenen File Descriptor im Prozess, der sie hält, einen Eintrag im Connection Tracking jedes zustandsbehafteten Geräts auf dem Weg und einen Anteil am geltenden Socket-Limit pro Quelle. Verbindungen, die eintreffen, während sich die Accept-Queue staut, warten darin; ist sie voll, werden sie abgewiesen. Nichts davon ist CPU-Zeit oder Bandbreite, und genau deshalb bleibt der Angriff so leise.

Zwanzig Leute rufen im Laden an und sagen kein Wort. Alle Leitungen sind belegt, das Personal sitzt bereit, und die echten Kunden hören das Besetztzeichen.

Ein Socket, der verbunden ist und noch nichts gesagt hat, ist für sich genommen nicht verdächtig; ein Mobilgerät mit schlechtem Empfang sieht genauso aus. Deshalb legen Server nicht einfach auf, und deshalb muss die Abwehr in einem Limit bestehen und nicht in einer Regel.

Welche Ihrer Schutzmassnahmen hätte das abfangen müssen

Es lohnt sich, die ganze Kette abzugehen, die eine Verbindung durchläuft, denn die Antwort fällt hier schmaler aus, als die meisten Teams erwarten. Drei Gruppen zählen: was vorgelagert ist, was das Durchgekommene auffängt, und ob Ihnen überhaupt jemand meldet, dass es passiert ist.

Zone 1

Hätte eines davon den Angriff stoppen müssen?

  • Web Application Firewall. Prüft HTTP-Anfragen. Es wird nie eine Anfrage gestellt, sie hat also nichts zu bewerten.
  • Netzwerk-Firewall. Die einzige Massnahme auf der passenden Schicht. Ob sie Verbindungen pro Quelle begrenzt und ob ihre eigene Tracking-Tabelle noch Luft hat, hängt von Ihrer Konfiguration ab.
  • Volumetrischer DDoS-Schutz. Beobachtet Bandbreite und Paketrate. Ein Socket im Leerlauf erzeugt beides nicht.
  • Rate Limiting und Bot-Erkennung. Das eine zählt Anfragen, das andere beurteilt das Verhalten des Clients. Beides fehlt hier.
  • Ein Proxy oder eine Scrubbing-Schicht, die TCP für Sie terminiert. Wo es sie gibt, ist das echter Schutz – vorausgesetzt, Ihr Ursprungsserver ist nicht direkt erreichbar.

Zone 2

Was bekommt die Last ab?

  • Load Balancer oder Reverse Proxy. Terminiert er die Verbindungen, entscheiden seine eigenen Limits über den Ausgang. Klären Sie, welche Komponente den Socket tatsächlich hält.
  • Connection Tracking und Accept-Queue. Ein Eintrag pro Socket, und neu eintreffende Verbindungen reihen sich hinter allem ein, was bereits gehalten wird.
  • Der lauschende Dienst. Hier tut es weh. Die Verbindungsobergrenze pro Worker und das Limit für offene Dateien sind harte Grenzen, und Sie stossen an die niedrigere von beiden.
  • Anwendung und Datenbank. Bleiben unberührt. Es wird keine Anfrage gestellt, also läuft kein Handler und es geht keine Abfrage an die Datenbank.
  • Auto-Scaling. CPU, Bandbreite und Anfragerate sehen alle nach Leerlauf aus, also skaliert nichts. Zusätzliche Knoten würden einem gemeinsam genutzten zustandsbehafteten Gerät ohnehin nicht helfen.

Zone 3

Würden Sie davon erfahren?

  • Dashboards für Bandbreite und Anfragerate. Durchgehend flach. Beide messen das Falsche.
  • Eine Kurve der gleichzeitigen Verbindungen oder der Tracking-Tabelle. Die einzige interne Messgrösse, die das deutlich zeigt. Ob es sie gibt und ob jemand hinschaut, bleibt offen.
  • Eine Prüfung von aussen. Erkennt es zuverlässig, weil keine neuen Verbindungen mehr angenommen werden – nützt aber nur, wenn Sie eine betreiben.
  • Access-Logs und Ihr SIEM. Keine Anfrage, keine Zeile im Access-Log. Was überhaupt festgehalten wird, landet im Error-Log, und zwar als Meldung über ein Ressourcenlimit und nicht über einen Angriff.
  • Alarmierung und Pikettdienst. Kein Schwellenwert wird überschritten. Der Dienst weist Verbindungen ab, und niemand wird alarmiert.

Violett markiert, was sich nur mit einem Test auf Ihrer eigenen Infrastruktur klären lässt. Alles Übrige folgt aus der Funktionsweise des Angriffs und nicht daraus, wie sorgfältig Ihr Team konfiguriert hat.

Firewall und WAF sind hier sauber auseinanderzuhalten, denn dies ist der Angriff, bei dem es teuer wird, beide für dasselbe Gerät zu halten. Eine Web Application Firewall liest HTTP: Request-Zeilen, Header, Query-Strings, Bodies. Eine Verbindung, die den Handshake abschliesst und danach schweigt, liefert ihr nichts davon, und ein Client, der noch nicht gesprochen hat, ist keine Auffälligkeit, gegen die sich eine Regel schreiben liesse. Die WAF ist nicht falsch konfiguriert; der Verkehr liegt unterhalb der Schicht, an der sie ansetzt.

Eine Netzwerk-Firewall arbeitet mit Adressen, Ports und Verbindungszustand – also mit genau dem, was dieser Angriff ausnutzt. Sie kann zählen, wie viele aufgebaute Verbindungen eine Quelle hält, und die nächste abweisen. In netfilter ist das das Match connlimit, bei dem --connlimit-above den Schwellenwert setzt und --connlimit-mask Quellen nach Präfixlänge gruppiert; nftables kennt dafür ct count. Ihre Mitwirkung hat allerdings einen Preis: Jede Verbindung, die sie verfolgt, kostet einen Eintrag in ihrer eigenen Tabelle. Die Massnahme, die den Angriff am ehesten sieht, ist damit selbst eine Ressource, die er verbraucht.

Die Parameter, und was das hier nicht ist

Vier Einstellungen, jede mit einer Ressource auf Ihrer Seite verknüpft:

  • Gehaltene Sockets pro Worker. Standard 20, Bereich 1 bis 100. Mit der Anzahl Worker multipliziert ergibt das die Zahl der Einträge, die Sie der Verbindungstabelle des Ziels zumuten. Die tatsächliche Obergrenze ist der kleinere Wert aus diesem Produkt und den Socket-Limits des Ziels pro Quelle und insgesamt.
  • Idle-Timeout. Standard 600’000 Millisekunden, also zehn Minuten; einstellbar von 100 Millisekunden bis zu einer Stunde und durch die Laufzeit des Durchlaufs begrenzt. Ein höherer Wert hält die Sockets länger, der Druck bleibt bestehen und die Öffnungsrate sinkt. Ein niedrigerer Wert sorgt für schnelleren Wechsel und verlagert die Last vom Belegen auf das Annehmen und Abbauen von Verbindungen.
  • Denkzeit. Standard 0. Wie lange ein Slot nach dem Schliessen eines Sockets wartet, bevor er den nächsten öffnet; das bestimmt die Nachbesetzungsrate.
  • Port. Standardmässig 443 oder 80, abgeleitet aus dem Schema der Ziel-URL. Das Schema legt nur die Nummer fest; die Verbindung ist in beiden Fällen reines TCP.

Ein TLS-Flood gibt Verbindungen ebenfalls auf, zwingt den Server aber zuvor durch einen vollständigen Handshake mit Schlüsselaustausch, Zertifikat und Sitzungsaufbau; diese asymmetrische Kryptografie kostet den Server rund fünfzehnmal so viel wie den Client. Sein Ziel ist die CPU. Ein TCP-Connection-Flood wendet auf keiner Seite Kryptografie auf, und er kostet das Ziel einen Tabelleneintrag und einen File Descriptor.

Slowloris geht wieder anders vor. Er spricht HTTP, sendet eine unvollständige Anfrage und schickt den Rest danach häppchenweise nach – deshalb fangen ihn Timeouts beim Lesen der Header und ein puffernder Proxy ab. Ein TCP-Connection-Flood sendet überhaupt kein HTTP; alles, was über den Inhalt von Anfragen urteilt, ist hier deshalb nicht bloss schwach, sondern gar nicht im Spiel.

Wie Sie sich gegen einen TCP-Connection-Flood verteidigen

Alles Nützliche ist hier eine Zahl, und jede Zahl kann veraltet sein, falsch sein oder niedriger liegen als angenommen. Das sind die Zahlen, die Sie prüfen sollten, mit den dokumentierten Standardwerten, wo es welche gibt:

  • Limits für gleichzeitige Verbindungen pro Quelle, auf der Netzwerkschicht. Das Match connlimit von netfilter mit --connlimit-above ist das direkte Werkzeug, und --connlimit-mask fasst statt einer einzelnen Adresse ein ganzes Quellnetz zusammen. Eine Warnung: limit_conn in nginx deckt diesen Fall nicht ab. Die Dokumentation sagt es ausdrücklich: Eine Verbindung wird erst gezählt, wenn der Server eine Anfrage aus ihr bearbeitet und der vollständige Request-Header gelesen ist («a connection is counted only if it has a request being processed by the server and the whole request header has already been read»). Ein Socket, der nie eine Anfrage sendet, wird nie gezählt.
  • Kapazität des Connection Tracking. Im Connection Tracker von Linux liegt nf_conntrack_max standardmässig beim Vierfachen der Bucket-Anzahl, und auf einer Maschine mit mehr als 4 GB Speicher liegt die Bucket-Anzahl standardmässig bei 65’536. Wichtiger noch: nf_conntrack_tcp_timeout_established steht standardmässig auf 432’000 Sekunden, also fünf Tage. Eine aufgebaute Verbindung im Leerlauf behält ihren Eintrag tagelang, solange nicht etwas anderes den Socket schliesst.
  • Limits für File Descriptors. Jeder Socket ist eine offene Datei. fs.file-max begrenzt die Handles, die der Kernel systemweit vergibt – ohne dokumentierten Standardwert, weil er pro System bemessen wird. Meist greifen die Limits pro Prozess zuerst: nginx setzt worker_connections auf 512 pro Worker, zählt Verbindungen zu Upstreams ebenso mit wie die von Clients und hält fest, dass der Wert das geltende Limit für offene Dateien nicht überschreiten kann («cannot exceed the current limit on the maximum number of open files»); angehoben wird dieses Limit mit worker_rlimit_nofile, das keinen Standardwert hat. Verlassen Sie sich auf die kleinere Zahl.
  • Tiefe der Accept-Queue. nginx setzt das Listen-Backlog unter Linux standardmässig auf 511, und was ein Dienst anfordert, wird stillschweigend auf net.core.somaxconn begrenzt – seit Linux 5.4 standardmässig 4096, auf älteren Kerneln 128. Eine kurze Warteschlange macht aus gehaltenen Verbindungen früher abgewiesene.
  • Idle-Timeouts, auf einer Schicht, die welche kennt. Die Transportschicht hilft standardmässig nicht: tcp_keepalive_time steht auf zwei Stunden, danach folgen neun Probes im Abstand von 75 Sekunden. nginx macht es besser, mit client_header_timeout von 60 Sekunden für das Lesen eines Request-Headers und keepalive_timeout von 75. Zu prüfen ist, ob jeder Listener in Ihrem Stack ein Gegenstück dazu hat – viele haben keins.

Ein Listener mit einem Header-Timeout von 60 Sekunden schliesst diese Sockets, weil die Simulation sie standardmässig zehn Minuten hält. Das verschiebt die interessante Frage, statt sie zu beantworten. Entschieden wird der Durchlauf davor: in der Tabelle der Firewall, im Load Balancer und in der Accept-Queue.

Was Sie selbst prüfen können, und wo es Sie täuscht

Zwei davon erledigen Sie an einem Nachmittag. Das dritte führt in die Irre.

Lesen Sie die Zahlen aus und rechnen Sie nach. Holen Sie sich das Maximum des Connection Tracking, die Descriptor-Limits, die Verbindungsobergrenze pro Worker, das Listen-Backlog und somaxconn. Der kleinste Wert ist Ihre tatsächliche Kapazität. Das kostet nichts, dauert eine Stunde und überrascht viele. Es sagt Ihnen, wo die Obergrenze liegt – nicht, was geschieht, wenn Sie sie erreichen.

Prüfen Sie die Alarmierungswege für sich allein. Lösen Sie einen synthetischen Alarm aus und prüfen Sie, ob er den richtigen Pikettdienst erreicht und ob jemand darauf reagiert. Dafür braucht es keinen Angriffsverkehr.

Öffnen Sie viele Verbindungen von einer einzelnen Maschine aus, gegen eine Staging-Umgebung, die Ihnen gehört, mit Freigabe aus dem Change-Management. Legitim – und genau hier liegt die Falle.

Die Rechnung sollte man einmal ausschreiben. Nehmen wir an, Ihr Limit pro Quelle liegt bei 100 gleichzeitigen Verbindungen. Eine Maschine versucht fünftausend zu öffnen und wird bei 100 gestoppt. In den Zählern der Firewall erscheinen Abweisungen, die Zahl der Verbindungen bewegt sich kaum, der Dienst bleibt stabil, und der Test wird als bestanden protokolliert. Das Ergebnis stimmt. Der Schluss daraus ist falsch, denn die Massnahme, die sich gerade bewährt hat, ist für das Szenario, vor dem Sie sich fürchten, fast die unwichtigste.

Verteilen Sie dasselbe nun. Zehntausend Quellen halten je zwanzig Verbindungen. Jede Quelle bleibt weit unter dem Limit, keine einzige verletzt eine Regel, und Ihre Tabelle enthält zweihunderttausend aufgebaute Verbindungen. Gleiche Firewall, gleiche Konfiguration, umgekehrtes Ergebnis. Ein Limit pro Quelle senkt nicht die Gesamtkapazität, die verbraucht werden kann; es begrenzt nur den Anteil einer einzelnen Quelle. Bei genügend Quellen wandert die entscheidende Grenze zu Ihrer Tracking-Tabelle und Ihrem Descriptor-Limit – also zu den Zahlen, an die der Test von einer Maschine nie herankam, weil ihn das Limit vorher bei 100 gestoppt hat. Mit Präfixmasken stellt sich dasselbe Problem: Die Gruppierung nach Netz erfasst einen gemieteten Adressblock bei einem Hoster und richtet gegen IP-Adressen aus Privathaushalten wenig aus, die über Tausende unabhängiger Netze verstreut sind.

Sobald das Limit wie die Abwehr aussieht, hebt niemand die dahinterliegenden Grenzwerte an und niemand verfolgt die Tracking-Tabelle in einer Grafik; der Befund, auf den es angekommen wäre, entsteht also nie. Und keine Konfigurationsprüfung sagt Ihnen, wie sich der Stack an seiner Obergrenze verhält. Manche Dienste weisen neue Verbindungen sauber ab, manche bleiben hängen, manche fallen durch ihren Health Check und werden aus dem Verbund genommen, obwohl sie noch laufen.

Wie Obsidio einen TCP-Connection-Flood testet

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 überhaupt Verkehr fliesst; die Last steigt stufenweise an, statt auf voller Höhe zu beginnen, und jeder Durchlauf lässt sich im Betrieb abbrechen. Sie wählen die Sockets pro Worker, den Idle-Timeout, die Denkzeit und den Port.

Die Last kommt von über 100’000 weltweit verteilten echten Geräten und nicht von einer Handvoll Instanzen im Rechenzentrum. Genau das kann ein Selbsttest nicht nachbilden: Ein Limit pro Quelle wird so beansprucht, wie eine verteilte Flut es beansprucht – über viele Netze hinweg, wobei jede einzelne Quelle unauffällig bleibt.

  • Ignorieren Sie die Anfragerate. Der angezeigte Wert zählt Socket-Öffnungen und nicht Durchsatz, und die Schätzung liegt bei einer Öffnung pro Slot. Er wirkt unscheinbar niedrig, gerade während der Durchlauf greift. Schauen Sie stattdessen auf die gleichzeitigen Verbindungen und den Spitzenwert, dazu auf die Rate der Öffnungsversuche, wenn Sie den Wechsel statt der Belegung prüfen.
  • Messen Sie die Erreichbarkeit von aussen. Was Nutzer erleben, ist eine Verbindung, die nicht angenommen wird; serverseitige Messwerte können das verdecken.

Klären Sie die Aufteilung vorher. Obsidio misst, wie viele Verbindungen gehalten wurden, den Spitzenwert, die Öffnungsrate und ob der Dienst von aussen erreichbar blieb – all das ist von aussen sichtbar. Ob Ihr Descriptor-Limit oder Ihre Tracking-Tabelle tatsächlich vollgelaufen ist, was Ihr Error-Log festgehalten hat und ob jemand alarmiert wurde, sehen Sie nur auf Ihren eigenen Konsolen. Sorgen Sie dafür, dass während des Zeitfensters jemand darauf schaut.

Stärke wird bewiesen, nicht versprochen. Ein TCP-Connection-Flood prüft weder Ihre Bandbreite noch Ihre Regeln. Er prüft, ob die Grenzwerte, mit denen Ihr Stack bemessen wurde, noch die Grenzwerte sind, mit denen er läuft.

Wo das in ein Testprogramm gehört

Der TCP-Connection-Flood gehört zur Kategorie Verbindungserschöpfung, gemeinsam mit dem TLS-Flood. Beide zielen auf die Verbindung statt auf die Anfrage: Der eine verbraucht CPU-Zeit über die Kryptografie des Servers, der andere Tabellenplatz durch blosses Schweigen. Ein Stack, der den einen wegsteckt, kann am anderen scheitern; deshalb braucht die Kategorie beide.

Über die zehn Simulationstypen hinweg verschiebt sich die Bruchstelle erheblich. Eine Flut auf der Anwendungsschicht gibt Ihren vorgelagerten Schutzmassnahmen etwas, worauf sie reagieren können, reicht bis zum Connection Pool Ihrer Datenbank und macht aus dem Erkennungsproblem statt Schweigen Lärm. Dieser hier endet auf der Transportschicht, bleibt für Ihre WAF unsichtbar und wird von fünf oder sechs Ganzzahlen in Konfigurationsdateien entschieden.

Für regulierte Institute muss der Durchlauf zudem einen Nachweis hinterlassen. Obsidio erzeugt kryptografisch attestierte, manipulationssichere Berichte mit Zuordnung zu FINMA, DORA und NIS2, erstellt in Trusted Execution Environments. Das Ergebnis ist damit ein Beleg, den Sie ablegen können, und kein Screenshot, für den Sie bürgen müssen.

Um eine autorisierte TCP-Connection-Flood-Simulation gegen Ihre eigene Infrastruktur zu planen, sehen Sie sich die Obsidio-Plattform an oder sprechen Sie mit dem Team. Unabhängig, neutral, überprüfbar: Resilienz, die Sie belegen können.

← Zurück zum Blog