サマリ
Capital OneからのSSRF攻撃による大規模な情報漏えい等をうけて、Amazonはインスタンスメタデータに対する保護策としてInstance Metadata Service (IMDSv2) を発表した。本稿では、IMDSv2が生まれた背景、使い方、効果、限界を説明した上で、SSRF対策におけるIMDSv2の位置づけについて説明する。SSRFとは
SSRFは、下図のように「外部から直接アクセスできないエンドポイント」に対して、公開サーバーなどを踏み台としてアクセスする攻撃方法です。SSRF(Server Side Request Forgery)の詳細については過去記事「SSRF(Server Side Request Forgery)徹底入門」を参照ください。最終的な攻撃目標は多様ですが、近年問題になっているのが、クラウドサービスのインスタンス・メタデータを取得するAPIのエンドポイントです。有名なものがAmazon EC2の169.254.169.254(IMDS)ですが、類似の機能をクラウドサービス各社が提供しています。
先の記事でも紹介したSSRF脆弱なサンプルを以下に示します。これは、はてなブックマークのようなソーシャルブックマークアプリの「プレビュー機能」を想定しています。
このスクリプトをAmazon EC2上においてSSRF攻撃すると、下図のようにIAMのクレデンシャルが表示されます。<?php require_once('./htmlpurifier/library/HTMLPurifier.includes.php'); $purifier = new HTMLPurifier(); $ch = curl_init(); $url = $_GET['url']; curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $html = curl_exec($ch); echo $purifier->purify($html);
また、SSRF攻撃の標準的な対策は、ネットワーク的な対策で、EC2の場合は以下のようなiptablesによる防御が従来から推奨されていました。
ここまでが長い前置き(前提知識の確認)です。sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP
Amazon ECS コンテナインスタンスの IAM ロール - Amazon Elastic Container Service より引用
EC2インスタンスメタデータサービスv2(IMDSv2)とは
この状況に対して、Amazonが批判されたり、Amazonの責任ではないという反論があったりしていましたが、Amazonは今年の11月20日オフィシャルブログにて、Instance Metadata Service v2(IMDSv2) を発表しました。以下は、クラスメソッドの臼田さんのブログ記事から要点の引用です。- v2へのアクセスには事前に取得したTokenを必須とする
- TokenはPUTで取得する必要がある
- Tokenリクエスト時に有効期限(秒)を設定できる
- Tokenはヘッダに入れてリクエストする必要がある
- v1を無効化できる(デフォルトでは併用可能)
- メタデータサービス自体を無効化できる
$ curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60"
AQAAAKunSEcqfWQgz1E-ryJ3fdWDoOkbn8Nn4h2C6qN6nP56npog8Q==$
赤字のBASE64っぽいものがトークンです。TTLを60秒としているので、この値は既に無効です。PUTメソッドとX-aws-ec2-metadata-token-ttl-secondsヘッダを要求することで、攻撃難易度を上げています。続いて、トークンを利用したメタデータの取得です。X-aws-ec2-metadata-tokenヘッダにより、先程取得したトークンを指定します。
これだけだと、IMDSv1が有効になっているのでSSRF攻撃は緩和されません。IMDSv1を無効化するには、AWSCLIから以下のように --http-tokens を required に設定します。$ curl -H "X-aws-ec2-metadata-token: AQAAAKunSEcqfWQgz1E-ryJ3fdWDoOkbn8Nn4h2C6qN6nP56npog8Q==" http://169.254.169.254/latest/meta-data/iam/info/ { "Code" : "Success", "LastUpdated" : "2019-12-08T02:32:19Z", "InstanceProfileArn" : "arn:aws:iam::999999999999:instance-profile/test-role", "InstanceProfileId" : "ZZZZZZZZZZZZZZZZZZZZZ" }$
$ aws ec2 modify-instance-metadata-options --instance-id i-FFFFFFFFFFFFFFF --http-tokens required --http-endpoint enabled
{
"InstanceId": "i-FFFFFFFFFFFFFFFFF",
"InstanceMetadataOptions": {
"State": "pending",
"HttpTokens": "required",
"HttpPutResponseHopLimit": 1,
"HttpEndpoint": "enabled"
}
}
$
この状態で先の攻撃をすると、以下のように攻撃は防御されます。SSRF攻撃の文脈でPUTメソッドやカスタムHTTPリクエストヘッダを指定することは難しそうなので、「これだけでSSRF対策は十分ではないか」と思う人もいそうですが、実は攻撃は可能です。
Gopherプロトコルとは
以前からSSRF界隈ではGopherプロトコルの活用が話題となっていて、はせがわようすけさんが分かりやすいスライドで紹介されています。このスライドの12ページからがGopherを用いた攻撃手法についての説明です…が、このスライドは今年の9月18日の講演のものですので、当時存在しなかったIMDSv2についての言及はありません。このため、はせがわさんのスライドを引き継ぐ形で、GopherによるIMDSv2への攻撃を紹介します。
まず、Gopher自体の紹介はこちらの記事などを参照していただくとして、ここではcurlとnetcatによりgopherプロトコルを簡単に試してみます。
まずはcurlコマンドにより以下のURLをアクセスしてみます。
curlコマンド実行前にnetcatで8888ポートを待ち受けていると、以下のような表示になります。$ curl gopher://localhost:8888/_Hello%0d%0aHiroshi%20Tokumaru%0d%0a
$ nc -l 8888
Hello
Hiroshi Tokumaru
Response ← この行はResponse 改行 Ctrl-d を手入力したもの
$
この際の呼び出し側は下記となります。
$ curl gopher://localhost:8888/_Hello%0d%0aHiroshi%20Tokumaru%0d%0a
Response
$
このように、Gopherプロトコルを使うと、任意リクエストをURLで指定でき、そのレスポンスを受け取れることから、HTTPやSMTPその他のプロトコルをエミュレートできることになります。Gopherプロトコルを用いたIMDSv2に対する攻撃
先程のサンプルプログラムをEC2のIMDSv1を無効化した環境に設置した状態で、Gopherプロトコルを用いて攻撃してみましょう。まずはPUTメソッドによるトークン取り出しです。表示が見やすいようにHTMLソースの形で表示しています。アドレスバーには gopher://169.254.169.254:80/_PUT というURLがちらっと見えていますね。このトークン(有効時間は60秒…もっと長くすることも可能)を用いて、メタデータを表示させた結果が下記です。
このように、IMDSv1を無効化してIMDSv2のみ有効としても、Gopherプロトコルを用いてSSRF攻撃ができました。
リダイレクトを許可している場合
今までの「脆弱なスクリプト」は、与えられたURLに対してスキーム(プロトコル)もホストのIPアドレスもチェックしていなかったので、これらのチェックを追加してみましょう。これだけだと防御できて当然なので、cURLのオプションとしてCURLOPT_FOLLOWLOCATIONをtrueにします。これは、リダイレクトをcURL内部で自動的に追跡するという意味です。このスクリプトに対して、リダイレクトを用いた攻撃をします。具体的には下記のスクリプト(リダイレクタ)のURLをサンプルスクリプトに指定します。<?php require_once('./htmlpurifier/library/HTMLPurifier.includes.php'); $purifier = new HTMLPurifier(); $ch = curl_init(); $url = $_GET['url']; $urlinfo = parse_url($url); // URLのパース $scheme = $urlinfo['scheme']; $host = $urlinfo['host']; $ip = gethostbyname($host); if ($ip === "169.254.169.254") { // IPアドレスのチェック die("Invalid host"); } elseif ($scheme !== 'http' && $scheme !== 'https') { // スキームのチェック die("Invalid scheme"); } curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // リダイレクトを自動追跡 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $html = curl_exec($ch); echo $purifier->purify($html);
実行結果は以下となり、トークンを取得できていることがわかります。<?php header('Location: gopher://169.254.169.254:80/_PUT%20/latest/api/token...以下悪用防止のため略
同様に、このトークンを使用してEC2インスタンスのメタデータを取得することができます。
※ はせがわさんのスライドでは、この攻撃にはスクリプト側で明示的に任意プロトコルへのリダイレクトが許可されている必要があるように読めます(P23)が、私が実験により確認した範囲では、任意プロトコルの明示的な許可は必要ないようです。
対策
上記攻撃には以下の対策候補が考えられます。- curlで扱うプロトコルをHTTPおよびHTTPSに限定(常に指定を推奨)
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
- リダイレクトの追跡を禁止する(curlのデフォルトに戻す、あるいは以下を設定)
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // リダイレクト追跡しない
- CURLINFO_PRIMARY_IPにより「実際にアクセスしたIPアドレス」を求め、169.254.169.254(等ブラックリストのIPアドレス)であれば表示をやめる
$primary_ip = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
- URLからホスト名に対応するIPアドレスとスキームを確認する(先のスクリプトでは実施済み)
結局どうすればよいか
IMDSv2はそもそもAmazonからもインスタンスメタデータに対する defense in depth (多層防御)と紹介されており、根本的な解決策ではありません。なので、他の根本的な解決策を実施した上で、予防的な対策(緩和策)として用いるべきです。では、根本的な解決策はなにかというと、インスタンスメタデータの保護という点では、先に紹介したiptables等を用いたネットワーク的な対策が確実です。あるいは、以下により、HTTPによるインスタンスメタデータ参照そのものを禁止することも有効です。
ただし、上記はインスタンスメタデータに対する保護であり、SSRF攻撃全般を防御できるわけではないため、他に守るべきエンドポイントがある場合には他の対策を併用する必要があります。$aws ec2 modify-instance-metadata-options --instance-id i-FFFFFFFFFFFFFFFF --http-endpoint disabled { "InstanceId": "i-FFFFFFFFFFFFFFFFF", "InstanceMetadataOptions": { "State": "pending", "HttpTokens": "required", "HttpPutResponseHopLimit": 1, "HttpEndpoint": "disabled" } }
まとめ
Instance Metadata Service v2 (IMDSv2) について紹介しました。IMDSv2を用いることにより、SSRF攻撃をかなり緩和されることが期待できるものの、根本的な解決策ではなく緩和策の一つとして用いるべきと考えます。これは、Amazon自体がIMDSv2をdefense in depth(多層防御)とうたっていることからも伺えます。また、SSRF攻撃の対策は難易度が高いため、可能であればSSRF攻撃の影響を受けない仕様(例えば外部から受け取ったURLにアクセスしない)の検討を推奨します。