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

インフラエンジニアの盲点:SSRFでクラウドメタデータを「乗っ取る」という悪夢

「うちは外からアクセスできない内部ネットワークにあるから大丈夫」

ペネトレーションテストの現場で、開発者やインフラエンジニアから最も多く聞く、そして最も危険なセリフです。クラウド環境において、その「内側」は、実は攻撃者にとっての黄金郷になり得ます。特に、AWSの 169.254.169.254 に代表されるインスタンスメタデータサービス(IMDS)へのSSRF攻撃は、適切に対策されていない環境では、たった一行の脆弱なコードからクラウド環境全体のアカウント乗っ取りに直結します。

今日は、なぜSSRFが「ただのURLフェッチミス」で済まないのか、そしてどうすれば「絶対に抜かれない」環境を作れるのか、現場の最前線の視点から解説します。

—

1. SSRFから「クラウド環境の全権掌握」までのシナリオ

SSRF(Server-Side Request Forgery)は、Webアプリケーションがユーザーの入力を元に外部へリクエストを投げる機能を悪用し、本来アクセスできない内部リソースを叩かせる攻撃です。

クラウド環境では、インスタンスに付与されたIAMロールの認証情報(AccessKey/SecretKey/Token)を、メタデータサービス経由で取得できます。攻撃者は以下のようなステップを踏みます。

1. 脆弱性の発見: ユーザーが入力したURLをそのまま curl や file_get_contents で取得するAPIを見つける。
2. 内部探索: メタデータサービスのIP(169.254.169.254)を標的にリクエストを送る。
3. IAMクレデンシャルの搾取: http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name> を叩き、一時的な認証情報を引き出す。
4. 環境の掌握: 奪った認証情報で aws s3 ls や aws ec2 describe-instances を実行し、バックアップを抜き取る、あるいは新規に攻撃用インスタンスを立ち上げる。

これはもはやWebアプリのバグではなく、クラウドインフラのバグとして扱うべき事案です。

—

2. 【防御策】まずやるべきは「IMDSv2」への強制移行

AWSの場合、最も強力な防御は「IMDSv2」の利用です。従来のIMDSv1は単なるGETリクエストでクレデンシャルが取れましたが、IMDSv2はセッション指向の認証(トークンの取得)を要求するため、通常のSSRFでは攻撃が成立しません。

クラウド設定(AWS CLI)

まだIMDSv1を使っているなら、今すぐ以下のコマンドで制限をかけましょう。

# インスタンスのIMDSv2を強制し、ホップ制限を1にする設定
aws ec2 modify-instance-metadata-options \
    --instance-id i-xxxxxxxxx \
    --http-tokens required \
    --http-put-response-hop-limit 1 \
    --http-endpoint enabled

—

3. アプリケーション側での堅牢な実装(PHPの例)

アプリケーション側で外部リクエストを扱う場合、URLをそのまま受け取るのは禁忌です。ホワイトリスト方式と、IPアドレスのバリデーションを組み合わせた実装例を紹介します。

セキュアなURLフェッチの実装(PHP)

<?php
function fetch_external_resource(string $url) {
    $parsed_url = parse_url($url);
    $host = $parsed_url['host'] ?? '';

    // 1. ホワイトリストによるドメイン制限
    $allowed_domains = ['api.example.com', 'cdn.example.net'];
    if (!in_array($host, $allowed_domains)) {
        throw new Exception("許可されていないドメインです");
    }

    // 2. 名前解決後のIPアドレスチェック(SSRF対策の要)
    $ip = gethostbyname($host);
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
        throw new Exception("内部ネットワークへのアクセスは禁止されています");
    }

    // cURLでのリクエスト実行
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    // 3. リダイレクトを追跡させない(SSRFの隠れ蓑を防ぐ)
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
    return curl_exec($ch);
}
?>

—

4. 現場の教訓:防御の多層化

コードレベルの対策は完璧ではありません。以下の「多層防御」を必ず組み込んでください。

  • ネットワーク分離: アプリケーションサーバーがメタデータIPへ直接アクセスできないよう、iptables や Security Group で外向き通信を制限する。
  • WAFの導入: 悪意あるリクエストパターン(169.254 などの文字列を含むリクエスト)をWAFで検知・遮断する。
  • 最小権限の原則: インスタンスに付与するIAMロールは「S3の特定のバケットのみ読み取り可」など、必要最小限の権限に絞る。万が一クレデンシャルを抜かれても、被害を最小限に抑えられます。

Nginxでのリクエストフィルタリング例

アプリケーションに到達する前に、メタデータサービスへのアクセスを弾く設定です。

# nginx.conf の location 設定
location /proxy {
    # ユーザーからの入力を受け取るリクエストの例
    # 内部IPへのアクセスはここで遮断する
    if ($arg_url ~* "169.254.169.254") {
        return 403 "Forbidden";
    }
    proxy_pass $arg_url;
}

最後に:セキュリティは「信頼」ではなく「疑念」から始まる

SSRFは、設計段階で「このサーバーが内部ネットワークのどこまで覗けるか?」を徹底的に疑うことで防げます。もし皆さんの開発環境や本番環境で、外部からURLを投げられる機能があるなら、今すぐ 169.254.169.254 にリクエストを送ってみてください。

もし応答が返ってくるなら、それはすでに「火事」が起きているのと同じです。即座にアーキテクチャを見直し、IMDSv2の導入とネットワーク制限を実装してください。インフラを守れるのは、最後には現場のエンジニアの「疑う力」だけなのです。

コメント

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