はじめに
Security-JAWSの10周年記念「Security-JAWS DAYS ~10th Anniversary Event~」のDay2で実施されたCTF(Capture The Flag)に参加しました。
イベント概要
- 時間:13:00~17:00/4時間
- 問題数:27問(総スコア 5,590 pt ~ 5,650 pt)
- チュートリアル:6問/290 pt
- メインライン:12問/2,300 pt
- ボーナス:5問/1,400 pt
- 上級:2問/700 pt
- BLUE TEAM:1問/300 pt
- フィナーレ:1問/600 pt
- 架空のSaaS企業「TechVault社」のAWS環境
ストーリーとしては、TechVault社のポータルサービスへの侵入調査から始まり、最終的にある人の不正取引の証拠を掴むというシナリオで、CTFとしてはかなり作り込みがありました。
結果
- 解いた問題:27問(27問中)
- スコア:5,310 pt
- かかった時間:2時間48分36秒
- 最終順位:12位(125人中)
- 良かった点:
- 普段は使わない知識やCLIを使い、手を動かすことでそれぞれを深く理解できたこと。
- 反省点:
- PCの環境を事前に整えておけばよかった。
- 普段はdevContainerを使っているため、素の環境にはAWS CLIやPythonなど最低限しかインストールされておらず、Docker、OpenSSL、Boto3などが使えませんでした。そのため、必要以上に時間がかかったところがありました。
問題構成
チュートリアル(290pt)
Web調査とAWS CLIの基礎を段階的に学ぶ入門問題群。
| ID | タイトル | 概要 |
|---|---|---|
| T1 | Webの偵察・robots.txt |
robots.txtのDisallowディレクティブから隠しパスを発見する |
| T2 | 公開S3バケット | パブリック公開されたS3バケットから直接ファイルを取得する |
| T3 | HTMLソースと開発者の失敗 | ブラウザのソースビューアで見えるHTMLコメントの調査 |
| T4 | curl入門 | レスポンスヘッダーに含まれる情報の確認 |
| T5 | .envファイルの流出 | Webルートに誤配置された.envファイルの発見 |
| T6 | AWS CLIの第一歩 |
.envから取得したキーでsts get-caller-identityを実行 |
T5→T6の流れがよかったです。.envからキーを拾い、すぐにAWS CLIを触るという導線で、「Webの脆弱性がAWSへの侵入口になる」ことを体感させる構成でした。
メインルート(2,300pt)
Stage 0から始まり、侵入→権限昇格→証拠収集という攻撃チェーンを追うCTFの本筋です。
| ID | タイトル | 概要 |
|---|---|---|
| Stage 0 | 消し忘れたデバッグ | 認証APIの失敗レスポンスに残ったデバッグ出力からキーを取得 |
| Stage 1A | バケツをひっくり返せ | S3バケットの中に.hidden/プレフィックスで隠されたファイルを発見 |
| Stage 1B | 俺は誰だ | IAMポリシーのメタデータからユーザーの権限を読み解く |
| Stage 1C | 消えない過去 | GitリポジトリのコミットログにAWSキーが残存 |
| Stage 1D | AIに聞いてみた | AIアシスタントへのプロンプトインジェクション |
| Stage 2A | 消えたファイル | S3バージョニングで削除済みファイルを復元 |
| Stage 2B | 権限の地図 |
sts:AssumeRoleを使ってDataAnalystRoleへラテラルムーブメント(横移動) |
| Stage 2D | 関数の秘密 | Lambda関数の環境変数に格納された機密情報 |
| Stage 2E | パラメータの迷宮 | SSM Parameter Storeのパスを辿って情報を収集 |
| Stage 2G | AIの権限 | 過剰権限のBedrockエージェント経由でアクセス制限済みS3データを取得 |
| Stage 3A | 金庫の鍵 | Secrets Managerから暗号化ZIPの復号パスワードを取得 |
| Final | 証拠の統合 | 集めた情報を組み合わせてCEOの不正送金証拠ファイルを復号 |
Stage 2GのBedrockエージェントの問題は新鮮でした。エージェントがS3に直接アクセスできる設定になっていることで、自分の権限では読めないバケットの内容をエージェントに「教えて」と聞くだけで引き出せてしまう。AIを組み込む際の権限設計が重要だということを感じました。
ボーナス・上級・BLUE TEAM・フィナーレ(3,000pt)
メインルートから派生する形で、より深い技術的な問題がありました。
| ID | タイトル | 区分 | 概要 |
|---|---|---|---|
| Stage 2C | サーバーの影 | bonus | EC2インスタンスのタグにFlagが格納されている |
| Stage 2F | 自動で見つける | bonus |
gitleaksでブランチ全体をスキャン |
| Stage 3B | 見えない声 | bonus | SSRFでIMDSv1にアクセスしEC2ロールの一時クレデンシャルを窃取 |
| Stage 3C | 偽の顔 | advanced | Cognitoのサインアップ時にcustom:role=adminを自己申告 |
| Stage 3D | イメージの中の真実 | bonus | DockerレイヤーにRUNで削除したはずのファイルが残存 |
| Stage 3E | 隣の金庫 | advanced | S3 Vectorsのワイルドカード権限で他テナントのデータを取得 |
| Stage 4 | 痕跡を追え | blueteam | CloudTrailログから攻撃者の操作時刻を特定 |
| Stage 5 | 不審な動き | finale | CloudTrailの深夜帯の操作からCTO共謀の証拠を復号 |
| Stage 5B | 複合攻撃面 | bonus | Stage 3DとStage 3Eで入手した情報を組み合わせて内部APIを呼び出す |
Stage 3Dは一番大変でした。
なぜかというと、作業環境にはDockerが入っていなかったからです。そのため、ECRのAPIを直接叩いてイメージマニフェストを取得し、レイヤーごとにダウンロードしてtarで展開するという手順を実施しました。
docker historyを使えば一発で見えるものを、低レイヤーから辿ったのでかえって構造をよく理解できたので良かったですが、時間がかかりすぎてしまいました。
Stage 4とStage 5は、大量のCloudTrailログをjqで解析して攻撃者の痕跡を追う作業で、SOCやインシデントレスポンスの雰囲気を体験できるステージでした。
特にStage 5は、ログの中のJST深夜帯の操作を手がかりにSecrets Managerのパスを特定し、OpenSSLで暗号化ファイルを復号するという流れで、複数のステップをつなぎ合わせる必要があって面白かったです。
ただ、ここでも作業環境にOpenSSLが入っていないという状態だったので、Pythonでなんとかしました。
攻撃チェーンの全体像
各問題は独立しているようで、実は一本のストーリーとして繋がっていました。
デバッグAPIからAWSキーを取得(Stage 0)
↓
IAM偵察でAssumeRole可能なロールを発見(Stage 1B)
↓
DataAnalystRoleへラテラルムーブメント(横移動)(Stage 2B)
↓
┌──────────────────────────────────────────────────┐
│ 複数の経路を並走して情報を収集 │
│ ・EC2タグ(Stage 2C) │
│ ・S3バージョニングで削除済みファイル復元(Stage 2A)│
│ ・Lambdaの環境変数(Stage 2D) │
│ ・SSMパラメータ(Stage 2E) │
│ ・SSRF→IMDSv1(Stage 3B) │
│ ・ECRレイヤー解析(Stage 3D) │
│ ・S3 Vectorsのクロステナント漏洩(Stage 3E) │
└───────────────────────────────────────────────────┘
↓
Secrets Managerからパスワード取得(Stage 3A)
↓
ZIPを復号してCEO不正の証拠入手(Final)
↓
CloudTrailログからCTO共謀を特定(Stage 4→5)
単体では影響が限定的に見える脆弱性でも、連鎖させると深刻な侵害になります。
Stage 5Bの複合攻撃はその典型で、Stage 3DとStage 3Eで入手した情報を組み合わせてはじめて到達できる内部APIがありました。
学んだこと・対策まとめ
情報露出(デバッグなど)
開発中は便利なデバッグ出力も、本番で有効なままにしておくとAWSキーごと漏れることがあります。HTMLコメント、レスポンスヘッダー、robots.txtも攻撃者の偵察に使われます。
対策
- 本番環境でデバッグモードを無効化する。
-
X-Powered-Byなどの不要なレスポンスヘッダーを削除する。 - フロントエンドのソースコードに秘密情報を置かない。
S3の設定ミス
3種類の問題が出ました。パブリック公開、.hidden/プレフィックスによる隠蔽、バージョニングによる削除済みファイルの復元。いずれも「S3はファイルシステムではない」という点を理解していないと気づけないところでした。
対策:
- Block Public Accessを全バケットで有効化する。
- プレフィックスはアクセス制御にならない。
- バージョニングを有効にするなら、削除マーカーと古いバージョンのライフサイクルポリシーも設計する。
Gitコミット履歴の機密情報
.envファイルを削除してコミットしても、git log -pを叩けば履歴から丸見えです。gitleaksのような専用ツールを使うと全ブランチ・全コミットを一括スキャンできてしまいます。
対策:
-
git-secretsやgitleaksをPre-commitフックに組み込む。 - 一度コミットしてしまったら、
git filter-repoで履歴を書き換えた上でキーをローテートする。
プロンプトインジェクション
「前の指示を無視して」という一文でシステムプロンプトの内容を引き出せてしまいました。AIを組み込む際にシステムプロンプトに機密情報に相当するものを置いておくのは危険ということを身をもって体験しました。
対策:
- システムプロンプトに機密情報を置かない。
- 入力と出力にバリデーションを入れる。ユーザー入力とシステム指示の境界を明確にする。
IAM権限の過剰付与とAssumeRole
sts:AssumeRole権限があれば別のロールに切り替えられます。IAMポリシーのDescriptionやEC2タグにフラグが仕込まれていたのは問題設定のためだが、現実でもメタデータへの機密情報混入は発生しうるケースです。
対策:
- 最小権限原則を徹底する。
-
sts:AssumeRoleを付与するなら対象リソースを絞る。 - 信頼ポリシーのConditionで呼び出し元を制限する。
シークレット管理の不備
Lambda環境変数、SSM Parameter Store、Secrets Managerと3種類の格納場所が出てきました。Secrets Managerを使っていても、それにアクセスできるIAM権限が広すぎれば意味がないということを再認識しました。
対策:
- 機密情報はSecrets Managerで管理する。
- ただしGetSecretValueのリソースポリシーを対象のシークレットだけに絞る。
SSRF × IMDSv1
ダッシュボードのURLプレビュー機能が、内部ネットワークのhttp://169.254.169.254へのアクセスを許可していました。IMDSv1はトークン不要でアクセスできるため、SSRF経由でEC2ロールの一時クレデンシャルを窃取できてしまいます。
対策:
- EC2のIMDSv2を必須化する。
- URLフェッチ機能はホワイトリスト制限し、プライベートIPやリンクローカルアドレスへのアクセスをブロックする。
Dockerレイヤーへの機密情報残存
COPY secret.txt . → RUN python setup.py → RUN rm secret.txt というDockerfileでは、最終イメージにはsecret.txtが見えません。しかしECRのAPIを使ってレイヤーを直接ダウンロードすると、RUN rmより前のレイヤーにsecret.txtがそのまま残っています。
対策:
- マルチステージビルドを使い、機密情報はビルドコンテキストに含めない。
- 実行時にSecrets Managerから取得する構成にする。
マルチテナントの権限設計ミス
S3 VectorsのリソースポリシーがResource: "*"で設定されており、他テナントのベクターデータを自テナントのロールで読み出せる状態でした。SaaSでのマルチテナント設計は権限の分離が特に重要ということを再認識しました。
対策:
- リソースポリシーのResourceとConditionをテナントIDで絞る。
- ベクターバケットを共用するなら、クエリやメタデータへのアクセスをテナントスコープで制限する。
Cognito認可設計の欠陥
aws cognito-idp sign-upの--user-attributesにcustom:role=adminを指定するだけで、管理者権限を自称できるようになっていました。認証トークンのカスタムクレームを信頼して認可判断していたのが原因というケースです。
対策:
- ロールの付与はPre Sign-upのLambda Triggerなどサーバー側で制御する。
- フロントエンドや外部から指定できる属性で認可判断しない。
AIエージェントの過剰権限
BedrockエージェントのIAMロールに、DataAnalystRole自身からは読めないS3バケットへのアクセス権が付与されていました。「エージェントに聞く」という形で、直接は届かないデータを間接的に引き出せてしまう状態になっていました。
対策:
- AIエージェントのロールにも最小権限原則を適用する。
- エージェントが参照できるリソースを明示的にリスト化して制限する。
CloudTrailによる証拠保全
CloudTrailのログがあれば「何がいつ誰によって行なわれたか」をほぼ完全に再現できます。jqで数行書くだけで攻撃者の行動を追跡できました。
対策:
- CloudTrailを全リージョンで有効化し、S3へ保存する。
- GuardDutyと組み合わせてリアルタイム検知する。
- ログバケットにオブジェクトロックを設定して改ざんを防ぐ。
感想
全27問を通して、「AWSの設定ミスがどのように連鎖するか」を体感できました。個々の問題は「あるある」な脆弱性で、実務でも見かけることがあります。それぞれが危険なことは知っていました。しかし、それらが連鎖することでさらに深刻な侵害につながることを、改めて体感できました。その連鎖が一本のストーリーとして繋がっているところが、このCTFの設計として秀逸だと感じました。
AWSの資格勉強では学べない「なぜそれが危険なのか」を手を動かしながら理解できる機会なので、同じような機会があればまた参加したいと思いました。




