flawopen.com/Incidents/capital-one-ssrf-imds-july-2019
●
Capital One · CWE-918 · July 2019 · 緊急
セキュリティ研究 · FlawOpen
Capital OneのSSRFとAWS IMDSv1認証情報の窃取
連邦刑事告訴状は、設定ミスの WAF により SSRF が AWS IMDSv1 に到達し、一時的なロール認証情報と約 1.06 億人分の S3 データが露出したと主張しています。トークンを必須にし、ロールを制限します。[10], [2]
💡 わかりやすい解説 (ELI5)
DOJ の刑事告訴状が主張した経路は次のように例えられます。指定した住所から書類を取ってくる受付係を想像してください。リクエストの中継により、誰かが受付係を社内の部屋へ誘導できてしまいます。そこで受付係はワークロード用の一時的な入館証を取得できます。その証で入れるのは、関連付けられたロールが許可する部屋だけです。設計者は社員しかその部屋に行けないと思い込んでいましたが、受付係をそこへ誘導できました。[10], [8]
主要な概念と専門用語
SSRF- サーバーサイドリクエストフォージェリ(
CWE-918)。攻撃者が選んだリクエストをサーバーに送らせ、そのリクエストにサーバーのネットワーク上の位置と信頼を持たせる手法です。[9] Web アプリケーションファイアウォール- Web アプリケーションの前段に置くフィルター。刑事告訴状は、Capital One の WAF の設定ミスにより内部アドレスへの要求を中継できたと主張しています。[10]
IMDSv2- IMDSv2 は PUT リクエストで取得するセッショントークンを使います。
HttpTokens=requiredに設定すると、トークンなしの IMDSv1 要求を拒否できます。単純な GET しか送れない SSRF 中継の多くを防げますが、SSRF 全般を防ぐものではありません。[7], [6] IAM ロール認証情報- インスタンスに発行される一時的な AWS キー。ロールに許されていることは、キーを持つ人も失効するまで同じようにできます。[8]
フレームワーク対応
- このガイドで説明する弱点の CWE 参照: CWE-918
- D3-OTF 送信トラフィックのフィルタリング — このガイドで説明する送信ネットワークの制限により、サービスまたはホストが接続できる宛先を絞れます。
インシデントのタイムライン
2019-03-22
2019-07-17
外部の研究者が、Capital One の責任ある開示プログラムを通じて流出データを報告する。[2]
2019-07-29
Capital One がインシデントを公表し、FBI がシアトルで容疑者を逮捕する。[1]
ステップ・バイ・ステップの攻撃フロー
ソースコード比較:脆弱 vs 堅牢化
これは説明用の Flask コードであり、Capital One の設定ではありません。許可ホストリストがアプリケーション側の主な制御です。必須の送信プロキシは接続時に解決後のアドレスへポリシーを適用し、インスタンスメタデータを含むプライベートおよび link-local 範囲を拒否し、各リクエストと再試行で同じ検査を行う必要があります。プロキシ設定で実際の動作を確認してください。リダイレクトは追跡しません。接続前にアプリ側で別途 DNS 解決すると、DNS rebinding の競合が残ります。[9], [5], [6]
脆弱な実装
import requests
from flask import Flask, request
app = Flask(__name__)
@app.get("/fetch")
def fetch():
# サーバーは呼び出し元が指定した任意の URL を取得する。
url = request.args["target"]
resp = requests.get(url, timeout=5)
return resp.text
堅牢化されたセキュアパッチ
from urllib.parse import urlsplit
import requests
from flask import Flask, abort, request
app = Flask(__name__)
ALLOWED_HOSTS = {"images.example-cdn.com", "api.partner.example"}
EGRESS_PROXY = "http://egress-proxy.internal:3128"
# DNS 解決後に宛先ポリシーを適用するよう、このプロキシを設定します。
@app.get("/fetch")
def fetch():
parts = urlsplit(request.args.get("target", ""))
if parts.scheme != "https" or parts.hostname not in ALLOWED_HOSTS:
abort(400, "destination not allowed")
if parts.port not in (None, 443) or parts.username or parts.password:
abort(400, "destination not allowed")
resp = requests.get(parts.geturl(), timeout=(2, 5), allow_redirects=False, proxies={"https": EGRESS_PROXY}, trust_env=False)
if resp.is_redirect:
abort(502, "upstream redirect refused")
return resp.text
エンジニアリング&システム堅牢化チェックリスト
- ✓URL 取得を送信プロキシ経由にし、接続境界でホスト許可リストと IP 範囲ポリシーを適用します。検査済みアドレスにのみ接続し、再試行にもポリシーを適用してください。リダイレクトは無効にするか各段階で再検証します。169.254.169.254 という文字列だけのブロックでは不十分です。[9], [4]
- ✓該当するすべてのインスタンスで
HttpTokens=requiredを設定し、既存インスタンスも確認してください。アカウントのデフォルト設定は各インスタンスを自動更新しません。ワークロードのネットワーク経路に合わせてHttpPutResponseHopLimitを選びます。AWS は値 1 だとコンテナで問題が起きる場合があると警告しています。トークン必須設定は IMDSv1 要求を拒否しますが、hop limit は別のネットワーク経路設定であり、トークンなし GET を拒否する制御ではありません。[6], [7] - ✓刑事告訴状は、SSRF 経路によりインスタンスのロールが露出したと主張しています。認証情報が漏れても無関係な S3 にアクセスできないよう、ロールをワークロードに必要な操作とリソースに限定してください。[10], [8]
- ✓FlawOpen の検知ガイダンス: インスタンスのネットワーク外でロールの一時認証情報が使われたらアラートを出し、ワークロードとクラウド監査の状況を照合します。
- ✓コードレビューでは、サーバーが取得する URL にユーザーが影響できる経路を、SQL クエリに影響できる経路と同じように扱い、許可リストのテストを必須にする。[9], [5]
参考資料
- 米司法省 — 大手金融サービス企業のデータ窃取でシアトルの IT 技術者を逮捕(2019 年 7 月 29 日)
- Capital One — 2019 年のサイバーセキュリティインシデントに関する情報
- OCC — 2019 年のインシデントをめぐる Capital One への民事制裁金(2020 年 8 月)
- クラウド環境で SSRF が危険なのはなぜか
- fetch() は SSRF を引き起こすか
- AWS — Configure the Instance Metadata Service options
- AWS — Use the Instance Metadata Service to access instance metadata
- AWS — Retrieve security credentials from instance metadata
- OWASP — Server Side Request Forgery Prevention Cheat Sheet
- DOJ — United States v. Paige Thompson, criminal complaint (29 July 2019)