SSRF:クラウド時代の「見えない侵入者」を完封する技術
現場でインシデント対応をしていると、最近特に頭を抱えるのがSSRF(Server-Side Request Forgery)だ。かつては「外部から内部を覗き見られる」程度に思われていたかもしれないが、クラウドネイティブな現代において、SSRFはクラウド環境の乗っ取りに直結する「特等席への切符」になり得る。
今日は、教科書的な説明はすっ飛ばして、実務で明日から使える「SSRFを物理的に無効化する」ための防衛論を叩き込む。
—
1. なぜSSRFが「詰み」の一手になるのか
攻撃者はSSRFを使って何をするか? 外部のWebサイトにアクセスさせるだけなら可愛いものだ。彼らが狙うのは、「メタデータサービス(IMDS)」と「内部ネットワークの隠れた管理画面」だ。
例えばAWSなら `http://169.254.169.254/latest/meta-data/iam/security-credentials/` にアクセスできれば、そのサーバーに付与されたIAMロールの認証情報を盗み出せる。これだけで、データベースやS3バケットへの鍵を奪われ、インフラ全体が陥落する。
攻撃者の視点(PoCの概念)
攻撃者が入力欄にこんなURLをぶち込む
http://internal-db.local:8080/admin/delete_user?id=123
あるいはクラウドメタデータへ
http://169.254.169.254/latest/meta-data/iam/security-credentials/RoleName
アプリがサーバーサイドでこれを実行してしまった瞬間、あなたのアプリは攻撃者の「踏み台」に成り下がる。
—
2. 実践的防御:許可リスト(Allowlist)とバリデーション
「URLを正規表現でチェックする」なんて甘い考えは捨てろ。`http://google.com@evil.com` のようなURLスキームの盲点や、DNSリバインディング攻撃は、正規表現をすり抜ける。
正攻法は「信頼できる宛先のみを許可する」ことだ。
Pythonでのセキュアな実装サンプル
Pythonの `requests` や `urllib` を使う際、必ず「解決したIPアドレス」をチェックする。
import socket
import urllib.parse
def is_safe_url(url):
# 1. ホスト名を抽出
parsed = urllib.parse.urlparse(url)
hostname = parsed.hostname
# 2. 許可リストによるホワイトリスト制限
allowed_hosts = [‘api.trusted-service.com’, ‘images.example.com’]
if hostname not in allowed_hosts:
return False
# 3. IPアドレスに解決して、プライベートIPを遮断(重要!)
try:
ip = socket.gethostbyname(hostname)
# RFC1918 プライベートアドレスのチェック
if ip.startswith((’10.’, ‘172.16.’, ‘192.168.’, ‘127.’)):
return False
except:
return False
return True
実装時はこの関数を通してからリクエストを飛ばす
—
3. インフラ層でトドメを刺す:ネットワーク分離とメタデータ保護
アプリ層の防御をすり抜けられた時のために、インフラ側で「そもそも届かない」環境を作っておくのがプロの仕事だ。
AWSの場合:IMDSv2の強制
AWSを使っているなら、メタデータサービスへのアクセスにセッションベースの認証を強制するIMDSv2を有効化しろ。これだけで、単純なSSRFによるクレデンシャル窃取は防げる。
設定コマンド(CLI):
EC2インスタンスでIMDSv2を必須にする
aws ec2 modify-instance-metadata-options \
–instance-id i-xxxxxx \
–http-tokens required \
–http-put-response-hop-limit 1 \
–http-endpoint enabled
ネットワーク分離(Nginxの役割)
アプリケーションサーバーから内部ネットワークへの通信を、ファイアウォール(iptables/Security Group)で物理的に制限する。
Nginxで行う宛先制限の考え方:
もしサーバーがプロキシとして機能しているなら、バックエンドへの接続先を固定しろ。
nginx.conf の一例
location /fetch-image/ {
# ユーザーが指定できるのはパスのみにするなどの設計が理想
proxy_pass http://backend-api.internal-network.local;
# 内部ネットワークへの直接アクセスは許可しない
}
—
4. 最後に:セキュリティは「多層」で戦え
SSRF対策の要諦は、「ユーザー入力を信頼しない」×「ネットワークを細分化する」×「権限を最小化する」の3つに集約される。
1. URLは直接叩かせない: IDやキーだけ受け取り、サーバー側でURLを組み立てる設計にする。
2. メタデータへのアクセス遮断: インスタンスプロファイルには必要最小限の権限しか与えない。
3. 出口対策: アプリケーションサーバーに、インターネットへの不用意なアウトバウンド通信をさせない(Egress Filter)。
「これくらい大丈夫だろう」という慢心こそが、攻撃者が最も好む脆弱性だ。今日紹介したコードと設定は、明日から君たちのチームの標準にしてほしい。
何かあればいつでも相談してくれ。堅牢なシステムを構築するのは、泥臭い積み重ねの先にあるんだ。

コメント