SSRF (Server-Side Request Forgery) は、攻撃者が指定した URL を Web アプリのサーバが代わりに取りに行ってしまう脆弱性。攻撃の本質は「外から触れないはずだった内部リソースを、サーバを踏み台にして触らせる」点にある。サーバ自身は信頼境界の内側にいるので、内部 API・クラウドのメタデータエンドポイント・社内ネットワーク・localhost がすべて到達可能になる。OWASP Top 10 にも A10:2021 として独立カテゴリで追加され、Capital One の 1 億件流出 (2019) のような大規模事件の入口にもなり続けている。本稿では 3 タイプの分類 / URL パーサ回避や DNS rebinding の攻撃技法 / 著名事件 / Allowlist・IMDSv2・egress フィルタによる多層防御までを扱う。
SSRF とは何か — サーバを踏み台にする攻撃 #
Web アプリには「URL を受け取って、サーバ側でその URL の中身を取りに行く」機能がよくある。プレビュー生成、外部 API 連携、Webhook、ファイル取込、PDF レンダラ、画像リサイザ、OG 画像取得など。
このとき、URL を検証せずに HTTP クライアントに渡してしまうと、攻撃者は任意の宛先をサーバから叩かせることができる。外部の https://evil.example/ だけでなく、http://127.0.0.1:8080/admin や http://169.254.169.254/ のような 本来サーバの中からしか到達できないアドレス も指定できてしまう。
サーバは内部ネットワーク・クラウド管理面・隣接サービスに対しては 信頼された通信元として扱われていることが多い。SSRF はその信頼を丸ごと借りる。攻撃者は自分の IP からは届かなかった 内部 API・クラウドメタデータ・Redis/MySQL の管理ポート・社内 Confluence・192.168.x.x の機器 等にサーバ経由でアクセスできるようになる。XSS が「ブラウザの信頼境界を突破する」攻撃なら、SSRF は 「サーバの信頼境界を突破する」攻撃。
CSRF とは別物 — 名前は紛らわしいが構造が逆 #
CSRF (Cross-Site Request Forgery) は 被害者のブラウザに対象サイトへのリクエストを送らせる 攻撃。攻撃者は被害者のクッキー・認証を借りてくる。一方 SSRF は 対象サイトのサーバに別のサーバへのリクエストを送らせる。借りるのはサーバの IP / IAM ロール / 内部到達性。被害者のセッションは関係ない。両方とも「リクエストを偽造させる」点では共通だが、攻撃面も対策もまったく違う。
OWASP Top 10 における位置づけ #
2021 年版で A10:2021 — Server-Side Request Forgery として独立カテゴリ入りした。それまでは「Injection」や「Broken Access Control」に分散していたが、クラウド全盛で SSRF からの IAM 奪取が頻発するようになり、OWASP コミュニティ投票で 1 位になって独立した経緯がある。現代の Web 脆弱性の代表格 という扱い。
なぜ SSRF はクラウド時代に深刻なのか #
SSRF は 20 年以上前からある古典的脆弱性だが、クラウドが普及した 2010 年代後半から急に「致命的」扱いになった。理由ははっきりしている。
クラウドメタデータエンドポイント #
AWS / GCP / Azure などの IaaS は、各インスタンスが自分の IAM ロールに紐づく短期クレデンシャルをローカルの特殊 IP から取得する仕組みを持つ。
| クラウド | エンドポイント | 取れるもの |
|---|---|---|
| AWS | http://169.254.169.254/latest/meta-data/ |
IAM ロール名 + 一時クレデンシャル (AccessKey/Secret/Token) |
| GCP | http://metadata.google.internal/computeMetadata/v1/ |
サービスアカウントトークン (Metadata-Flavor: Google ヘッダ必要) |
| Azure | http://169.254.169.254/metadata/instance |
Managed Identity トークン (Metadata: true ヘッダ必要) |
| Alibaba/Oracle | http://100.100.100.200/ 等 |
同等のメタデータ |
これらは インスタンス自身からの HTTP リクエストには無認証で応答する。設計上は「インスタンスローカル通信」なので問題ない、という前提だったが、SSRF があるとその前提が崩れる — 外部攻撃者の指示でサーバが自分のメタデータを叩き、応答に含まれる IAM クレデンシャルを攻撃者に返してしまう。
取り出した一時クレデンシャル (AccessKey/SecretKey/SessionToken) は、そのインスタンスに付与された IAM ロールのすべての権限を持つ。インスタンスに s3:* や rds:* が付いていれば、攻撃者は自分の端末から AWS CLI でその権限を行使できる。Capital One 事件はまさにこのパターン。
内部ネットワーク・サービスメッシュ #
クラウド以外でも、社内ネットワーク・Kubernetes クラスタ内部・サービスメッシュ内では「同じネットワークの中なら互いに認証不要 or 緩い」設計が多い。SSRF は その「中の人」になりすますための切符 になる。
- Kubernetes の Pod 内から
http://kube-apiserver/api/v1/...を叩ける - 内部の管理用ダッシュボード (Jenkins / Grafana / Kibana / Consul) が認証なしで listen している
- 社内 LDAP / Active Directory / Confluence / GitLab の内部 URL
localhost:6379の Redis、localhost:9200の Elasticsearch (どちらも初期設定で認証なし)
つまり SSRF は「単独の脆弱性」ではなく「攻撃の入口」 #
XSS や SQLi のように「これ単独で被害が完結する」というより、ラテラルムーブメントの起点になる脆弱性。SSRF を 1 つ取ると、その先に 10〜100 の追加攻撃面が広がる。これが「クラウド時代に深刻」と言われる理由。
SSRF の 3 タイプ #
応答が攻撃者にどう見えるかで分類する。検出難易度と悪用しやすさが大きく違う。
Basic SSRF (応答が完全に見える) #
サーバが取得したレスポンスを そのままレスポンスとして返す タイプ。最も悪用しやすい。
代表シナリオ: 「URL を入れるとプレビューを返す」API、PDF 生成サービス、URL ベースの画像変換器。攻撃者は ?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ を投げ、応答 JSON にメタデータが丸ごと含まれる。
Blind SSRF (応答は見えないが副作用は起こせる) #
サーバはリクエストを発射するが、応答内容は攻撃者に返さない タイプ。「URL を Webhook で叩く」「外部 URL を起点に何かを保存する」系。直接の情報奪取は難しいが、次の方法で悪用できる:
- タイミング差分 — 内部ホスト到達可能性を応答時間で推定 (
192.168.0.1は数 ms、未使用 IP は数秒タイムアウト) - 副作用がある内部 API — 攻撃者は応答を見られなくても、内部 API の 状態変更操作 (削除・送金・権限付与) を引き起こせる
- OOB (Out-of-Band) 検出 — 攻撃者が制御する DNS / HTTP サーバへサーバが接触してきたかを別経路で観測。Burp Collaborator が代表ツール
Semi-Blind SSRF (部分的に見える) #
応答全体は返らないが、HTTP ステータスコード / Content-Length / エラーメッセージ等の 断片 が観測できるタイプ。ポートスキャンには十分。「200 OK = 開いてる、500 = 開いてないか応答不正」と切り分けて、内部ネットワークのサービス一覧を構築できる。
応答が見えなくても、内部 API への POST /admin/users/delete はそのまま実行される。SSRF は「読み取り」だけでなく「副作用のある書き込み」も発射できることを忘れがち。GET メソッドのみ叩ける制約があっても、内部 API が GET で破壊的操作を受け付けるならアウト (REST 原則違反の API は世の中に山ほどある)。
攻撃の典型コードと回避テクニック #
最小の脆弱コード #
<?php
// preview.php — URL を受けて中身を返す
$url = $_GET['url'];
header('Content-Type: text/plain');
echo file_get_contents($url); // ★ ここが穴
# 攻撃 (AWS の IAM クレデンシャルを取得)
https://victim.example/preview.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/web-role
file_get_contents / curl / requests.get / fetch — どの言語の HTTP クライアントでも、ユーザ入力 URL を素通し すれば SSRF が成立する。
スキーム制限の罠 #
「http:// と https:// だけ許可すればよい」と考えがちだが、HTTP クライアントによっては 他のスキーム をサポートしている。
| スキーム | 影響 |
|---|---|
file:// |
サーバのローカルファイル読み込み (file:///etc/passwd) |
gopher:// |
任意のバイト列をポートに送信できる。Redis や SMTP を SSRF から直接操作する古典攻撃 |
dict:// |
DICT プロトコル。ポートスキャンや一部サービスのレスポンス取得 |
ftp:// |
FTP サーバ操作。内部 FTP が立ってると刺さる |
ldap:// |
LDAP 検索。Log4Shell 系のペイロード起動にも使われた |
jar:// (Java) |
Java 特有。jar:http://...!/... で内部 ZIP 展開 |
PHP の curl_init はデフォルトで多数のスキームを許可している。CURLOPT_PROTOCOLS で明示的に絞る必要がある。
URL パーサ・正規化の落とし穴 #
防御側は「127.0.0.1 や localhost を弾けばよい」と考えるが、攻撃者は同じホストを 無数の表記 で表現できる。
http://127.0.0.1/ # 普通の表記
http://localhost/ # hosts ファイル経由
http://127.1/ # 短縮表記 (127.0.0.1 と等価)
http://0/ # 0 → 0.0.0.0 → ループバック扱い
http://0177.0.0.1/ # 8 進数表記 (0177 = 127)
http://2130706433/ # 10 進数表記 (32-bit 整数)
http://0x7f.0.0.1/ # 16 進数表記
http://[::1]/ # IPv6 ループバック
http://[::ffff:127.0.0.1]/ # IPv4-mapped IPv6
http://attacker.example/ # A レコードで 127.0.0.1 を返す自前ドメインブラックリスト方式は 必ず破られる。後述の防御では「DNS 解決後の最終 IP を見て private/loopback/link-local を弾く」のが基本になる。
DNS Rebinding #
特に厄介な技法。攻撃者は自分の DNS サーバを用意し、1 回目の問い合わせには公開 IP、2 回目には 127.0.0.1 を返すよう設定する。サーバ側のフローが「① URL 検証時に DNS 解決 → ② HTTP リクエスト時に DNS 解決」と 2 回 DNS を引く なら、検証と実通信の間で IP が「rebind」されて検証を回避できる。
対策は単純で、「DNS を 1 回引いた結果の IP を保持し、その IP に直接 HTTP リクエストする (Host ヘッダだけドメイン名)」という pinning パターン。多くの SSRF 防御ライブラリ (例: ssrfFilter, safe-curl) が採用している。
リダイレクト連鎖 #
ホワイトリスト方式 (許可ドメインだけ通す) でも、HTTP リダイレクトを追従する設定 だと回避される。攻撃者は自分のサーバに 301 Location: http://169.254.169.254/... を返すエンドポイントを置く。サーバが許可ドメインへの初回リクエストの応答リダイレクトを盲信して追従すれば、最終的に内部 IP に到達してしまう。
curl -L 相当の自動リダイレクトは 無効化する、もしくは追従先 URL に対しても同じ検証を再適用する必要がある。
URL パーサの解釈差 (Parser Confusion) #
サーバ言語の URL パーサと HTTP クライアントの URL パーサが 異なる解釈 をする差分を突く攻撃。
http://allowed.example#@evil.example/
http://allowed.example@evil.example/
http://allowed.example\\@evil.example/
ある言語は allowed.example をホストとして解釈し検証 OK にする一方、別の HTTP クライアントは evil.example をホストとして接続する、というズレ。Python の urllib と Go の net/url で異なる挙動が CVE になった事例も複数ある。ホスト検証と HTTP リクエストは同一のライブラリで行う のが防御の鉄則。
著名な SSRF 事件 #
Capital One 1 億件流出 (2019) #
SSRF が一般のニュースになった転換点。Capital One が AWS 上で運用していた WAF (ModSecurity ベース) に SSRF があり、攻撃者は ① WAF の URL パラメータに http://169.254.169.254/latest/meta-data/iam/security-credentials/... を投げて IAM ロールの一時クレデンシャルを取得 → ② そのクレデンシャルを使い AWS CLI から S3:ListBuckets → S3:GetObject で 約 1 億件のクレジットカード申込データ を吸い出した。
原因の一つは WAF インスタンスに広範な S3 権限を付けていた こと。SSRF 単体ではなく、IAM ロールの最小権限原則違反が被害を拡大させた。これを契機に AWS は IMDSv2 (後述) を導入し、メタデータエンドポイントへのアクセスにセッショントークンを要求する仕組みを追加した。
GitLab (CVE-2021-22214 ほか) #
GitLab には CI/CD やリポジトリ取込に関連する SSRF が複数回見つかっている。CVE-2021-22214 では認証なしで内部メタデータエンドポイントへアクセスできる SSRF があり、自己ホスト型 GitLab を運用する企業のクラウド環境で IAM 奪取に繋がる可能性があった。GitLab はバージョンアップで Allowlist 強化と Loopback/Link-Local 拒否を入れて対応。
Shopify Bug Bounty (2018-2022) #
Shopify は HackerOne の Bug Bounty 経由で多数の SSRF を受領し、最高 $25,000 級の報奨金を出している。「?url= を解釈する内部マイクロサービス」「OAuth コールバックの URL 検証バイパス」「画像取得 worker 経由」など、攻撃面が広い大規模 EC プラットフォームでは SSRF の隙が常に発生する例として有名。
Microsoft Exchange (ProxyShell / SSRF + 認証バイパス, 2021) #
Exchange サーバの一連の脆弱性 (CVE-2021-34473 ほか) は、SSRF + 認証バイパス + 任意ファイル書き込みの チェーン で RCE に到達した。SSRF 部分は内部の /autodiscover/autodiscover.json エンドポイントを叩いて他ユーザのバックエンド URL を引き出すために使われた。事件の規模は世界中の Exchange サーバ数万台が侵害される程度に達し、Webshell が無差別に植え付けられた。
教訓 #
- クラウドメタデータが攻撃者の最優先ターゲットになる
- IAM ロールの権限を絞る だけで被害は桁違いに小さくなる (Capital One の核心)
- SSRF は単独でも危険だが、他の脆弱性とチェーンして RCE 級になる (Exchange の核心)
- 自己ホストの OSS (GitLab, Confluence, Jira 等) を内部で動かす場合、その SSRF も同じ前提で危険
防御策 — 多層防御 #
XSS と同じく、SSRF も単一の対策だけでは破られる。URL 検証・IP Pinning・スキーム制限・egress フィルタ・IMDSv2・IAM 最小権限 を組み合わせる。
Allowlist (ホワイトリスト) 方式の URL 検証 — 最優先 #
ブラックリスト方式 (127.0.0.1 を弾く) は破られる。許可する宛先 (取得元として正当なドメイン) を明示列挙する方式に切り替える。
import ipaddress, socket
from urllib.parse import urlparse
ALLOWED_SCHEMES = {'http', 'https'}
ALLOWED_HOSTS = {'api.partner.example', 'cdn.partner.example'}
def validate_url(url: str) -> str:
u = urlparse(url)
if u.scheme not in ALLOWED_SCHEMES:
raise ValueError('bad scheme')
if u.hostname not in ALLOWED_HOSTS:
raise ValueError('host not allowed')
# DNS 解決して private/loopback/link-local を最終的に弾く (rebinding 対策)
addr = socket.gethostbyname(u.hostname)
ip = ipaddress.ip_address(addr)
if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
raise ValueError('private/loopback ip')
return addr # 解決済み IP を返し、呼び出し側はこの IP に直接接続する
ポイント:
- スキームを明示限定
- ホスト名のホワイトリスト
- DNS 解決後の最終 IP を
ipaddressで分類して弾く (Rebinding 対策) - 検証で得た IP に直接 HTTP リクエストし、
Host:ヘッダだけドメインを入れる (検証と実通信のあいだに rebind されない) - リダイレクトを禁止 (許可するなら追従先 URL に同じ検証を再適用)
IMDSv2 (AWS) — メタデータエンドポイントの認証 #
Capital One 事件を受けて AWS が導入。メタデータ取得を 2 段階セッション にし、PUT /latest/api/token で短期トークンを取得 → X-aws-ec2-metadata-token ヘッダを付けないと応答しない仕様に変えた。
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -H "X-aws-ec2-metadata-token: $TOKEN" \
"http://169.254.169.254/latest/meta-data/iam/security-credentials/"SSRF の典型的な「GET だけ投げる」「ヘッダを自由に付けられない」「TTL を 1 ホップに制限」が成り立つと トークン取得そのものが SSRF からは難しい — というのが IMDSv2 の防御効果。EC2 では起動オプションで IMDSv1 を無効化 (MetadataOptions: HttpTokens=required) するのが現在のベストプラクティス。
Egress フィルタ — 出口で止める #
Web アプリのサーバから出ていける宛先を ファイアウォール / NAT GW / VPC Endpoint Policy で絞る。たとえ SSRF が成立しても、ネットワーク層でブロックされていれば内部 IP やメタデータには到達できない。
- VPC のセキュリティグループの outbound rules で
169.254.0.0/16/10.0.0.0/8/192.168.0.0/16/172.16.0.0/12を全部拒否 - AWS の VPC Endpoint Policy で「自インスタンスからの S3 はこのバケットだけ」と絞る
- アプリ用 ECS タスクは 専用サブネット + 専用 SG に置き、メタデータ IP を SG レベルで遮断
スキーム・ポート・サイズ制限 #
HTTP クライアントの設定で:
- スキームは
http/httpsのみ (file://,gopher://,dict://,ldap://,jar://等を禁止) - ポートは 80 / 443 のみ (内部サービスのポートを叩けなくする)
- リダイレクト追従を禁止 (もしくは追従先を再検証)
- タイムアウトを短く (Blind SSRF のタイミング解析が辛くなる)
- 応答サイズ上限 (内部の重い API を叩いてリソース枯渇させる攻撃も封じる)
PHP の cURL なら:
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
IAM 最小権限 — 被害を抑える最後の砦 #
たとえ SSRF が成立して IAM クレデンシャルが奪われても、そのロールにできることが限定されていれば被害は小さい。Capital One 事件の核心は WAF インスタンスに S3:ListAllMyBuckets と S3:GetObject が付いていたこと。WAF が S3 を読む必要があるか?という問い直しで防げた可能性が高い。
- インスタンス / Pod ごとに 専用 IAM ロール を作る
- Resource を絞る (
s3:GetObjectをarn:aws:s3:::myapp-uploads/*だけに) - Action を絞る (
s3:*ではなくs3:GetObjectだけ) - 機密データを扱うバケットには VPC Endpoint Policy + Bucket Policy 二重ロック
サニタイザライブラリ #
SSRF 防御は自前で書くと罠を踏みやすい。実績のあるライブラリを使う:
- Node.js:
ssrf-req-filter,request-filtering-agent - Python:
safeurl-python,advocate - Go:
safehttp,safedialer - Java:
safe-url, OWASP ESAPI
これらは「DNS 解決 → IP 分類 → IP pin → リダイレクト再検証」を内部でやってくれる。
テストと検出 #
手動テスト #
URL を受け取りそうな機能 (プレビュー / Webhook / 外部取込 / OG 画像 / PDF レンダラ / 画像変換) を片っ端から狙う。
http://127.0.0.1/
http://169.254.169.254/latest/meta-data/
http://metadata.google.internal/computeMetadata/v1/
file:///etc/passwd
gopher://127.0.0.1:6379/_*1%0D%0A%248%0D%0Aflushall%0D%0A
http://collaborator-id.burpcollaborator.net/ # Blind 検出用
# 上記が弾かれたら表記バリエーション (10 進数 IP / 0177.0.0.1 等) で再試行Burp Suite Collaborator (Blind SSRF 検出) #
Blind SSRF は応答が見えないので OOB 検出 が基本。Burp Collaborator は攻撃者専用の DNS / HTTP サーバを提供し、サーバが「Collaborator 経由のドメイン」を引きに来たかどうかを記録する。
- DNS interaction だけある → SSRF はあるが HTTP は飛ばない (DNS だけ引いている)
- HTTP interaction まである → SSRF が成立して任意 URL に GET できている
- どちらもない → 該当箇所では SSRF 不成立
無料代替として interactsh (project discovery)、canarytokens 等もある。
静的解析 #
ソースコードで「ユーザ入力 → HTTP クライアント呼び出し」のデータフローを追跡する。Semgrep / CodeQL / SonarQube に SSRF ルールがある。フレームワーク特有のシンク (requests.get, urllib.request.urlopen, Net::HTTP.get, HttpClient.get) を機械的に grep するだけでも当たりが付く。
本番モニタリング #
- VPC Flow Logs で メタデータエンドポイント宛のトラフィック異常 を監視 (本来は正常な数のはず、急増したら異常)
- ファイアウォール / NAT GW のログで private network への外向き trial を検知
- WAF 層で
169.254.169.254等のリテラル文字列を含むリクエストをアラート
関連する攻撃 #
| 攻撃 | 関係 |
|---|---|
| CSRF (Cross-Site Request Forgery) | 名前が紛らわしいだけで構造は逆。CSRF は被害者ブラウザにリクエスト送信させる。SSRF はサーバにリクエスト送信させる |
| RFI / LFI (Remote/Local File Inclusion) | PHP include 系で URL を読み込む脆弱性。SSRF と機能的に重なるが、結果はファイル取込 → RCE。allow_url_include=Off で多くは塞がれる |
| XXE (XML External Entity) | XML パーサが <!ENTITY xxe SYSTEM "http://..."> を解決して外部リソースを取得 → SSRF の一形態として悪用される |
| CSWSH (Cross-Site WebSocket Hijacking) | ブラウザ側の WebSocket オリジン検証不備。SSRF とは別だが「サーバ間通信が信頼されている前提」を突く点で似ている |
| Smuggling (HTTP Request Smuggling) | フロント/バックエンド間で HTTP リクエスト解釈差を突く。これも内部 API への到達手段になる点で SSRF と被害ベクトルが重なる |
まとめ — 開発者が最低限押さえる 7 項目 #
SSRF は地味だが現代の Web アプリ脆弱性の入口 として最も警戒すべき種類のひとつ。クラウドのメタデータ / 内部 API / プライベートネットワークがすぐ近くにある現代の構成では、「サーバが任意の URL を取りに行ける」状態が即座に IAM 奪取・内部探索・横展開に繋がる。
- URL の Allowlist 検証 を導入し、ホスト名・スキーム・ポートを限定する
- 検証は DNS 解決後の最終 IP に対して行い、private/loopback/link-local を機械的に弾く
- HTTP クライアントの リダイレクト追従を無効化 する (もしくは追従先を再検証)
- AWS なら IMDSv2 必須化 (
HttpTokens=required) - VPC / SG で egress を絞る — メタデータ IP・プライベート CIDR を出口で遮断
- インスタンス / Pod の IAM ロールを最小権限 にする (action と resource の両方)
- SSRF 防御は自前で書かず、実績ライブラリ (
safe-url系) を使う
「URL を受け取って取りに行くだけ」のシンプルな機能こそ最も SSRF を生みやすい。信頼境界は IP アドレスではなく、明示的な許可リストでしか定義できないことを設計段階で意識する必要がある。