WordPressで考えるSSRFホワイトリストバイパス|URL解釈のズレで内部アクセスが可能になる仕組み

SSRF対策として、「特定のURLのみアクセスを許可する」というホワイトリスト方式が使われることがあります。

一見すると安全に思えますが、この方法だけでは不十分です。

今回のWeb Security Academyのラボ「SSRF with whitelist-based input filter」では、「stock.weliketoshop.net のみ許可」という制限があるにも関わらず、内部の localhost にアクセスし、管理者ユーザーを削除することができました。

この記事では、その仕組みを初心者向けに整理しながら、WordPressで同様の問題が起きるケースについても解説します。

SSRFホワイトリストバイパスで、フィルタの判定と実際のアクセス先が異なる仕組みを示す図

SSRF with whitelist-based input filterラボの概要

  • 在庫チェック機能が外部URLにリクエストを送信する
  • 許可されているホストは、stock.weliketoshop.netのみ
  • 目標
    http://localhost/admin にアクセスし、ユーザー carlos を削除する

SSRFホワイトリストバイパスの流れ

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 に送信される
  • 管理画面の削除処理が実行される

ラボ解決となります。


なぜこの攻撃が成立するのか

SSRFバイパスにより内部リソースへアクセスする流れを示す図

このラボの本質は「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でも同様の実装は十分あり得るため、注意が必要です。


このブログの運営環境

  • WordPress
  • GeneratePress
  • エックスサーバー

エックスサーバー公式サイト