This issue has been assessed as high severity. Review affected configurations immediately.
AWS Cognito Identity Pools have a feature called unauthenticated access — designed so mobile and web apps can give guest users limited AWS API access before they log in. In practice, a surprisingly large number of production environments have this enabled with overly permissive IAM roles attached. The result: anyone who can discover the Cognito Identity Pool ID can obtain valid temporary AWS credentials and start making API calls to your account.
This misconfiguration has appeared in bug bounty reports, pen test findings, and at least one documented public breach. It sits in a part of AWS that many developers set up during an early prototype and never revisit.
How Unauthenticated Access Works
Cognito Identity Pools let you federate multiple identity sources (user pools, Google, SAML, etc.) into a single pool that exchanges tokens for temporary AWS credentials via STS AssumeRoleWithWebIdentity. The unauthenticated flow is different: no identity token is required at all.
# Step 1: Get an identity ID without any credentials
aws cognito-identity get-id \
--account-id 123456789012 \
--identity-pool-id us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
--no-sign-request
# Returns:
# { "IdentityId": "us-east-1:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" }
# Step 2: Exchange the identity ID for temporary STS credentials
aws cognito-identity get-credentials-for-identity \
--identity-id "us-east-1:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" \
--no-sign-request
# Returns:
# {
# "Credentials": {
# "AccessKeyId": "ASIAX...",
# "SecretKey": "...",
# "SessionToken": "...",
# "Expiration": "..."
# }
# }
Those credentials belong to the IAM role configured as the “Unauthenticated role” in the Identity Pool. Whatever permissions that role has, the anonymous caller now has too.
What Attackers Can Do With This
The impact depends entirely on the unauthenticated role’s IAM policy. Common misconfigurations seen in the wild:
S3 read access — the intended use case for serving public assets through authenticated S3 requests. Often scoped too broadly, exposing entire buckets rather than specific prefixes.
# Enumerate what the unauthenticated role can reach
AWS_ACCESS_KEY_ID=ASIAX... AWS_SECRET_ACCESS_KEY=... AWS_SESSION_TOKEN=... \
aws sts get-caller-identity
# Run privilege enumeration
AWS_ACCESS_KEY_ID=ASIAX... aws s3 ls --recursive s3://your-app-data-bucket/
DynamoDB read — common pattern: “let guest users read public content from DynamoDB.” The table often contains more than the developer intended to expose.
Lambda invocation — guest users being able to trigger Lambda functions can create abuse vectors depending on what the functions do (sending emails, triggering workflows, accessing internal APIs).
GetSecretValue — occasionally seen: unauthenticated roles with access to Secrets Manager. This is almost always a misconfiguration rather than intentional.
The Identity Pool ID itself isn’t a secret — it’s typically embedded in mobile apps and JavaScript bundles where any motivated person can find it.
Finding Misconfigured Pools in Your AWS Account
# List all Identity Pools
aws cognito-identity list-identity-pools --max-results 60 --region us-east-1
# Describe each to check if unauthenticated access is enabled
aws cognito-identity describe-identity-pool \
--identity-pool-id us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
| jq '.AllowUnauthenticatedIdentities'
# If true, check what IAM role is attached for unauthenticated callers
aws cognito-identity get-identity-pool-roles \
--identity-pool-id us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
| jq '.Roles.unauthenticated'
Then enumerate what the unauthenticated role can do:
# Get the role ARN, then list its attached policies
ROLE_ARN=$(aws cognito-identity get-identity-pool-roles \
--identity-pool-id us-east-1:xxxx \
| jq -r '.Roles.unauthenticated')
ROLE_NAME=$(echo $ROLE_ARN | cut -d'/' -f2)
aws iam list-attached-role-policies --role-name $ROLE_NAME
aws iam list-role-policies --role-name $ROLE_NAME
CloudTrail Detection
Unauthenticated Cognito calls create CloudTrail events. The key signals:
// CloudTrail: GetCredentialsForIdentity from unauthenticated caller
{
"eventSource": "cognito-identity.amazonaws.com",
"eventName": "GetCredentialsForIdentity",
"requestParameters": {
"identityId": "us-east-1:aaaaaaaa-..."
},
"userAgent": "aws-cli/2.x Python/3.x",
"sourceIPAddress": "198.51.100.42"
}
CloudWatch Insights query to detect anomalous unauthenticated credential issuance:
fields @timestamp, sourceIPAddress, userAgent, requestParameters.identityId
| filter eventSource = "cognito-identity.amazonaws.com"
and eventName = "GetCredentialsForIdentity"
| stats count(*) as callCount by sourceIPAddress, bin(30m)
| filter callCount > 20
| sort by callCount desc
Follow-on API calls using the issued credentials appear in CloudTrail as events from the unauthenticated IAM role principal — look for API calls from sts.amazonaws.com with a session name matching the identity ID pattern.
Terraform Hardening
resource "aws_cognito_identity_pool" "main" {
identity_pool_name = "app_identity_pool"
# Disable unauthenticated access unless explicitly required
allow_unauthenticated_identities = false
# If you MUST allow unauthenticated, restrict the role severely
allow_classic_flow = false
}
resource "aws_iam_role" "cognito_unauthenticated" {
name = "cognito-unauth-role"
# Only allow if unauthenticated access is genuinely needed
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Federated = "cognito-identity.amazonaws.com" }
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"cognito-identity.amazonaws.com:aud" = aws_cognito_identity_pool.main.id
}
"ForAnyValue:StringLike" = {
"cognito-identity.amazonaws.com:amr" = "unauthenticated"
}
}
}]
})
}
resource "aws_iam_role_policy" "cognito_unauthenticated" {
name = "cognito-unauth-policy"
role = aws_iam_role.cognito_unauthenticated.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
# Minimum necessary — restrict to specific S3 prefix only
Effect = "Allow"
Action = ["s3:GetObject"]
Resource = "arn:aws:s3:::your-bucket/public/*"
}
]
})
}
Remediation Priority
Immediate: Audit all Identity Pools with AllowUnauthenticatedIdentities = true. For each, enumerate what the unauthenticated role can do. If the role has access to anything beyond specific read-only public resources, treat it as an active security incident — the credentials may have been obtained by external parties.
Short-term: Disable unauthenticated access if your application doesn’t require guest access. If it does, scope the unauthenticated role to the minimum resource and action set required — typically a single S3 prefix with s3:GetObject only.
Ongoing: Add a Config rule or Security Hub custom insight to alert when any Identity Pool has AllowUnauthenticatedIdentities set to true, so future misconfigurations are caught at creation rather than discovered in a pen test.
The gap is usually a prototype setting that was never revisited. A one-line check in your IaC review process catches it before it reaches production.