【実務・中級編】 OWASP Top 10:2021 A10:2021-Server-Side Request Forgery (SSRF)の防御 – 暗号理論・認証基盤 & エンドポイントセキュリティ防御ガイド

SSRFの恐怖:なぜ「たかがURL指定」がクラウド全滅の引き金になるのか

現場で数多のインシデントを見てきたが、SSRF(Server-Side Request Forgery)ほど過小評価され、かつ被害が甚大になりやすい脆弱性はない。

開発者がよくやるミスは、「外部の画像をサムネイル化する」「外部APIから情報を取得する」といった機能で、URLのバリデーションを甘く見てしまうことだ。攻撃者はそのサーバーを「踏み台」として使い、本来なら外部からは決して到達できない「あなたの社内の管理画面」や「クラウドのメタデータサービス(IMDS)」を叩きにいく。

特にクラウド環境において、AWSの 169.254.169.254 を叩かれた瞬間、ゲームオーバーだ。そこにはインスタンスのIAMロールの認証情報が平然と置かれている。これを奪取されたら、攻撃者はあなたのクラウド環境の管理者権限を手に入れ、バックアップを消し、データを暗号化し、やりたい放題だ。

今回は、この「見えない脅威」をコードとインフラの力で封殺する方法を教える。

—

1. 攻撃者が狙う「盲点」:なぜバリデーションはすり抜けるのか

よくある「URLのブラックリスト方式(127.0.0.1やlocalhostを拒否する)」は無意味だ。攻撃者は http://169.254.169.254/ を http://[::]:80/ に変えたり、DNSリバインディングを使ったりして、いとも簡単に規制を回避する。

守りの鉄則は「ホワイトリスト」と「ネットワーク分離」の二段構えだ。

—

2. 実装レベルでの防御:PHPによる「厳格なホワイトリスト」

外部からのリクエストを処理する際、プロトコルとホスト名を厳密に制限せよ。以下は、URLをパースして許可されたドメインのみを通す堅牢な実装例だ。

<?php
/**
 * セキュアなURLフェッチャーの実装例
 */
function secure_fetch_url(string $url): string {
    $allowed_hosts = ['api.trusted-partner.com', 'images.example.com'];
    $parsed = parse_url($url);

    // 1. スキームはHTTPSに限定(HTTPは中間者攻撃のリスクがある)
    if ($parsed['scheme'] !== 'https') {
        throw new Exception("HTTPSのみ許可されています");
    }

    // 2. ホスト名がホワイトリストに含まれているか確認
    if (!in_array($parsed['host'], $allowed_hosts, true)) {
        throw new Exception("許可されていないホストです");
    }

    // 3. cURLでリクエスト(フォローリダイレクトは無効化が望ましい)
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // リダイレクトを辿らせない(SSRFの常套手段)
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
    $response = curl_exec($ch);
    curl_close($ch);

    return $response;
}

—

3. インフラレベルでの防御:AWS IMDSv2の強制

アプリケーションに脆弱性が残っていたとしても、クラウド側の設定で被害を最小化できる。AWSのインスタンスメタデータサービス(IMDS)には、従来の「v1」ではなく、セッション認証を必要とする「v2」を必ず強制せよ。

IMDSv2を強制するCLIコマンド

TerraformやCloudFormationで設定するのがベストだが、既存環境なら以下のコマンドで即座に設定を変更できる。

# IMDSv2を強制し、メタデータへのアクセスにセッションが必要な状態にする
aws ec2 modify-instance-metadata-options \
    --instance-id i-xxxxxxxxxxxxxxxxx \
    --http-tokens required \
    --http-put-response-hop-limit 1 \
    --http-endpoint enabled

この設定により、攻撃者が単純に curl を叩いても、正しいトークンを発行するためのPUTリクエストを送信できないため、認証情報へのアクセスが不可能になる。

—

4. ネットワーク分離:出口戦略の徹底

最後に、あなたのアプリケーションサーバーが「本来通信する必要のない場所」へ通信できないよう、セキュリティグループ(SG)やネットワークACLで出口を封鎖せよ。

  • Egress制限の鉄則:
  • サーバーはインターネットへの無差別なアウトバウンド通信を許可してはならない。
  • NATゲートウェイやプロキシ(SquidやEnvoyなど)を経由させ、FQDNベースで通信先をホワイトリスト化する。
  • 特に 169.254.169.254 へのトラフィックは、ネットワークレイヤーで完全に破棄するように設定する。

—

チーフエンジニアからのアドバイス

セキュリティを「機能の一部」として考えるな。それは「システムの前提条件」だ。

「便利だから」という理由で、外部から渡されたURLをそのまま curl や fetch に投げるコードは、時限爆弾を抱えているのと同じだ。今回示したコードの parse_url を通した検証や、CURLOPT_FOLLOWLOCATION を false にするような些細な工夫が、将来の重大なインシデントを防ぐ。

コードを書くときは常に想像せよ。「この引数に http://169.254.169.254/latest/meta-data/iam/security-credentials/ を入れたら何が起きるか?」と。その問いを持ち続けるエンジニアだけが、この荒波を生き残れる。

何かあればいつでも相談してくれ。堅牢なシステム作りを共に楽しもう。

コメント

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