
SSRFフィルターはホスト名のテキストをチェックしていましたが、実際の接続先は後でDNSによって決定されました。このギャップにより、攻撃者が制御するWebhook URLがループバック、メタデータ、プライベートネットワークのターゲットに到達可能になりました。
この問題は、オープンソースのチャットボットビルダーであるTypebotをレビューしているときに、シンプルなセキュリティの問いを念頭に発見しました。
SSRF保護がホスト名のテキストを検証しても、実際の接続先が後でDNSによって決定されたらどうなるか?
この場合、その問いは実際のバグにつながりました。
TypebotのWebhook / HTTPリクエストブロックに対するSSRF保護は、以下しか検証していませんでした:
リクエストを許可する前にホスト名を解決していませんでした。
つまり、ssrf-repro.exampleのようなホスト名は、検証時には無害に見えても、後で以下のように解決される可能性がありました:
127.0.0.1169.254.169.254そして、バックエンドのHTTPクライアントによってそれでも取得されていました。
この問題はCVE-2026-34207になりました。
Typebot: Typebot on GitHub
CVE: CVE-2026-34207
修正バージョン: 3.16.0
これは、広く使われているオープンソースのチャットボットプラットフォームであるに影響しました。公式サイトでは、Typebotはに信頼されていると紹介されています。また、サイトではとを宣伝しています。

攻撃者が制御するWebhook URL -> ホスト名がリテラルのみのSSRF検証を通過 -> 許可判断前にDNS解決なし -> バックエンドHTTPクライアントがホスト名を内部ターゲットに解決 -> サーバーサイドリクエストがループバック/メタデータ/プライベートネットワークに到達 -> 実行ログを通じてレスポンスデータが利用可能に
Typebotはチャットボットビルダーです。
ユーザーは以下のようなフローを作成できます:
Webhook / HTTPリクエストブロックを通じてアウトバウンドHTTPリクエストをトリガーするつまり、アウトバウンドリクエストの実行は実際のセキュリティ境界です。
ここでの重要な質問は、TypebotがWebhookブロックをサポートしているかどうかではありませんでした。
本当の質問は:
SSRF保護は、サーバーが実際に接続する接続先を検証しているのか、それともURLに現れるホスト名テキストだけを検証しているのか?
この場合、最初にテキスト形式のみを検証していました。
それが間違いでした。
SSRF防御は非常に予測可能な方法で失敗します。
ほとんどの場合、興味深いミスは次のようなものではありません:
169.254.169.254をブロックし忘れた」localhostをブロックし忘れた」より深刻なミスは境界のミスです:
ここで注目すべきはまさにその点でした。
Typebotはすでに、リテラルのメタデータIP、ループバック、プライベートレンジ、エンコードされたIPトリックに対するSSRF強化ロジックを持っていました。
そのため、次の質問が明白になりました:
ホスト名自体は危険ではなくても、後で危険な接続先に解決される場合はどうなるか?
それがまさに起こったことです。
根本的な問題は、解決されたIPアドレスではなく、ホスト名テキストに基づく接続先検証でした。
脆弱な実装では、validateHttpReqUrl()は:
http:とhttps:のみ許可metadata.google.internal、metadata.goog、metadata、localhostなどのリテラルホスト名の短いリストをブロックこれが重要な部分です。
ホスト名が通常の値(例:ssrf-repro.example)だった場合、parseIPAddress(hostname)はnullを返し、バリデーターはそこで停止しました。
承認前にDNS解決は行われませんでした。
したがって、脆弱なロジックは実質的に次のようになりました:
const ip = parseIPAddress(hostname);
if (ip) {
validateIPAddress(ip);
}
つまり:
バグの後半は実行パスにありました。
executeHttpRequest()では、Typebotは最初に検証を実行し、その後、ky(request.url, ...)を使用して実際のリクエストを行いました。
したがって、シーケンスは:
これが脆弱性全体です。
重要な区別は、信頼の判断がどこで行われたかです。
多くのバグは、悪い説明をすると小さく見えます。
これを次のように説明すると:
「ホスト名フィルターが不完全だった」
品質の問題のように聞こえます。
それが本当の問題ではありません。
本当の問題は:
これは表面的なフィルタリングの弱点ではありません。
これは信頼境界の失敗です。
そして、HTTP実行エンジンが実行ログにレスポンスデータを記録していたため、この問題は最も強いケースでもブラインドではありませんでした。
したがって、これは単なる:
だけではありませんでした。
それは:
これは実際のSSRF脆弱性です。
2つのPoCレイヤーを使用しました。それぞれ異なることを示すためです。
最初のPoCは根本原因を明確に切り分けました。
小さなローカルハーネスを使用しました:
127.0.0.1でループバックHTTPサーバーを起動http://ssrf-repro.example:18080/...のようなURLを検証ssrf-repro.exampleが127.0.0.1に解決されるようにするこれにより、正確な欠陥が実証されました:
キャプチャされた出力は以下を示しました:
これにより、検証のギャップが直接証明されました。
2つ目のPoCは、問題のある実際の機能パスを通じてバグを示しました。
最も簡単な再現手順は:
Webhookブロックを作成する代表的なブロックは次のようになります:
{
"id": "blk-webhook",
"type": "Webhook",
"options": {
"webhook": {
"method": "GET",
"url": "http://ssrf-repro.example:8000/"
}
}
}
次のようなhostsファイルエントリがある場合:
127.0.0.1 ssrf-repro.example
バリデータは依然としてURLを受け入れました。なぜなら:
ssrf-repro.exampleはblockedHostnamesに含まれていなかったlocalhostではなかったparseIPAddress("ssrf-repro.example")はnullを返したその後、バックエンドHTTPクライアントはホスト名を127.0.0.1に解決し、接続しました。
これにより、完全な主張が確立されました:
最初のPoCは根本原因を証明します。
2つ目のPoCは製品への影響を証明します。
この分割は重要です。
次のことだけを示しても:
「このフィルターはホスト名を受け入れる」
十分な証明にはなりません。
次のことだけを示しても:
「内部リクエストが発生した」
理由を切り分けていません。
より強力なレポートは:
これが完全なストーリーです。
この問題は合理的にHighと分類されました。
アドバイザリの分類は:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L
これは理にかなっています。
これはそれ自体では、認証されていないインターネット全体のSSRFではありませんでした。
必要な権限はLowでした。なぜなら、スタンドアロンのバグでは、Webhook / HTTPリクエストブロックを設定またはトリガーできるアクターが必要だったからです。
しかし、その境界内では、影響は深刻でした:
これ自体が強力なSSRF問題です。
そして、到達可能性を広げる他のバグと組み合わさると、さらに危険になります。
認証付きSSRFを見ると、すぐに過小評価する人もいます。
それは誤りです。
本当の質問は:
「攻撃者はログインしていたか?」
ではありません。
本当の質問は:
「その攻撃者は、セキュリティモデルが禁止するはずの場所にサーバーを接続させることができたか?」
ここでは、答えはイエスでした。
バリデータは以下を保護すると主張していました:
しかし、ホスト名解決のギャップにより、それら同じ送信先が別の表現を通じて再び侵入可能になりました。
これはまさにCVEに値する種類のバグです。
修正は堅牢でした。症状ではなく、実際の境界を修正したからです。
パッチ適用後の実装では、validateHttpReqUrl()はリクエストを承認する前にホスト名を解決するように変更されました。
バリデータは現在:
node:dns/promisesからlookupをインポートこれは正しい修正です。なぜなら、信頼判断を以下から変更するからです:
から:
へと変更します。
これが最初から存在すべきセキュリティ特性です。
また、修正では以下の回帰テストも追加されました:
これは実際のSSRF修正に求められる強化の種類です:
この問題はTypebot 3.16.0で解決されました。
この問題は、GitHubのセキュリティ報告フローを通じて非公開で報告されました。
報告には以下が含まれていました:
validateHttpReqUrl()の根本原因executeHttpRequest()の下流の実行パスメンテナーは問題を受け入れ、検証ロジックを修正し、脆弱性は後に次のように公開されました:
CVE-2026-34207
修正はTypebot 3.16.0でリリースされました。
ここでの重要な教訓はシンプルです:
セキュリティ判断は、ネットワークスタックが実際に使用するのと同じ表現で行われなければならない。
それは明白に聞こえます。
しかし、多くのSSRF保護はまさにそのルールに従わないために失敗します。
それだけで十分です。
このバグはまた、SSRFレビュー一般に関する重要なことを強化します:
実際の送信先を解決して検証しなければ、SSRFフィルターは依然として不完全です。
それが本当の教訓です。
この脆弱性は派手なペイロードに関するものではありませんでした。
適切な境界の問いを問うことに関するものでした。
Typebotでは、SSRFバリデータは最初にホスト名テキストをチェックしました。 実際の送信先は後でDNSによって決定されました。 ネットワーククライアントは解決されたアドレスに従いました。
そのギャップがバグでした。
それがこのissueがCVE-2026-34207になった理由です。
Typebot 3.16.0で修正されました。