画像の URL を1つ受け取っただけなのに、なぜそれがクラウドの認証情報を盗む入口になるのか?
あなたも「画像の URL を貼ってください」という入力欄や、Slack のような Webhook URL 登録フォームを実装したことがあるはずだ。ユーザーが指定した URL をサーバーが代わりに取得するだけの、一見無害な機能に見える。だが、そのリクエストを実際に発行しているのはブラウザではなくサーバー自身だと意識したことはあるだろうか。
- SSRF はユーザーが指定した URL をサーバー自身に取得させることで、外部からは直接届かない内部ネットワークやクラウドのメタデータサービスに間接的に到達する攻撃である。
- IP アドレスのブロックリストは表記ゆれ・DNS リバインディング・リダイレクトで回避されやすく、OWASP は行き先を明示的に絞る許可リスト方式を推奨している。
- AWS の IMDSv2 はトークン取得に PUT リクエストとカスタムヘッダーを必須化し、既定でホップリミットを 1 に制限することで、単純な URL 指定型の SSRF から認証情報を守る設計になっている。
SSRF とは何か — サーバーを「代理人」にする攻撃
通常、外部の攻撃者がアプリケーションの内部ネットワーク(管理用ダッシュボード、DB の管理ポート、クラウドのメタデータサービスなど)に直接アクセスすることはできない。ファイアウォールや VPC のルーティングが、外部からの経路そのものを塞いでいるからだ。しかし「ユーザーが指定した URL をサーバー側で取得する」機能があると話は変わる。攻撃者は自分では触れない内部アドレスを、サーバーに代わりに取りに行かせることができてしまう。これが SSRF(Server-Side Request Forgery)である。
外部の攻撃者 Webサーバー │ URLを指定 │ │───────────────────▶│ fetch(url) │ │ │ ▼ │ ┌───────────────────┐ │ 直接は │ 169.254.169.254 │ │ 届かない │ 社内DB・管理画面 等 │ │ └───────────────────┘
このような機能はよくある。画像 URL からサムネイルを生成する、Webhook 登録用の URL へ後で通知を送る、PDF 生成ツールに URL を渡して読み込ませる、といった実装だ。共通するのは「URL の文字列を受け取り、サーバーの HTTP クライアントで fetch する」という処理で、その HTTP クライアントは通常、社内ネットワークやクラウド基盤の内部にも届く場所で動いている。
なぜクラウド環境で特に危険なのか
AWS では、インスタンス自身が 169.254.169.254 というリンクローカルアドレスに問い合わせることで、自分に割り当てられた IAM ロールの一時的な認証情報を取得できる。この Instance Metadata Service(IMDS)は本来インスタンス自身のプロセスだけが使う想定だが、SSRF でサーバーに「このアドレスを取りに行って」と指示できれば、攻撃者は一度も AWS にログインすることなく、そのインスタンスに紐づいた IAM ロールの権限をまるごと持ち出せてしまう。
旧来の IMDSv1 は単純な GET リクエストだけでこの認証情報を返していたため、URL を一つ渡すだけの典型的な SSRF と相性が良すぎた。後継の IMDSv2 は、まず PUT リクエストで最大 21,600 秒(6 時間)有効なセッショントークンを取得し、そのトークンを後続の GET リクエストに X-aws-ec2-metadata-token ヘッダーとして含めないと値を返さない 2 段階の設計にした。多くの SSRF はアプリケーションが「渡された URL を GET する」処理しかできず、HTTP メソッドや任意ヘッダーまでは攻撃者が制御できないため、この一手間だけで素通りできなくなるケースが多い。さらに IMDSv2 は PUT レスポンスのホップ数を既定で 1 に制限しており、リバースプロキシなどをもう 1 段挟んで転送しようとするとトークンが届かなくなる。
オフィスビルの受付は、来訪者から預かった荷物を社内の郵便受けへ届けられる。来訪者自身はセキュリティゲートの先には入れないが、「この荷物を郵便受け B まで運んでください」と受付に頼めば、受付は自分の権限で建物の奥まで運んでくれる。もし受付が伝票の行き先を確認せず何でも運んでしまえば、来訪者は実質的に建物の奥(社外秘の部屋)まで荷物を届けさせられる。SSRF は、サーバーという「建物内を自由に動ける受付」を外部の入力どおりに動かしてしまう問題である。
ブロックリストで内部アドレスを弾く、はなぜ不十分なのか
「127.0.0.1 や 169.254.169.254、10.0.0.0/8 のような内部アドレスを文字列でブロックすればいい」と考えたくなる。しかし OWASP の SSRF 防止ガイドは、こうしたブロックリスト方式は回避されやすいと明言している。IP アドレスには 127.0.0.1 と同じ意味を持つ 127.1 のような省略表記や 8 進数・16 進数表記など複数の書き方があり、正規表現一つで全パターンを弾くのは難しい。加えて DNS リバインディングという手法では、攻撃者が管理するドメインが最初の検証時には無害な外部 IP を返し、実際にリクエストを送る瞬間にだけ内部 IP へ切り替わるため、URL の検証とリクエストの実行の間にタイムラグがあるだけで突破されてしまう。
HTTP リダイレクトも同様の抜け穴になる。検証時には許可リストに載った外部ドメインの URL でも、そのサーバーが 302 で内部アドレスへリダイレクトを返せば、HTTP クライアントが標準でリダイレクトを追跡する設定になっている限り、最終的な接続先は検証をすり抜けた別のホストになる。
正しい防ぎ方 — 許可リストとリダイレクトの遮断
OWASP が勧める方向は逆転の発想で、通信を「塞ぐ」のではなく通す先を明示的に決める許可リスト(allowlist)方式である。URL 全体を信用するのではなく、行き先のホスト名や IP アドレスをあらかじめ決めた許可リストと厳密に照合し、一致したときだけサーバー側でリクエストを組み立てる。任意の URL をそのまま HTTP クライアントに渡すことを避け、スキーム・ホスト・パスを検証済みの値から自分で組み立て直すことで、URL パーサーの解釈違いに起因する回避も防げる。
加えて、HTTP クライアントのリダイレクト追跡はデフォルトで無効化するか、追跡するリダイレクト先ごとに同じ許可リストで再検証する。外部との自由な通信が業務上必要なケース(汎用の Webhook 機能など)では許可リストだけでは対応しきれないため、アプリケーション層の検証に加えてネットワーク層でも内部セグメントへの到達を遮断する多層防御が推奨される。
画像 URL からサムネイルを作る機能、Slack 風の Webhook 登録、外部 PDF・HTML を取り込むエクスポート機能などは、実装時に「URL の文字列を受け取って HTTP クライアントに渡す」までを素朴に書くと SSRF の入口になりやすい。特に AWS・GCP 上で動くアプリでは、クラウドのメタデータサービスへの到達を意識し、許可リストとリダイレクト無効化をセットで検討する価値がある。
「検証済みの URL を fetch するだけ」の処理が、HTTP クライアントのリダイレクト追跡によってどう裏切られるかをローカルで再現する。python3 だけで動く。
mkdir -p /tmp/ssrf-demo && cd /tmp/ssrf-demo
echo "internal-only-secret-data" > secret.txt
python3 -m http.server 9001 --directory /tmp/ssrf-demo &
cat > redirect.py <<'EOF'
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(302)
self.send_header('Location', 'http://127.0.0.1:9001/secret.txt')
self.end_headers()
HTTPServer(('127.0.0.1', 8000), Handler).serve_forever()
EOF
python3 redirect.py &
curl -s -o /dev/null -w '1回目(リダイレクト追跡なし): %{http_code}\n' http://127.0.0.1:8000/
curl -sL http://127.0.0.1:8000/
kill %1 %2
1 回目の curl は 302 が返るだけで止まる。2 回目は -L でリダイレクトを自動追跡した結果で、最初にアクセス許可を検証したはずの 127.0.0.1:8000 とは別のホスト・ポート(9001)から internal-only-secret-data が返ってくる。許可リストで最初の URL だけを検証しても、クライアントがリダイレクトを追跡すれば最終的な接続先はすり抜けることが体感できる。
- URL を渡すだけの機能だから、危険があるとしても大した被害にはならない — サーバー自身の通信経路とクラウドの権限を借りて内部ネットワークや IAM ロールの認証情報にまで到達できるため、外部に公開していない資産が丸ごと危険にさらされる。
- 内部アドレスを正規表現や IP の範囲でブロックリストに入れれば防げる — 127.1 のような表記ゆれ、DNS リバインディング、リダイレクトなど複数の回避手段があり、OWASP は許可リスト方式への転換を推奨している。
- IMDSv2 にしておけば SSRF の心配はなくなる — IMDSv2 はメタデータの窃取を難しくする対策の一つに過ぎず、内部 DB や管理画面などメタデータ以外の内部資産への到達自体は防げないため、アプリ側の許可リストとネットワーク分離は依然として必要になる。
- SSRF
- サーバー自身に任意の URL へリクエストさせ、外部からは直接到達できない内部ネットワークへ間接的にアクセスする攻撃手法。
- IMDSv2
- AWS のインスタンスメタデータサービスの新版。PUT で取得したトークンを GET リクエストのヘッダーに含めないと値を返さない 2 段階方式。
- 許可リスト(allowlist)
- 通信を許可する宛先をあらかじめ列挙し、一致したものだけを通す検証方式。ブロックリストと対になる考え方。
- DNS リバインディング
- 検証時と実際の通信時でドメインの名前解決結果を意図的に変える手法。URL 検証とリクエスト実行の間のタイムラグを突く。
- ホップリミット
- IMDSv2 の PUT レスポンスがネットワーク上で許容される転送回数の上限。既定値は 1 で、追加の転送を挟むとトークンが届かなくなる。
- OWASP: Server Side Request Forgery Prevention Cheat Sheet — 許可リスト方式と URL を自分で組み立て直す具体的な実装指針、ブロックリストが破られる理由を公式にまとめている。
- AWS: Use the Instance Metadata Service to access instance metadata — IMDSv2 の PUT + トークン方式と既定のホップリミットの仕様を一次情報として確認できる。
- OWASP Top 10:2021 — A10 Server-Side Request Forgery (SSRF) — SSRF が Top 10 に採用された経緯と典型的な脆弱パターンの分類を解説する公式ページ。
- AWS Security Blog: Add defense in depth against open firewalls, reverse proxies, and SSRF vulnerabilities with enhancements to the EC2 Instance Metadata Service — IMDSv2 が SSRF・オープンファイアウォール経由の攻撃をどう防ぐ設計かを AWS のセキュリティチームが解説している。