
Erkennt und nutzt HTTP-Request-Smuggling-Schwachstellen über HTTP/2-zu-HTTP/1.1-Konvertierung aus, mithilfe automatisierter Header-Smuggling-Techniken, um Backend-Parsing-Diskrepanzen zu identifizieren.
Dieses Tool hilft dabei, HTTP-Request-Smuggling zu erkennen und auszunutzen, wenn dies durch die Konvertierung von HTTP/2 -> HTTP/1.1 durch den Frontend-Server erreicht werden kann.
Das Schema ist wie folgt:
Der Angreifer möchte eine solche Anfrage finden, die vom Backend-Server als zwei separate Anfragen angesehen wird.
Wenn die Frontend<->Backend-HTTP/1.1-Verbindung Keep-Alive verwendet, könnte das Frontend Anfragen anderer Benutzer an dieselbe Verbindung senden. Wenn wir in der Lage sind, die Verbindung durch eine Teil-Anfrage zu „vergiften“, die nach einer legitimen kommt, können wir die Anfrage eines anderen Benutzers abgreifen.
Andere mögliche Szenarien umfassen das Umgehen von Frontend-Serverschutz und Umschreibungen, Cache-Poisoning oder Cache-Deception.
Weitere Informationen zu HTTP-Request-Smuggling finden Sie in der Portswigger Web Security Academy.
In HTTP/2 sind alle HTTP-Headernamen und -Werte binär. Daher können sie technisch gesehen zusätzliche Leerzeichen oder sogar Zeilenumbrüche enthalten.
RFC7540#10.3 besagt, dass Implementierungen, die HTTP/2-Anfragen in HTTP/1 konvertieren, die Einschränkungen des Zeichensatzes berücksichtigen müssen, die sich aus einer solchen Konvertierung ergeben; die meisten Implementierungen lehnen sie tatsächlich ab. Trotzdem hoffen wir, solche zu finden, die solche Header zulassen. Sie werden die konvertierte HTTP/1.1-Anfrage an das Backend verfälschen.
Ein weiterer Punkt ist, dass einige aktuelle Korrekturen im Zusammenhang mit HTTP-Request-Smuggling möglicherweise nur für HTTP/1.1-Parser implementiert wurden.
Im Allgemeinen hoffen wir, dass es Implementierungen von HTTP/2 gibt, die nicht sehr mit der aktuellen Forschung zu HTTP-Request-Smuggling in HTTP/1.1 vertraut sind und keine entsprechenden Gegenmaßnahmen enthalten.
Überraschenderweise ja!
Ich habe eine Möglichkeit gefunden, einen Header mit einem Leerzeichen durch Cloudflare zu schmuggeln und damit eine Tür für Cloudflare<->Client-Smuggling zu öffnen (falls die Software eines Cloudflare-Clients Headernamen akzeptiert und kürzt). Hier ist der Blogbeitrag.
Es gibt auch einen weiteren Bug-Bounty-Bericht, der noch nicht öffentlich ist. Er nutzt die Tatsache, dass eine benutzerdefinierte Software keine Zeilenumbrüche in HTTP2-Headern herausfiltert, und das Schmuggeln findet zu 100 % statt (ich kann Anfragen anderer Benutzer sehen).
Ich verstehe jedoch, dass diese Art von Schwachstellen frustrierend selten sein muss: Anders als bei HTTP/1.1 gibt es nicht so viele HTTP/2-Implementierungen, und die meisten sind sicherheitstechnisch entwickelt, sodass verdächtige oder ungültige Header abgelehnt werden.
Das Tool verfügt über einen Unterbefehl, der automatisch zu erkennen versucht, ob ein Ziel anfällig für den HTTP-Request-Smuggling-Angriff ist. Der Algorithmus hinter dieser Funktion wird in diesem Abschnitt beschrieben.
Um einen HTTP-Request-Smuggling-Angriff durchzuführen, müssen wir tatsächlich zuerst einen einzelnen Header schmuggeln (entweder Content-Length oder Transfer-Encoding). Das bedeutet, wir müssen einen Header senden, der a) steuert, wo der Anfragetext endet, und b) nicht vom Frontend verarbeitet, aber vom Backend verarbeitet wird.
Dies wird normalerweise erreicht, indem ein Header auf bestimmte Weise modifiziert wird: Anhängen von Leerzeichen oder Tabs an das Ende seines Namens, Ersetzen des Werts durch ein semi-Äquivalent usw.
Die Grundidee des Algorithmus zur Erkennung von Schwachstellen besteht darin, zu erkennen, ob der Server tatsächlich einen geschmuggelten Header so verarbeitet, als wäre er Content-Length oder Transfer-Encoding. Wir tun dies, indem wir mehrere Anfragen senden: einige mit gültigen und andere mit ungültigen Werten für den Header. Dann versuchen wir zu erkennen, ob es eine Möglichkeit gibt, die Antworten dieser beiden Gruppen zu unterscheiden.
Deshalb enthält die Ausgabe des Tools nicht die Wörter „anfällig/nicht anfällig“: Es sagt lediglich, ob es Antworten unterscheiden kann, die auf die Anfragen dieser beiden Gruppen kamen.
Das Tool betrachtet zwei Sätze von HTTP-Antworten als unterscheidbar, wenn mindestens eine der folgenden beiden Bedingungen erfüllt ist:
Die Timeouts werden als einzigartiger Statuscode-Wert behandelt, der keinem anderen gleicht; daher hebt das Tool das klassische „Erkennung durch Timing“-Schema auf.
Betrachten wir ein Beispiel. Angenommen, wir versuchen, den transfer-encoding-Header zu schmuggeln, indem wir den Bindestrich durch einen Unterstrich ersetzen.
Wenn es scheint, dass der Server mit Status 400 antwortet, jedes Mal wenn wir transfer_encoding:zalupa senden, und hängt, wenn es transfer_encoding:chunked ist, können wir sagen, dass der Server den Header wahrscheinlich als Wert für Transfer-Encoding verarbeitet. Theoretisch könnte es entweder der Frontend- oder der Backend-Server sein.
Der erste Fall ist nicht interessant, da wir die nicht geschmuggelte Version des Headers ohnehin senden könnten, und der zweite Fall ist das, wonach wir suchen. Da alles über HTTP/2 läuft, ist der erste Fall meist vermeidbar: Der HTTP/2-Server bestimmt das Ende des Anfragetextes auf andere Weise, die nichts mit den Anfrage-Headern zu tun hat, und erwartet nie, dass der Text im chunked-Format von HTTP/1.1 (dem mit hexadezimalen Chunk-Längen) vorliegt.
Die konkreten Variationen der Erkennungstechniken sind:
Wir senden eine geschmuggelte Version von Transfer-Encoding: chunked (z. B. transfer_encoding:chunked) und verschiedene Textkörper: gültig ist 0\r\n\r\n und ungültig ist 999\r\n.
Falls die Antworten unterschiedlich sind, können wir sicher sein, dass der Backend-Server den geschmuggelten Header empfängt und verarbeitet. Es gibt keinen Grund für das Frontend, dies zu tun: HTTP/2 verwendet nicht das von uns gesendete chunked-Format, sodass es ungültig wäre.
Wir erwarten, dass das Backend für die ungültigen Anfragen hängt (d. h. die Anfrage führt zu einem Timeout), da es auf weitere Daten wartet.
Dies ist die zuverlässigste Erkennungsvariante: Wenn der Server beim Lesen des Textkörpers hängt, ist wahrscheinlich etwas schiefgelaufen, da es keine Verwendung für HTTP/1.1-Transfer-Encoding in einer HTTP/2-Anfrage gibt.
Wir senden erneut eine geschmuggelte Version von Transfer-Encoding und verschiedene Textkörper: 0\r\n\r\n als gültigen Text und X\r\n\r\n als ungültigen.
Der Fall ist derselbe wie oben, aber statt den Text zu lesen, erwarten wir, dass das Backend ihn zumindest validiert.
Wir senden eine geschmuggelte Version des Content-Length-Headers mit den Werten 1 und -1.
Durch das Senden mehrerer Paare von gültigen/ungültigen Anfragen können wir die Wahrscheinlichkeit eines zufälligen Fehlalarms verringern. Andererseits können wir frühzeitig aufhören, wenn wir sehen, dass es keine Möglichkeit gibt, Antworten auf „gültige“ Anfragen von den Antworten auf „ungültige“ Anfragen zu trennen.
Das Tool versucht, mehrere Schmuggeltechniken anzuwenden, indem es einen Header auf verschiedene Weise modifiziert. Keine davon ist neu und gleichzeitig nicht offensichtlich.
Um einen Header zu schmuggeln, hängen wir ein Leerzeichen an. Dies ist die häufigste und klassische Methode. Wir hoffen, dass der Header nicht vom Frontend verarbeitet, sondern unverändert an das Backend gesendet wird, das das Leerzeichen entfernt.
Das Tool probiert eine Vielzahl von Zeichen als Leerzeichen aus: Es umfasst , \t, \v, \x00 und die Unicode-Leerzeichen.
Um einen Header zu schmuggeln, ersetzen wir den Bindestrich (-) durch einen Unterstrich (_). Wenn das Backend in gewisser Weise von CGI inspiriert ist, könnte es Header wie Header-Name in die HEADER_NAME-Form umwandeln; daher wird der Bindestrich ohnehin zum Unterstrich. Bei der Bestimmung, wie der Textkörper geparst werden soll, fragt ein solches Backend vermutlich den Wert von CONTENT_LENGTH / TRANSFER_ENCODING aus seinem Header-Wörterbuch ab, und dieser wird dort vorhanden sein.
Diese Technik ist HTTP/2-spezifisch. Da HTTP/2 ein binäres Protokoll ist, können wir versuchen, Zeilenumbrüche im Header-Namen oder -Wert zu senden. Der Standard verbietet dies, aber wir hoffen, eine Implementierung zu finden, die dies dennoch akzeptiert.
Während der HTTP/2 -> HTTP/1.1-Konvertierung teilt sich der Header in zwei verschiedene Header auf, was bedeutet, dass die Anfrage für das Backend anders aussieht.
Um einen Header zu schmuggeln, setzen wir seinen Namen und Wert nach einem Zeilenumbruch: Ein Header mit dem Namen "Transfer-Encoding" und dem Wert "chunked" wird zu einem mit dem Namen "fake" und dem Wert "fake\r\ntransfer-encoding: chunked".
Angenommen, das Backend verwendet eine höhere Programmiersprache und führt keine ausreichende Header-Validierung durch. In diesem Fall könnte es die Namen vor jeder weiteren Aktion in Großbuchstaben umwandeln und dies mit Unicode-fähigen Funktionen tun. Glücklicherweise enthält TRANSFER-ENCODING den Buchstaben S, der in Großbuchstaben als ſ (\u017f) umgewandelt wird.
Ähnlich können wir nach einem Backend suchen, das den Wert von Transfer-Encoding in Kleinbuchstaben umwandelt: Wir senden chunKed anstelle von chunked mit \u212a anstelle von K.
Natürlich ist es erforderlich, dass das Frontend UTF-8-Header-Namen/-Werte an das Backend weiterleitet.
Um das Tool zu installieren, führen Sie go install github.com/neex/http2smugl@latest aus.
Das Tool enthält zwei Unterbefehle: request und detect. Der erste dient lediglich zum Erstellen von HTTP/2-Anfragen: Die meisten Client-Tools akzeptieren keine ungültigen Header, daher ist es praktisch, eines zu haben, das Benutzereingaben unverändert an den Server sendet.
Der andere ist detect. Er versucht verschiedene Techniken des HTTP-Request-Smuggling, um zu erkennen, ob das Ziel anfällig ist. Der Erkennungsalgorithmus ist kompliziert; ich würde es begrüßen, wenn Sie ihn durchlesen und mir Ihre Kommentare senden. Er wird weiter unten im entsprechenden Abschnitt beschrieben.
http2smugl requestVerwenden Sie diesen Unterbefehl, um eine (vielleicht leicht fehlerhafte) HTTP/2-Anfrage zu senden. Der erste Parameter ist die URL, und die anderen sind einfach Header im Format name:value (beachten Sie, dass nach dem Doppelpunkt kein Leerzeichen steht). Backslash-Escape wird unterstützt: Sie können die Escape-Codes \r, \n und \xXX verwenden. Um z. B. einen Header-Namen zu senden, der einen Doppelpunkt enthält, verwenden Sie \x3a (z. B. name\x3awith\x3acolons:value).
http2smugl detectDieser Unterbefehl versucht, das HTTP-Request-Smuggling mithilfe verschiedener Techniken zu erkennen. Um ihn zu verwenden, führen Sie einfach http2smugl detect [HTTPS URL] aus.
Der Befehl gibt nur dann etwas aus, wenn er erkennen kann, dass der Server einen geschmuggelten Header parst. Um zu verstehen, was das bedeutet, lesen Sie bitte den entsprechenden Abschnitt.
Eine experimentelle Unterstützung für HTTP/3 (QUIC) wurde implementiert. Ich empfehle jedoch nicht, sie zu verwenden, da ich keinen einzigen Fehler im Zusammenhang mit HTTP/3 gefunden habe.
Um HTTP/3 mit dem Unterbefehl request zu verwenden, geben Sie https+h3:// als Protokoll in der URL anstelle von nur https. Dasselbe wird im detect-Befehl unterstützt.
Es gibt auch ein Flag --try-http3 für den Unterbefehl request, das das Verhalten ändert, falls das Protokoll in der URL nicht angegeben ist (nur Hostname). Wenn das Flag vorhanden ist, wird der Befehl für solche Einträge in der Befehlszeile oder in der Zieldatei (sowie HTTP/2) das https+h3-Protokoll versuchen. Z. B. wird http2smugl detect --try-http3 www.example.com sowohl HTTP/3 als auch HTTP/2 versuchen, aber http2smugl detect --try-http3 https://www.example.com/ wird dennoch nur HTTP/2 versuchen.
In diesem Abschnitt beschreibe ich einige Fälle, in denen das Tool sagt, die Antworten seien „unterscheidbar“, obwohl keine Schwachstelle existieren könnte.
Der Elastic Load Balancer von Amazon implementiert mehrere Gegenmaßnahmen gegen HTTP-Request-Smuggling. Obwohl nicht alle davon die Anfrage standardmäßig ablehnen, wird ELB nach dem Senden einer Anfrage mit „verdächtigen“ Headern keine Verbindung wiederverwenden. Somit ist ein tatsächlicher Angriff nicht möglich.
Um zu erkennen, dass Sie es mit ELB zu tun haben, können Sie den Server-Antwortheader verwenden. Wenn er herausgefiltert wird, können Sie eine Anfrage mit dem content__length-Header (beachten Sie die zwei Unterstriche) und dem Wert -1 senden: Wenn es 400 ist, handelt es sich wahrscheinlich um ELB oder eine andere WAF (siehe unten).
Apache Traffic Server wird hauptsächlich bei Yahoo eingesetzt. Es verarbeitet HTTP/2 auf ungewöhnliche Weise: Es konvertiert es im Speicher in HTTP/1.1 und parst dann die resultierende Anfrage erneut. Obwohl ein Header technisch „geschmuggelt“ werden könnte, gibt es keine Möglichkeit, dass dies zu einer Schwachstelle führt: Sie könnten dieselben Bytes an eine HTTP/1.1-Verbindung senden.
Der einfachste Weg, um zu erkennen, dass Sie es mit ATS zu tun haben (neben dem Server-Header), ist das Senden einer TRACE-Anfrage. Wenn ein Max-Forwards: 0-Header in der Anfrage vorhanden ist, gibt ATS standardmäßig eine Antwort auf TRACE-Anfragen zurück, ohne sie an das Backend weiterzuleiten.
Es scheint, dass Microsoft IIS die Dekodierung von chunked-Encoding innerhalb von HTTP/2-Textkörpern unterstützt. Dies ist ein seltsames Verhalten, aber aus Sicherheitssicht harmlos.
Um zu erkennen, dass Sie es mit IIS zu tun haben, können Sie eine Anfrage mit einem "transfer-encoding:chunked"-Header und einem falschen chunked-Textkörper senden. Wenn Sie Microsoft-HTTPAPI oder ähnliches im Server-Header sehen, ist es das.
WAFs versuchen, verdächtige Header zu erkennen – das ist ihre Aufgabe. Manchmal führt dies dazu, dass die Tools „unterscheidbar“ sagen: Die WAF könnte etwas wie content_length:-1 blockieren und content_length:1 erlauben, nur weil ihre Filter zufällig zu diesen Entscheidungen kommen.
Wenn Sie eine WAF im Server-Header sehen, handelt es sich wahrscheinlich um einen Fehlalarm.
Wenn Sie Ideen zu diesem Thema haben, kontaktieren Sie mich über @emil_lerner auf Twitter oder @neexemil auf Telegram – oder posten Sie ein Issue hier auf GitHub.
Beide Werte sind aus Sicht des Frontends ungültig: Es gibt einen anderen Mechanismus zur Bestimmung der Textlänge in HTTP/2, und in beiden Fällen wird kein Anfragetext gesendet. Wenn die Antworten unterschiedlich sind, nehmen wir an, dass der Backend-Server die Header geparst hat.
Diese Methode ist die am wenigsten zuverlässige: Das Frontend könnte unterschiedliche Fehler ausgeben, wenn Content-Length einen ungültigen Wert hat, und wenn er nicht mit der tatsächlichen Länge übereinstimmt.