【実務・中級編】 SSRF(Server-Side Request Forgery)によるクラウドメタデータ取得 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

悪魔の入り口「SSRF」を叩く:クラウドメタデータという名のパンドラの箱

現場でインシデント対応をしていると、往々にして「なぜこんな単純なミスで?」という箇所で大事故が起きている。その筆頭が SSRF (Server-Side Request Forgery) だ。

単なる「外部サイトへのリクエスト転送機能」だと思って実装したコードが、実はクラウド環境においては「そのサーバーの権限(IAMロール)を奪い、環境全体を掌握するための鍵」になることを認識していないエンジニアがあまりにも多い。今日は、SSRFがなぜ危険なのか、そしてどう防御すべきか、綺麗事抜きの「現場の作法」を伝授する。

—

1. 攻撃者がメタデータサービスを狙う理由

AWSの 169.254.169.254 や GCPの 169.254.169.254(metadata.google.internal)といったメタデータサービスは、そのインスタンスに紐付いたIAMロールの一時認証情報(AccessKey, SecretKey, Token)を平気で返してくる。

もし君のWebアプリが、ユーザーからの入力をそのまま curl や file_get_contents に渡しているとしたら、攻撃者は次のようなリクエストを投げるだけで、インフラの鍵を盗み出せる。

GET /proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/your-role-name

これだけで、攻撃者の手元に一時的なクレデンシャルが渡る。あとは aws configure でそのキーを設定すれば、攻撃者は君のクラウド環境の中で「正規の管理者」として暴れまわることができる。これは脆弱性というより、事実上の「バックドア」だ。

—

2. 「許可リスト」によるバリデーションの限界と突破口

多くのエンジニアは「URLを正規表現でチェックすればいい」と考えがちだ。だが、甘い。以下のコードを見てくれ。

// 悪い例:正規表現による不完全なバリデーション
$url = $_GET['url'];
if (preg_match('/^https?:\/\/example\.com/', $url)) {
    echo file_get_contents($url);
}

攻撃者は https://example.com@169.254.169.254/ といったURLや、DNSリバインディング、あるいはURLエンコードを駆使してこのチェックをすり抜ける。さらに、IPアドレスの直接指定や独自スキーム(gopher:// など)を悪用されると、バリデーションは無力化される。

—

3. 実践的防御策:セキュアな実装パターン

SSRFを防ぐための鉄則は「外部リクエストを直接受け付けない」ことだが、どうしても必要な場合は以下の3つの層で防御を固めろ。

① インフラ層:IMDSv2の強制

AWSであれば、必ず IMDSv2 を有効化し、セッション指向の認証を必須にしろ。これだけで、単なるGETリクエストによるクレデンシャル盗取はほぼ不可能になる。

  • AWS CLIでの強制設定:
# IMDSv2を必須化(PUTリクエストによるセッション開始が必要になる)
aws ec2 modify-instance-metadata-options --instance-id i-xxxxxx --http-tokens required --http-endpoint enabled

② アプリ層:許可リストと名前解決の抑制

URLをパースしてIPを抽出し、プライベートネットワーク範囲(10.0.0.0/8, 169.254.169.254 等)を明示的に弾く。

import requests
from urllib.parse import urlparse
import ipaddress
import socket

def safe_get(url):
    parsed = urlparse(url)
    hostname = parsed.hostname
    
    # 1. ホスト名のIPアドレスを解決
    ip = socket.gethostbyname(hostname)
    
    # 2. プライベートIPアドレスかチェック
    if ipaddress.ip_address(ip).is_private:
        raise Exception("不正なアクセス先です")
    
    # 3. 許可されたドメインかチェック
    if hostname not in ["api.trusted.com"]:
        raise Exception("許可されていないドメインです")

    return requests.get(url, timeout=3)

③ ネットワーク層:Egressフィルター

Webサーバーが直接インターネットのメタデータサービスへ通信することを、セキュリティグループやiptablesで遮断する。

  • iptablesによるメタデータアクセス禁止設定:
# Webアプリが実行されているユーザー以外からのアクセスを拒否しつつ、メタデータへは絶対に行かせない
iptables -A OUTPUT -d 169.254.169.254 -j DROP

—

4. 最後に:エンジニアへの提言

SSRFを撲滅するために最も重要なのは、コードの書き方よりも「クラウドのメタデータサービスは、攻撃者にとっての『宝の山』である」という認識の共有だ。

実装時には必ず以下の問いを自分に投げかけてほしい。

  • 「このURLはユーザーが直接操作できる必要が本当にあるか?」
  • 「もしこのサーバーが乗っ取られたら、何ができるか?」

システム運用において、セキュリティは「機能」ではなく「品質」だ。動くものを作るのはスタート地点に過ぎない。君たちが書いたそのコードが、翌朝のニュースの種にならないよう、今日学んだ防御策をすぐにデプロイしてくれ。

もし実装で詰まったら、いつでも聞け。現場からは以上だ。

コメント

タイトルとURLをコピーしました