1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【保存版】SSRF攻撃の全て ─ クラウドメタデータ窃取を防ぐ6つの実装パターン

1
Posted at

Server-Side Request Forgery (SSRF) は OWASP Top 10 2021 で新設されたカテゴリで、クラウド環境におけるデータ窃取の主要経路である。特に AWS の IMDSv1 が絡んだインシデントは有名で、2019年の Capital One 事件では1億件超の個人情報が流出した。本記事では SSRF の仕組みと、2026年時点で実装すべき6つの防御パターンをコード付きで解説する。

SSRF とは

アプリケーションが任意のURLに対してHTTPリクエストを発行する機能 (URLプレビュー、Webhook、画像取得など) を悪用し、サーバー内部からしか到達できないエンドポイント を攻撃者が叩かせる手法である。

# 攻撃例: クラウドメタデータエンドポイントへのアクセス
http://169.254.169.254/latest/meta-data/iam/security-credentials/

成功すると、EC2 インスタンスに紐付いた IAM ロールの一時クレデンシャルが取得できてしまう。

Capital One 事件の概要

2019年、Capital One の WAF の設定不備を悪用し、攻撃者が SSRF で EC2 のメタデータから IAM クレデンシャルを取得、S3 から 1 億件の顧客データを持ち出した。事件後 AWS は IMDSv2 (セッション指向、デフォルトで Hop Limit 1) をリリースし、根本的な緩和策を提供した。

実装パターン1: 宛先URLのアローリスト化

ブラックリストではなくアローリスト方式にする。攻撃者は IPv6、0.0.0.0、10進数表記、DNSリバインディングなどあらゆる手法で回避してくる。

from urllib.parse import urlparse

ALLOWED_HOSTS = {"api.example.com", "img.example.com"}

def is_safe_url(url: str) -> bool:
    parsed = urlparse(url)
    if parsed.scheme not in ("https",):
        return False
    if parsed.hostname not in ALLOWED_HOSTS:
        return False
    return True

実装パターン2: IPレベルでのプライベート範囲ブロック

DNS 解決後の IP アドレスを明示的に検証する。これが重要で、URLのホスト名だけ見てもDNSリバインディングで突破される

import socket
import ipaddress

BLOCKED_RANGES = [
    ipaddress.ip_network("10.0.0.0/8"),
    ipaddress.ip_network("172.16.0.0/12"),
    ipaddress.ip_network("192.168.0.0/16"),
    ipaddress.ip_network("127.0.0.0/8"),
    ipaddress.ip_network("169.254.0.0/16"),  # クラウドメタデータ
    ipaddress.ip_network("::1/128"),
    ipaddress.ip_network("fd00::/8"),
    ipaddress.ip_network("fe80::/10"),
]

def resolve_and_check(hostname: str) -> str:
    infos = socket.getaddrinfo(hostname, None)
    for family, _, _, _, sockaddr in infos:
        ip = ipaddress.ip_address(sockaddr[0])
        for net in BLOCKED_RANGES:
            if ip in net:
                raise ValueError(f"Blocked IP range: {ip}")
    return infos[0][4][0]

実装パターン3: DNSリバインディング対策

攻撃者は TTL の短い DNS を設定し、検証時は 1.2.3.4 (公開IP)、実リクエスト時は 169.254.169.254 (メタデータ) を返す。対策は、DNSを一度解決した結果の IP で直接リクエストを送ることである。

import requests

def safe_get(url: str):
    parsed = urlparse(url)
    ip = resolve_and_check(parsed.hostname)
    # 解決済みIPで直接叩き、HostヘッダでSNIを維持
    return requests.get(
        f"{parsed.scheme}://{ip}{parsed.path}",
        headers={"Host": parsed.hostname},
        timeout=5,
        verify=True
    )

実装パターン4: AWS IMDSv2 の強制

EC2 のメタデータアクセスを IMDSv2 のみに制限する。IMDSv2 はセッショントークン必須のため、単純な GET で取得できなくなる。

# 既存インスタンスに強制
aws ec2 modify-instance-metadata-options \
  --instance-id i-0abc123 \
  --http-tokens required \
  --http-put-response-hop-limit 1 \
  --http-endpoint enabled

Terraform なら:

resource "aws_instance" "web" {
  ami           = "ami-xxxxxxxx"
  instance_type = "t3.micro"
  metadata_options {
    http_tokens                 = "required"
    http_put_response_hop_limit = 1
    http_endpoint               = "enabled"
  }
}

Hop Limit 1 により、コンテナ (Docker、ECS タスク) からメタデータが取得できなくなり、SSRF経由の窃取を大幅に軽減できる。

実装パターン5: 送信専用プロキシで閉じる

外向きリクエストを必ず Squid などのプロキシ経由に限定し、プロキシ側で宛先を制御する。

# /etc/squid/squid.conf
acl allowed_hosts dstdomain api.example.com img.example.com
acl private_ips dst 10.0.0.0/8 169.254.0.0/16 127.0.0.0/8

http_access deny private_ips
http_access allow allowed_hosts
http_access deny all

アプリ側は HTTP_PROXY 環境変数でこのプロキシを使うよう強制する。

実装パターン6: Redirect の制御

HTTP 302 でリダイレクト先が内部URLになるパターンを防ぐ。各リダイレクト先も毎回検証する。

import requests
from requests.adapters import HTTPAdapter

class SafeSession(requests.Session):
    def send(self, request, **kwargs):
        kwargs["allow_redirects"] = False
        resp = super().send(request, **kwargs)
        if resp.is_redirect:
            next_url = resp.headers["Location"]
            if not is_safe_url(next_url):
                raise ValueError("Unsafe redirect")
            # 再帰的に検証しつつ最大回数を制限
        return resp

実装時にハマるポイント

  • IPv6を忘れる: ::ffff:169.254.169.254 のような IPv4-mapped IPv6 アドレスをブロック漏れすると突破される
  • 10進IP表記: http://2852039166/169.254.169.254 と等価
  • URLスキームの想定漏れ: file:// gopher:// dict:// なども攻撃に使われる
  • ライブラリのデフォルト挙動: urllib requests httpx ともにデフォルトではリダイレクト追従する

まとめ

SSRF の防御は「宛先URLのホスト名だけ検証」では不十分で、DNS解決後のIP検証、IMDSv2の強制、送信プロキシ、リダイレクト制御を多層で組み合わせる必要がある。特にクラウド環境では、アプリ側の実装に関わらず IMDSv2 Hop Limit 1 を必ず設定しておくのが最低ラインである。

参考

  • OWASP SSRF Prevention Cheat Sheet
  • AWS IMDSv2 ドキュメント
  • PortSwigger Web Security Academy - SSRF
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?