
Der SSRF-Filter prüfte den Hostname-Text, aber das tatsächliche Ziel wurde später durch DNS bestimmt. Diese Lücke ermöglichte es angreifergesteuerten Webhook-URLs, Loopback-, Metadaten- und private Netzwerkziele zu erreichen.
Der SSRF-Filter überprüfte den Hostnamen-Text, aber das tatsächliche Ziel wurde später durch DNS bestimmt. Diese Lücke erlaubte es angreiferkontrollierten Webhook-URLs, Loopback-, Metadaten- und private Netzwerkziele zu erreichen.
Ich habe diesen Fehler gefunden, während ich Typebot, einen Open-Source-Chatbot-Builder, mit einer einfachen Sicherheitsfrage im Hinterkopf überprüft habe:
Was passiert, wenn der SSRF-Schutz den Hostnamen-Text validiert, das tatsächliche Ziel aber später durch DNS bestimmt wird?
In diesem Fall führte diese Frage zu einem echten Bug.
Der SSRF-Schutz von Typebot für Webhook / HTTP-Request-Blöcke validierte nur:
Er hat nicht Hostnamen aufgelöst, bevor er die Anfrage erlaubte.
Das bedeutete, dass ein Hostname wie ssrf-repro.example während der Validierung harmlos aussehen konnte, sich dann aber auflösen konnte in:
127.0.0.1169.254.169.254und trotzdem vom Backend-HTTP-Client abgerufen wurde.
Dieser Fehler wurde CVE-2026-34207.
Typebot: Typebot auf GitHub
CVE: CVE-2026-34207
Behoben in: 3.16.0
Dies betraf Typebot, eine weit verbreitete Open-Source-Chatbot-Plattform. Auf der offiziellen Website wird Typebot als vertrauenswürdig von 650+ Unternehmen weltweit präsentiert. Die Website wirbt außerdem mit 2M+ monatlichen Chats und 1.5M+ veröffentlichten Bots.
angreiferkontrollierte Webhook-URL -> Hostname passiert nur literale SSRF-Validierung -> keine DNS-Auflösung vor der Entscheidung -> Backend-HTTP-Client löst Hostname zu internem Ziel auf -> serverseitige Anfrage erreicht Loopback / Metadaten / privates Netzwerk -> Antwortdaten werden durch Ausführungsprotokolle verfügbar
Typebot ist ein Chatbot-Builder.
Es ermöglicht Benutzern, Abläufe zu erstellen, die:
Webhook / HTTP-Request-Blöcke auslösenDas bedeutet, dass die Ausführung ausgehender Anfragen eine echte Sicherheitsgrenze darstellt.
Die wichtige Frage war hier nicht, ob Typebot Webhook-Blöcke unterstützt.
Die eigentliche Frage war:
Validiert der SSRF-Schutz das tatsächliche Ziel, mit dem der Server eine Verbindung herstellt, oder nur den Hostnamen-Text, der in der URL erscheint?
In diesem Fall validierte er zuerst nur die Textform.
Das war der Fehler.
SSRF-Abwehrmechanismen versagen auf sehr vorhersehbare Weise.
Meistens sind die interessanten Fehler nicht:
169.254.169.254 zu blockieren"localhost zu blockieren"Die stärkeren Fehler sind Grenzfehler:
Das war hier der richtige Ansatzpunkt.
Typebot hatte bereits SSRF-Härtungslogik für literale Metadaten-IPs, Loopback, private Bereiche und codierte IP-Tricks.
Das machte die nächste Frage offensichtlich:
Was ist, wenn der Hostname wörtlich nicht gefährlich ist, sich aber später in ein gefährliches Ziel auflöst?
Genau das ist passiert.
Die Grundursache war die Zielvalidierung basierend auf Hostnamen-Text anstatt der aufgelösten IP-Adresse.
In der anfälligen Implementierung hat validateHttpReqUrl():
http: und https: erlaubtmetadata.google.internal, metadata.goog, metadata und localhost blockiertDas ist der wichtige Teil.
Wenn der Hostname ein normaler Wert war wie:
ssrf-repro.example
dann gab parseIPAddress(hostname) null zurück, und der Validator hörte dort auf.
Es fand keine DNS-Auflösung vor der Genehmigung statt.
Die anfällige Logik reduzierte sich also effektiv auf:
const ip = parseIPAddress(hostname);
if (ip) {
validateIPAddress(ip);
}
Das bedeutet:
Die zweite Hälfte des Bugs lag im Ausführungspfad.
In executeHttpRequest() führte Typebot zuerst die Validierung durch und später die eigentliche Anfrage mit ky(request.url, ...).
Die Reihenfolge war also:
Das ist die gesamte Schwachstelle.
Der wichtige Unterschied ist wo die Vertrauensentscheidung getroffen wurde.
Viele Bugs wirken klein, wenn man sie schlecht beschreibt.
Wenn man diesen Bug beschreibt als:
"Der Hostname-Filter war unvollständig"
klingt das nach einem Qualitätsproblem.
Das ist nicht das eigentliche Problem.
Das eigentliche Problem war:
Das ist keine kosmetische Filtersschwäche.
Das ist ein Vertrauensgrenzenfehler.
Und weil der HTTP-Ausführer Antwortdaten in Ausführungsprotokollen aufzeichnete, war das Problem nicht einmal in den stärksten Fällen blind.
Es war also nicht nur:
Es war:
Das ist eine echte SSRF-Schwachstelle.
Ich habe zwei Proof-Ebenen verwendet, weil sie zwei verschiedene Dinge demonstrierten.
Der erste PoC isolierte die Grundursache sauber.
Ich verwendete eine kleine lokale Testumgebung, die:
127.0.0.1 startetehttp://ssrf-repro.example:18080/... validiertessrf-repro.example zu 127.0.0.1 aufgelöst wurdeDas demonstrierte den genauen Fehler:
Die erfasste Ausgabe zeigte:
Das bewies die Validierungslücke direkt.
Der zweite PoC zeigte den Bug durch den tatsächlichen Feature-Pfad, der relevant ist.
Die einfachste Reproduktion war:
Webhook-Block, der auf diesen harmlosen Hostnamen zeigtEin repräsentativer Block sah so aus:
{
"id": "blk-webhook",
"type": "Webhook",
"options": {
"webhook": {
"method": "GET",
"url": "http://ssrf-repro.example:8000/"
}
}
}
Mit einem Hosts-Datei-Eintrag wie:
127.0.0.1 ssrf-repro.example
akzeptierte der Validator die URL dennoch, weil:
ssrf-repro.example nicht in blockedHostnames warlocalhost warparseIPAddress("ssrf-repro.example") gab null zurückDann löste der Backend-HTTP-Client den Hostnamen zu 127.0.0.1 auf und stellte trotzdem eine Verbindung her.
Damit war die vollständige Behauptung aufgestellt:
Der erste PoC beweist die Grundursache.
Der zweite PoC beweist die Produktauswirkung.
Diese Trennung ist wichtig.
Wenn man nur zeigt:
"Dieser Filter akzeptiert einen Hostnamen"
hat man nicht genug gezeigt.
Wenn man nur zeigt:
"Eine interne Anfrage ist aufgetreten"
hat man nicht isoliert, warum.
Der stärkere Bericht ist:
Das ist die vollständige Geschichte.
Dieses Problem wurde vernünftigerweise als Hoch eingestuft.
Die Advisory-Klassifizierung war:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L
Das macht Sinn.
Dies war für sich genommen keine offene, nicht authentifizierte, internetweite SSRF.
Die erforderlichen Privilegien waren Niedrig, da der eigenständige Bug einen Akteur erforderte, der einen Webhook / HTTP Request konfigurieren oder auslösen konnte.
Aber innerhalb dieser Grenze war die Auswirkung schwerwiegend:
Das ist für sich genommen ein starkes SSRF-Problem.
Und es wird noch gefährlicher, wenn es mit anderen Bugs kombiniert wird, die die Erreichbarkeit erweitern.
Manche Leute sehen authentifizierte SSRF und unterschätzen sie sofort.
Das ist ein Fehler.
Die eigentliche Frage ist nicht:
"War der Angreifer eingeloggt?"
Die eigentliche Frage ist:
"Konnte dieser Angreifer den Server dazu bringen, sich mit etwas zu verbinden, was das Sicherheitsmodell verbieten sollte?"
Hier war die Antwort ja.
Der Validator behauptete, Folgendes zu schützen:
Aber eine Hostname-Auflösungslücke ließ dieselben Ziele durch eine andere Darstellung wieder hinein.
Das ist genau die Art von Bug, die ein CVE verdient.
Der Fix war solide, weil er die tatsächliche Grenze korrigierte, nicht nur ein Symptom.
In der gepatchten Implementierung wurde validateHttpReqUrl() geändert, um Hostnamen vor der Genehmigung der Anfrage aufzulösen.
Der Validator:
lookup aus node:dns/promisesDas ist der richtige Fix, weil er die Vertrauensentscheidung ändert von:
zu:
Das ist die Sicherheitseigenschaft, die von Anfang an hätte existieren sollen.
Die Behebung fügte auch Regressionstests hinzu für:
Das ist die Art von Härtung, die man in einem echten SSRF-Fix haben möchte:
Das Problem wurde in Typebot 3.16.0 behoben.
Dieses Problem wurde privat über den Sicherheitsmeldeflow von GitHub gemeldet.
Der Bericht enthielt:
validateHttpReqUrl()executeHttpRequest()Die Maintainer akzeptierten das Problem, korrigierten die Validierungslogik, und die Schwachstelle wurde später veröffentlicht als:
CVE-2026-34207
mit dem Fix in Typebot 3.16.0.
Die wichtigste Lektion hier ist einfach:
Sicherheitsentscheidungen müssen auf derselben Darstellung getroffen werden, die der Netzwerk-Stack tatsächlich verwenden wird.
Das klingt offensichtlich.
Aber viele SSRF-Schutzmaßnahmen scheitern genau deshalb, weil sie dieser Regel nicht folgen.
Das reicht.
Dieser Bug unterstreicht auch etwas Wichtiges bei der SSRF-Überprüfung im Allgemeinen:
Wenn Sie das tatsächliche Ziel nicht auflösen und validieren, ist Ihr SSRF-Filter immer noch unvollständig.
Das ist die eigentliche Erkenntnis.
Diese Schwachstelle drehte sich nicht um eine raffinierte Payload.
Es ging darum, die richtige Grenzfrage zu stellen.
Bei Typebot prüfte der SSRF-Validator zuerst den Hostnamen-Text. Das tatsächliche Ziel wurde später durch DNS bestimmt. Der Netzwerk-Client folgte der aufgelösten Adresse.
Diese Lücke war der Bug.
Deshalb wurde dies CVE-2026-34207.
Behoben in Typebot 3.16.0.