まずは、「前回までのおさらい」です。先日以下の記事でSSRF攻撃およびSSRF脆弱性について紹介しました。
この記事の中で、以下のように紹介しました。
ホスト名からIPアドレスを求める際にも以下の問題が発生します。DNS Rebinding攻撃についてはインフラ側で対策が取られている場合もあります。そこで、EC2上のウェブサイトにて、DNS RebindingによるSSRF攻撃が可能か検証してみました。
上記のTOCTOU(Time of check to time of use)問題は、DNSの名前解決の文脈ではDNS Rebindingとも呼ばれます。
- DNSサーバーが複数のIPアドレスを返す場合の処理の漏れ
- IPアドレスの表記の多様性(参考記事)
- IPアドレスチェックとHTTPリクエストのタイミングの差を悪用した攻撃(TOCTOU脆弱性)
- リクエスト先のWebサーバーが、攻撃対象サーバーにリダイレクトする
攻撃対象のスクリプトを以下に示します。これは先日の記事のスクリプトを改修したもので、クエリ文字列urlで指定したURLのコンテンツを読み出し、表示するものです。IPアドレスのチェックを追加し、表示系を簡略化しています。
このスクリプトは、DNS Rebinding以外にもいろいろ駄目ですが、とりあえず接続先のIPアドレスが169.254.169.254でないことは確認しています。これを以下のIPアドレスで実行すると<?php header('Content-Type: text/plain'); $url = $_GET['url']; $urlinfo = parse_url($url); $host = $urlinfo['host']; // URLからホスト名を取り出し $ip = gethostbyname($host); // 接続先IPアドレスを取得 echo "Target IP : $ip\n"; // 接続先のIPアドレスを表示 if ($ip == "169.254.169.254") { die("Invalid host $host"); // IPアドレスが169.254.169.254ならエラー } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $_GET['url']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $body = curl_exec($ch); echo $body; $prime_ip = curl_getinfo($ch, CURLINFO_PRIMARY_IP); echo "\nActual IP : $prime_ip\n"; // 実際のIPアドレスを表示
http://ホスト名/ssrf.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/test-role
以下のようにエラーになります。
Target IP : 169.254.169.254 Invalid host 169.254.169.254
DNS Rebinding
ようやくDNS Rebindingの説明です。攻撃対象が下図のWeb Applicationだとします。図の右側のDNS Contents Server(DNS権威サーバー)は、接続先のドメイン名を管理しているDNSサーバーで、実は攻撃者が管理している想定です(example.jpは例示用のドメイン名です)。この状態で、先のサンプルスクリプトでurl=http://evil.example.jp/... を指定した場合、通常ケースでは以下のように名前解決されます。
上図のように、gethostbynameとcurl_execの双方で名前解決が発生しますが、DNSキャッシュサーバーのキャッシュが残存していれば、2回目の名前解決の際には、DNSコンテンツサーバーには問い合わせずにDNSキャッシュサーバーがキャッシュの値を返します。
そこで、DNSコンテンツサーバー(権威サーバー)側がTTL=0としてAレコードを返し、Aレコードの要求毎に異なるIPアドレスを返す攻撃がDNS Rebinding攻撃です。その様子を下図に示します。
DNSコンテンツサーバーがTTL=0でAレコードを返すので、DNSキャッシュサーバーは問い合わせがある度にDNSコンテンツサーバーにAレコードを問い合わせます。そして、最初の問い合わせでは無害なIPアドレス(203.0.113.5)を返し、二度目の問い合わせでは169.254.169.254(EC2インスタンスの設定を返すIPアドレス)を返しています。このようにして、IPアドレスのチェックをすり抜ける攻撃手法がDNS Rebinding攻撃です。
上図を見る限りいかにも成功しそうですが、実際にはキャッシュサーバーの仕様などに依存します。DNS Rebinding対策その他を目的として、キャッシュサーバー側でTTLを超えてキャッシュを保持するDNS Pinningを実施している可能性があるからです。
試してみる
簡易的なDNSコンテンツサーバーを書いて、上記攻撃を試してみました。このDNSサーバーは、上図の通り、1回目の応答ではAレコードとして 203.0.113.5 を返し、2回目以降は 169.254.169.254 を返します。その結果、以下のようにSSRF攻撃に成功しました。gethostbynameの時点では、アクセス対象のIPアドレスは 203.0.113.5 (Taget IPとして表示)でしたので、事前のIPアドレスチェックはかいくぐっていることがわかります。Target IP : 203.0.113.5 { "Code" : "Success", "LastUpdated" : "2019-03-03T08:00:51Z", "Type" : "AWS-HMAC", "AccessKeyId" : "ASIAxxxxxxxxxxxxxxxx", "SecretAccessKey" : "zCc1sxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Token" : "FQoGZXIvYXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx... "Expiration" : "2019-03-03T14:30:31Z" } Actual IP : 169.254.169.254
Actual IPは、curlが実際にアクセスしたIPアドレスであり、169.254.169.254となっています。
対策
DNS Rebinding攻撃を前提としない条件でも、接続先のURLについて以下のチェックが必要です。- プロトコル(スキーム)が http か https のいずれかである
- 接続先ホストに紐づくIPアドレスが禁止されたものでない
先のスクリプトでは、PHPのcurl実装で使用できるCURLINFO_PRIMARY_IPを利用して、「実際に接続したホストのIPアドレス」を表示しています。攻撃によるリスクが情報漏えいのみである場合は、このIPアドレスを検証して、許可されていない場合はエラーにすることで漏洩を回避できます。ただし、これで完全な対策になるかは、徳丸自身は検証していません。
いっそのこと…ということで、外部コンテンツの取得をPROXY経由にするという対策があります。PROXYなら、接続先のホワイトリストやブラックリストによる制限は容易だからです。これだと、DNS Rebinding以外の対策も同時に行えます。
PROXYサーバーを用意するほどでもない…というケースでは、Amazonが推奨しているiptablesによる対策が考えられます。先の記事でも引用しましたが、再掲します。
詳しくは、上記ドキュメントを参照ください。sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROPAmazon ECS コンテナインスタンスの IAM ロール - Amazon Elastic Container Service より引用
補足
DNS Rebindingの一般的な対策(アクセスされる側)は以下のいずれかを実装することです(両方でもよい)。- Hostヘッダの検証(参考)
- 適切な認証とアクセス制御