DEV Community

Bala Paranj
Bala Paranj

Posted on

Your Cognito Identity Pool is Handing Out AWS Credentials to Anonymous Users

✓ Human-authored analysis; AI used for formatting and proofreading.

Four AWS CLI commands. No username, password or authentication of any kind. Temporary AWS credentials in your hand, with whatever permissions the unauthenticated IAM role grants.

This is not a bug. It's a configuration. It's live in production environments right now.

The four commands

# 1. Get an identity ID (no auth required)
aws cognito-identity get-id \
  --identity-pool-id us-east-1:abc123

# 2. Get temporary AWS credentials (no auth required)
aws cognito-identity get-credentials-for-identity \
  --identity-id us-east-1:xxx

# 3. Use the credentials
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...

# 4. List whatever the role grants access to
aws s3 ls
Enter fullscreen mode Exit fullscreen mode

The identity pool ID is the only input. It's not a secret. It's embedded in mobile app bundles, JavaScript SDKs, and client-side configuration files. Shodan, GitHub search, and APK decompilation surface them routinely.

Why this exists

Cognito identity pools have a setting: allow_unauthenticated_identities. When set to true, anyone who knows the identity pool ID can call GetCredentialsForIdentity and receive temporary AWS credentials mapped to the pool's unauthenticated IAM role.

This was designed for mobile apps that need limited AWS access before the user logs in — reading public content, uploading analytics, fetching app configuration. The AWS documentation says "unauthenticated users should have very limited permissions."

In practice, the unauthenticated role often has more permissions than intended. The role was configured during an MVP sprint, the permissions were broadened during debugging, and nobody scoped them back down after launch. The identity pool setting stayed true because changing it might break the app.

Why your scanner doesn't flag it

Your CSPM checks individual resources against individual rules:

  • "Is this S3 bucket public?" — No. The bucket policy requires IAM authentication.
  • "Is this IAM role overpermissioned?" — Maybe. It has s3:GetObject on a specific bucket. That's narrow enough to pass most checks.
  • "Is this identity pool configured for unauthenticated access?" — Yes, but the scanner reports it as a MEDIUM finding. It doesn't know what the role can reach.

Each check passes or produces a low-priority finding. No scanner asks the compound question: "Can an anonymous internet user obtain AWS credentials through this identity pool and use those credentials to read data from this S3 bucket?"

That question spans three services (Cognito, IAM, S3) and three assets (identity pool, IAM role, S3 bucket). No single-resource check can phrase it.

The chain

Anonymous internet user
  │
  │ calls GetCredentialsForIdentity (no auth required)
  ↓
Cognito Identity Pool
  │ allow_unauthenticated_identities = true
  │
  │ returns temporary AWS credentials for
  ↓
IAM Role: Cognito_appUnauth_Role
  │ trusts cognito-identity.amazonaws.com
  │ has policy granting s3:GetObject
  ↓
S3 Bucket: app-data
  │ tagged data_classification: confidential
  │ encrypted with SSE-S3 (no key policy barrier)
  ↓
Data exfiltrated
  │ no CloudTrail data events enabled
  ↓
No audit trail
Enter fullscreen mode Exit fullscreen mode

Six steps. Five configurations. Each individually defensible. The interaction between them is the vulnerability.

Compound detection

Running this scenario through a static analysis tool that checks cross-service interactions produces a different picture than component-level scanning.

CEL predicate evaluation fires three findings: unauthenticated access enabled, self-registration unrestricted, MFA not enforced. These are the individual checks a scanner would produce.

SMT satisfiability checking (Z3, cvc5, Yices) asks: "Does there exist a principal, action, and resource such that an unauthenticated user can reach confidential S3 data through this configuration?" Three independent solvers from three institutions answer: yes. The witness names the specific identity pool, role, action, and bucket. This is a mathematical proof that the path exists.

Scope: this proof is valid within the scope of exported SIR facts; the Fact Export reference names which property domains the SIR currently covers.

Datalog reachability enumeration (Soufflé) asks: "How many such paths exist?" Answer: 12. The CISO's blast radius number — not "a path exists" but "twelve paths exist, six of which are anonymous."

Probabilistic risk modeling asks: "How likely is exploitation?" Answer: 41%. The identity pool endpoint is discoverable. No authentication barrier. The attack requires no special tooling.

Game-theoretic cost analysis asks: "What does the attack cost?" Answer: $300 in attacker effort (endpoint discovery + credential acquisition + data access). "What does the fix cost?" Answer: $50 (disable unauthenticated access — one API call, one test cycle). ROI: infinite.

Temporal drift analysis asks: "After remediation, how far is the configuration from regressing to unsafe?" Answer: 1 configuration change. A developer re-enabling unauthenticated access for debugging would reopen the entire chain.

Five individual findings become one compound finding with a proof, a blast radius, a probability, an attacker cost, a fix cost, and a drift margin. That's the difference between a scanner that checks components and an analysis that checks interactions.

The fix

aws cognito-identity update-identity-pool \
  --identity-pool-id us-east-1:abc12345 \
  --no-allow-unauthenticated-identities
Enter fullscreen mode Exit fullscreen mode

One setting. After the change, every solver returns "no path exists." Every engine agrees: safe.

If the app needs unauthenticated access (analytics upload, public content fetch), scope the unauthenticated role to the minimum required actions on the minimum required resources. Remove s3:GetObject on anything containing real data. Add CloudTrail data events so any access is logged.

How to check your own environment

# List your identity pools
aws cognito-identity list-identity-pools --max-results 20

# For each pool, check if unauthenticated access is enabled
aws cognito-identity describe-identity-pool \
  --identity-pool-id <pool-id> \
  | jq '.AllowUnauthenticatedIdentities'
Enter fullscreen mode Exit fullscreen mode

If it returns true, check what the unauthenticated role can do:

# Get the role ARN from the pool's role mapping
aws cognito-identity get-identity-pool-roles \
  --identity-pool-id <pool-id> \
  | jq '.Roles.unauthenticated'

# Check the role's permissions
aws iam list-attached-role-policies --role-name <role-name>
aws iam get-role-policy --role-name <role-name> --policy-name <policy-name>
Enter fullscreen mode Exit fullscreen mode

If the unauthenticated role has access to anything beyond truly public resources, you have the same vulnerability. The scanner that checks each resource individually won't tell you. The compound check will.

The broader problem

This isn't a Cognito-specific problem. It's a composition problem. The same problem appears in every major cloud breach:

  • Capital One: Five individually defensible configurations. The interaction between a WAF, a metadata endpoint, an IAM role, an S3 bucket, and missing data event logging created the breach path.
  • Bybit ($1.5B): A developer's S3 access combined with a wildcard resource grant and missing object-level logging.
  • SolarWinds: A build pipeline combined with signing key access and no integrity verification.

The vulnerability is never in one setting. It's in the interaction between settings across services, teams, and accounts. The tools that cost millions of dollars per year check one setting at a time. The composition is the gap they cannot close. Because their architecture evaluates resources independently. Checking interactions requires a fundamentally different approach.


The scenarios in this article are modeled on real configurations found in bug bounty programs and publicly disclosed incidents. The analysis uses Stave, an open-source static analysis tool that evaluates cloud configurations via CEL predicates and exports standardized facts for consumption by external reasoning engines — all from air-gapped snapshots with no cloud credentials required.

Top comments (0)