- Tracebitは、公開・非公開S3バケットのAWSアカウントIDを推定する手法を整理し、例のバケット
bucket-alpha から 123456789101 を復元した
- 重要な手がかりは、S3用 Interface VPC Endpoint ポリシーの
s3:ResourceAccount 条件と、リクエストが自分の CloudTrail ログに残るかどうか
- 非公開バケットでは、最終レスポンスが引き続き
AccessDenied でも、VPC Endpointポリシーを通過したリクエストだけがCloudTrailに現れるため、数字パターンに一致するかを判定できる
- ポリシー伝播とCloudTrailの遅延により、単純な探索では約
40 * 12分 = 8時間 かかる可能性があるが、aws:userid と RoleSessionName を使った 120個のポリシーステートメント の並列テストで10分未満まで短縮できる
- この手法は、
StringLike が s3:ResourceAccount に対して部分一致を許可するため可能であり、一部のアクティビティはバケット所有者のCloudTrailにも残る可能性がある
公開バケット手法からの拡張
- 2021年、Ben Bridtsは公開S3バケットのAWSアカウントIDを見つける方法を公開した
- Tracebitの方法はこのアイデアの複数の要素を再利用しつつ、非公開バケットも含めてS3バケットのアカウントIDを見つけることに焦点を当てている
- 実行例では、
bucket-alpha についてCloudTrailで通過したセッション名を集め、最終的に 123456789101 を復元した
従来の公開バケット方式が成り立つ理由
- Ben Bridtsの方法は、3つの条件が組み合わさることで動作する
- リクエストに IAMポリシー を適用できる
- ポリシーがリクエストを許可したかブロックしたかを推測できる
s3:ResourceAccount 条件キーにワイルドカードマッチングを適用できる
- 公開バケットでは、ポリシーがリクエストをブロックすると
AccessDenied になり、ポリシーが許可するとリクエストが成功するため、ポリシーを通過したかどうかを簡単に区別できる
s3:ResourceAccount を1桁ずつ絞り込むと、探索空間は数兆個から数百個規模まで縮小する
非公開バケットではレスポンスの代わりにCloudTrailを見る
- 非公開バケットでは、どのポリシーを適用しても対象バケットポリシーにより最終レスポンスは AccessDenied になる
- Tracebitの方式は、レスポンス結果ではなく、リクエストが自分の CloudTrailログ に現れるかを基準にする
- リクエストがCloudTrailに現れれば、VPC Endpointポリシーは許可し、その後バケットポリシーで拒否されたということ
- リクエストがCloudTrailになければ、VPC Endpointポリシーでブロックされたということ
- S3用 Interface VPC Endpoint を作成すると、リクエストにVPC Endpointポリシーを適用でき、このポリシーはバケットポリシーやリクエスト主体のIAMポリシーなど、他のポリシーと併せて評価される
- VPC Endpointポリシーでも
StringLike ワイルドカードとリソース条件キーを使用できるため、同じ探索方法が可能になる
基本手順:リージョン確認からイベント照会まで
- まず対象バケットのリージョンを見つける必要がある
- バケットのHTTPエンドポイントに
curl を送ると、リクエストが禁止されていても x-amz-bucket-region ヘッダーが返される
- 例では
bucket-alpha.s3.amazonaws.com のレスポンスヘッダーから us-east-1 を確認した
- 対象バケットと同じリージョンにVPCとS3用VPC Endpointをデプロイする
- VPC Endpointは、ポリシー適用が可能な Interface タイプである必要がある
- そのVPC EndpointはVPC内のS3リクエストに影響するため、この目的専用のVPCを作るのが望ましい
- VPC内でS3リクエストを送るためにEC2インスタンスを起動し、そのインスタンスがS3用VPC Endpointを使用していることを確認する
- VPC Endpointポリシーを変更し、
s3:ResourceAccount が特定の数字で始まるかをテストする
- たとえばアカウントIDが
0 で始まるか確認するには、s3:ResourceAccount に "0*" 条件を置く
- EC2インスタンスから対象バケットに
GetBucketAcl などの Managementリクエスト を送る
- Managementリクエストを使うと、CloudTrail設定で追加の扱いが少なくて済む
- リクエスト結果は予想どおり
AccessDenied になる
CloudTrailで数字パターンを判定する方法
- リクエスト後、CloudTrailで
GetBucketAcl イベントが現れるかを照会する
- イベントが現れれば、VPC Endpointポリシーがリクエストを許可したことになるため、アカウントIDはテストしたパターンと一致する
- 例:
"0*" 条件でイベントが見えれば、アカウントIDは 0 で始まる
- イベントが現れなければ、VPC Endpointポリシーがリクエストをブロックしたことになるため、そのパターンとは一致しない
- CloudTrailにイベントが現れるまで数分かかる場合があるため、イベントがないと判断する前に10分待つことが推奨される
- VPC Endpointポリシーの変更も完全に伝播して適用されるまで時間がかかり、ポリシー修正後に5分待つとうまく機能する
自動化したが、基本方式は遅い
- Tracebitはこのプロセスを自動化するスクリプトを作成し、バケットのアカウントIDを安定して見つけられるようにした
- 単純に1桁ずつすべての数字を確認するのではなく、各桁で二分探索に近い方法によりテスト回数を減らす
- たとえば
s3:ResourceAccount 条件に ["0*", "1*", "2*", "3*", "4*"] のように複数のパターンを入れて範囲を分ける
- それでも、ポリシー適用とCloudTrail確認の待ち時間がボトルネックとして残る
- 二分探索を使っても約
40 * 12分 = 8時間 かかる可能性がある
- 数時間実行した例では、
bucket-alpha のアカウントIDとして 123456789101 を正常に見つけた
120個のポリシーステートメントで10分未満まで短縮
- より速い方法は、VPC Endpointポリシーに可能なすべての桁・数字の組み合わせをあらかじめ入れておくこと
- ポリシーには合計 120個のステートメント が入る
- AWSアカウントIDの各位置ごとに、可能な10個の数字をテストする
- 各ステートメントは、
s3:ResourceAccount の特定位置のパターンと aws:userid 条件を併用する
aws:userid 条件は、STS AssumeRole 呼び出しで自由に指定できる RoleSessionName の値をマッチさせるために使われる
- 特定の
RoleSessionName でロールを引き受けると、特定の桁・数字テストに対応するポリシーステートメントを選択的に通過させられる
- このポリシーはVPC Endpointポリシーの最大文字数にぎりぎり収まる
- 120個すべての可能性を並列にテストするため、毎回ポリシーを修正したりCloudTrail結果を個別に待ったりする必要が減る
- この方式により、アカウントID探索時間は10分未満に短縮される
露出範囲と応用可能性
- 一部のアクティビティは、対象バケット所有者の CloudTrailログ に見える可能性がある
- Tracebitは公開前に AWS Securityチーム と協議した
- AWSアカウントIDが機密情報かどうかについてはすでに多くの議論があり、例のCloudTrailイベントではサードパーティのアカウントIDが
HIDDEN_DUE_TO_SECURITY_REASONS として隠されている
- 同じ手法は、バケットに関連する他のリソース条件キーにも適用できる可能性がある
- 例:
aws:ResourceOrgID
- 例:
aws:ResourceOrgPaths
- 例:
aws:ResourceTag
- S3以外にこの手法を適用できる他のサービスにも応用できる可能性がある
- すべてのリージョンに相互ピアリングされたVPCとVPC Endpointを作れば、対象バケットのリージョンに関係なく動作する構成を作れる可能性がある
- この手法は、
s3:ResourceAccount 条件に対して StringLikeの部分一致 を使用できるため可能である
- VPC Endpointポリシーによって拒否されたイベントもCloudTrailに記録されるなら、有益かもしれない
1件のコメント
Hacker News のコメント
s3:ResourceAccount 条件キーにワイルドカードマッチングを適用できるというのは本当に奇妙です。
アカウント ID の部分一致で権限を許可または拒否する正当な理由はなさそうです。
StringLikeを使う仕組みなので、そうなっているようです。DevOps 側でいまサイドチャネル攻撃を発見していく流れは少し興味深いです。CPU の投機的実行サイドチャネルである Meltdown や Spectre も発見当時は大きな波紋を呼びましたし、その前には電力解析や磁気ひずみ検出、定数時間暗号といった分野もありました。
https://en.m.wikipedia.org/wiki/Side-channel_attack
https://en.m.wikipedia.org/wiki/Power_analysis
最近のサイドプロジェクトで、OWL に触発された形式でクエリを書く機能を作ったのですが、URL からホストを取り出したり、プレフィックスクエリ、
likeクエリ、正規表現クエリなどを行う関係演算子ライブラリがあります。自分のサイドプロジェクトのさらに別のサイドプロジェクトなので、やりやすいように作り、意味をなさない場合でも演算子を常に使えるようにしてあります。数値に正規表現クエリをかけるとどうなるのかも知りませんし、気にしていません。AWS 内部にも似たようなものがあるのかもしれませんが、ユーザーが多くセキュリティに敏感なシステムなら基準は違うべきです。
誰かがこうしたアイデアを思いついて賢いと思うことはあるかもしれませんが、100% 制御していないシステムに実装するのは愚かに見えます。
一般にアカウント ID を公にばらまくことはないでしょうが、いつかは一部が露出すると考えるべきです。
より多くのサードパーティベンダーや SaaS プラットフォームが、IAM ユーザーとアクセスキーではなくロール委任を好む統合方式へ移行しており、それが正しい方向です。そうなると、少なくとも統合ポイントとして使うアカウントのアカウント ID は他の当事者に知られますし、相手側にも依存関係や脆弱性などがあります。
AWS アカウント ID は IP アドレスに似ています。機微な情報になり得ますが、仕事をするには誰かが知っている必要があります。
たとえば 1〜2 年前、マネーロンダリング対策の手続きのために統合しなければならないサードパーティがありました。通常の公開 SFTP ポートより安全なので、その組織と PrivateLink を設定しようと提案したのですが、相手企業はアカウント ID を隠す必要があるというセキュリティ上の理由で拒否しました。相互の権限付与のために PV エンドポイントのロール ARN に必要だったにもかかわらずです。
結局、彼らがインバウンドの 22 番ポートに使っている公開 IP レンジを許可リストに入れました。
教訓は、ID を難読化して賢いと思うことはできても、相手が戻ってくるアドレスを知らなければビジネスを運営するのは難しいということです。
私たちはベンダー側として、通常 VPC Endpoint Service で統合します。この方式では通信が単方向で、私たちのサービスが顧客 VPC 内のロードバランサーエンドポイントとして公開されます。
興味のある方のために、コードをここに置いてあります: https://github.com/tracebit-com/find-s3-account
興味深い発見なのは確かですが、タイトルだけ見たときは、もう少し直接的な方法があるのかと思いました。
AWS で管理者アカウントとして、組織内で「X リソースはどこにあるのか」を簡単に尋ねられて、特定の S3 バケットがどのアカウントにあるのかをすばやく知れればよいのにと思います。他のリソースもそうですが、S3 バケットは特に大きいです。
正直なところ、これはより良いプラクティスができる前のレガシーバケットや、バケットをすべてコードで定義する前から存在していたものに主に起きる問題です。それでも AWS アカウントが多いと、不明なアカウントやリージョンにあるリソースを探す作業は退屈になり得ます。
そうすれば、どのアカウントがリソースを所有しているかを見つけるのは、おおよそ
select accountId where arn = "x"のようにできます。グローバル名前空間を持つ他の公開 AWS リソースも、AWS アカウント ID を明らかにします。
https://blog.plerion.com/conditional-love-for-aws-metadata-e...
少し関連して、Cloudflare account_id と zone_id は公開されても安全
https://github.com/cloudflare/cloudflare-docs/issues/474
https://community.cloudflare.com/t/api-zone-id/355566
ただし、これでできることの一つに相関関係の把握がある。同じ AWS アカウントで複数の S3 サイトを運用しているなら、それらが同じアカウントでホストされていることを他人が見られる。これが重要かどうかは脅威モデル次第
完璧ではないが、抽象化レイヤーを一つ追加してくれる
関連して、AWS キー ID はシークレットキー部分でなくても、その中にアカウント ID が 1 ビットシフトされた形で入っている
https://medium.com/@TalBeerySec/a-short-note-on-aws-key-id-f...
このキー ID は S3 の署名付き URLに含まれるため、すでにアカウント ID を公開していた可能性が高い
おそらくダウンボートされるだろうが、それでも読むなら、これは隠蔽によるセキュリティが優れた防御ではない理由を示す例だ。自分は何かを見落とすし、執拗な攻撃者は見落とさない
隠蔽に依存しないセキュリティは、攻撃者が例えば AES-256 をそのまま破ってしまう天才を雇わない限り、自分が何かを理解しているかどうかに関係なく機能する: https://www.youtube.com/watch?v=KEkrWRHCDQU
これはなぜ重要になり得るのか?明確な例として、本番バケットが与えられると、同じ組織の開発用バケットを見つけられるようになる。個人的には想定された挙動ではない
このような列挙の試みを防ぐには、バケット名にランダムに生成したプレフィックスやサフィックスを入れる必要がある。また、代替ではなく追加の対策として、デフォルトのホスト名ではない名前でバケットオブジェクトを公開し、バケット名自体が漏れないようにするのもよい方法だ
例えば自宅住所は技術的には公開情報だが、家族写真と一緒に高速道路脇の広告板に「ここに住んでいます」と掲げられるのは絶対に望まない。必要な人にだけ渡し、概ね機密に近い形で保たれるか、用途制限があると信じ、期待している
秘密でもなく、センシティブでもなく、機密でもないなら、なぜ注意して共有しなければならないのか?
ユーザーは違う見方をするかもしれない