インフラエンジニアの盲点: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の導入とネットワーク制限を実装してください。インフラを守れるのは、最後には現場のエンジニアの「疑う力」だけなのです。
コメント