SSRF:その「便利な機能」が、あなたのクラウド環境を崩壊させる引き金になる
こんにちは。セキュリティチームのチーフエンジニアです。
最近、コードレビューやペネトレーションテストの現場で「あ、これSSRF(サーバーサイドリクエストフォージェリ)でやられるな」という箇所を放置しているケースを非常によく見かけます。
SSRFは、一見すると「外部のURLから画像を取得する機能」や「Webhookを叩く機能」という、ごく普通のアプリケーションの挙動に見えます。しかし、攻撃者はその「サーバーの権限」を悪用して、本来インターネットから隔離されているはずの内部ネットワークや、クラウド環境の心臓部であるメタデータサービスを丸裸にしようと狙っています。
今日は、理論的な話はそこそこに、明日から現場で使える「刺さる」防御策を共有します。
—
1. なぜSSRFが「終わりの始まり」なのか
攻撃者がSSRFを狙う真の目的は、多くの場合、Webサーバーそのもののデータではありません。彼らが狙うのは以下の2点です。
1. クラウドメタデータサービス (IMDS): AWSなら `169.254.169.254` です。ここを叩ければ、IAMロールの認証情報を盗み出し、あなたのクラウド環境を乗っ取ることが可能です。
2. 内部ネットワークの探索: 「外からは見えない」はずの内部API、Redis、DB管理画面へのアクセスです。認証がかかっていない内部サービスがどれほど多いか、想像以上に危険ですよ。
攻撃のPoC(概念)
攻撃者は以下のようなリクエストを仕掛けます。
`GET /fetch-image?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/your-role-name`
これだけで、サーバーは「自分自身の権限」でクラウドの秘密情報を吐き出してしまう。これがSSRFの恐ろしさです。
—
2. 【最重要】防御の基本は「許可リスト」のみ
多くのエンジニアがやりがちなのが、「ブラックリスト」による防御です。「`169.254` を弾けばいいんでしょ?」……それは甘いです。攻撃者はIPアドレスのエンコード(10進数変換やDNSリバインディング)を使い、網をすり抜けます。
防御の鉄則は「許可リスト(Allowlist)」です。 許可されたドメインやIP以外は、物理的に通信させない。これが唯一の正解です。
—
3. 実装サンプル:Python (Flask) での堅牢な実装例
単にURLを渡すのではなく、DNS解決を行ってからIPアドレスを検証する手法が最も安全です。
import socket
import urllib.parse
import requests
許可されたドメインのリスト
ALLOWED_DOMAINS = [“api.trusted-service.com”, “images.example.com”]
def is_safe_url(url):
parsed_url = urllib.parse.urlparse(url)
hostname = parsed_url.hostname
# 1. 許可リストによるドメイン検証
if hostname not in ALLOWED_DOMAINS:
return False
# 2. DNS解決してプライベートIPか確認
try:
ip = socket.gethostbyname(hostname)
# RFC 1918などのプライベートIPアドレス範囲を弾く
if ip.startswith((‘127.’, ‘169.254.’, ‘192.168.’, ’10.’)):
return False
except:
return False
return True
def fetch_data(url):
if not is_safe_url(url):
raise Exception(“不正なURLです”)
# 検証を通過したものだけリクエストを許可
return requests.get(url, timeout=3)
—
4. インフラ層でのダメ押し:IMDSv2の強制
アプリ側の実装をどれほど完璧にしても、ヒューマンエラーは防げません。インフラ側で「メタデータサービスへのアクセス」を物理的に遮断するのが、経験上最も効果的です。
AWSの場合:IMDSv2を必須化する
もしTerraformを使っているなら、Launch Templateで以下の設定を入れてください。
IMDSv2を強制し、セッション指向の認証を必須にする
metadata_options {
http_endpoint = “enabled”
http_tokens = “required” # これが重要!v1を遮断する
http_put_response_hop_limit = 1
}
これで、攻撃者が単純な `curl` コマンドでメタデータを叩くことは不可能になります。
—
最後に:エンジニアへ伝えたいこと
セキュリティは「ツールを入れたら終わり」ではありません。
SSRFのような脆弱性は、「外部から入力を受け取る場所」すべてに潜んでいます。
- 「ユーザーがURLを入力する機能」を作ったとき、一度立ち止まってください。
- そのリクエストは、サーバーの権限で実行されるべきものですか?
- そのリクエスト先を、サーバー自身が制限できていますか?
防御策はシンプルですが、徹底するのは泥臭い作業です。しかし、その一手間が、数億円規模のインシデントを防ぐ鍵になります。明日からの開発で、ぜひこの「許可リスト」の考え方をコードの隅々に浸透させてください。
何かあればいつでも相談してください。共に守りましょう。

コメント