Skip to content

Latest commit

 

History

History
445 lines (320 loc) · 19.2 KB

File metadata and controls

445 lines (320 loc) · 19.2 KB

Cognito User Pools

Support HackTricks and get benefits!

Basic Information

A user pool is a user directory in Amazon Cognito. With a user pool, your users can sign in to your web or mobile app through Amazon Cognito, or federate through a third-party identity provider (IdP). Whether your users sign in directly or through a third party, all members of the user pool have a directory profile that you can access through an SDK.

User pools provide:

  • Sign-up and sign-in services.
  • A built-in, customizable web UI to sign in users.
  • Social sign-in with Facebook, Google, Login with Amazon, and Sign in with Apple, and through SAML and OIDC identity providers from your user pool.
  • User directory management and user profiles.
  • Security features such as multi-factor authentication (MFA), checks for compromised credentials, account takeover protection, and phone and email verification.
  • Customized workflows and user migration through AWS Lambda triggers.

Source code of applications will usually also contain the user pool ID and the client application ID, (and some times the application secret?) which are needed for a user to login to a Cognito User Pool.

Potential attacks

  • Registration: By default a user can register himself, so he could create a user for himself.
  • User enumeration: The registration functionality can be used to find usernames that already exists. This information can be useful for the brute-force attack.
  • Login brute-force: In the Authentication section you have all the methods that a user have to login, you could try to brute-force them find valid credentials.

Registration

User Pools allows by default to register new users.

aws cognito-idp sign-up --client-id <client-id> \
    --username <username> --password <password> \
    --region <region> --no-sign-request

If anyone can register

You might find an error indicating you that you need to provide more details of abut the user:

An error occurred (InvalidParameterException) when calling the SignUp operation: Attributes did not conform to the schema: address: The attribute is required

You can provide the needed details with a JSON such as:

--user-attributes '[{"Name": "email", "Value": "[email protected]"}, {"Name":"gender", "Value": "M"}, {"Name": "address", "Value": "street"}, {"Name": "custom:custom_name", "Value":"supername&\"*$"}]'

You could use this functionality also to enumerate existing users. This is the error message when a user already exists with that name:

An error occurred (UsernameExistsException) when calling the SignUp operation: User already exists

{% hint style="info" %} Note in the previous command how the custom attributes start with "custom:".
Also know that when registering you cannot create for the user new custom attributes. You can only give value to default attributes (even if they aren't required) and custom attributes specified. {% endhint %}

Or just to test if a client id exists. This is the error if the client-id doesn't exist:

An error occurred (ResourceNotFoundException) when calling the SignUp operation: User pool client 3ig612gjm56p1ljls1prq2miut does not exist.

If only admin can register users

You will find this error and you own't be able to register or enumerate users:

An error occurred (NotAuthorizedException) when calling the SignUp operation: SignUp is not permitted for this user pool

Verifying Registration

Cognito allows to verify a new user by verifying his email or phone number. Therefore, when creating a user usually you will be required at least the username and password and the email and/or telephone number. Just set one you control so you will receive the code to verify your newly created user account like this:

cognito-idp confirm-sign-up --client-id <cliet_id> \
    --username aasdasd2 --confirmation-code <conf_code> \
    --no-sign-request --region us-east-1

{% hint style="warning" %} Even if looks like you can use the same email and phone number, when you need to verify the created user Cognito will complain about using the same info and won't let you verify the account. {% endhint %}

Updating Attributes

By default a user can modify the value of his attributes with something like:

aws cognito-idp update-user-attributes \
    --region us-east-1 --no-sign-request \
    --user-attributes Name=address,Value=street \
    --access-token <access token>

You can use this to modify the email and phone number of a user, but then, even if the account remains as verified, those attributes are set in unverified status (you need to verify them again).

{% hint style="warning" %} You won't be able to login with email or phone number until you verify them, but you will be able to login with the username.
Note that even if the email was modified and not verified it will appear in the ID Token inside the email field and the filed email_verified will be false, but if the app isn't checking that you might impersonate other users.

Moreover, note that you can put anything inside the name field just modifying the name attribute. If an app is checking that field for some reason instead of the email (or any other attribute) you might be able to impersonate other users. {% endhint %}

Anyway, if for some reason you changed your email for example to a new one you can access you can confirm the email with the code you received in that email address:

aws cognito-idp verify-user-attribute \
    --access-token <access_token> \
    --attribute-name email --code <code> \
    --region <region> --no-sign-request

Use phone_number instead of email to change/verify a new phone number.

{% hint style="info" %} The admin could also enable the option to login with a user preferred username. Note that you won't be able to change this value to any username or preferred_username already being used to impersonate a different user. {% endhint %}

Recover/Change Password

It's possible to recover a password just knowing the username (or email or phone is accepted) and having access to it as a code will be sent there:

aws cognito-idp forgot-password \
    --client-id <client_id> \
    --username <username/email/phone> --region <region>

{% hint style="info" %} The response of the server is always going to be positive, like if the username existed. You cannot use this method to enumerate users {% endhint %}

With the code you can change the password with:

aws cognito-idp confirm-forgot-password \
    --client-id <client_id> \
    --username <username> \
    --confirmation-code <conf_code> \
    --password <pwd> --region <region>

To change the password you need to know the previous password:

aws cognito-idp change-password \
    --previous-password <value> \
    --proposed-password <value> \
    --access-token <value>

Authentication

A user pool supports different ways to authenticate to it. If you have a username and password there are also different methods supported to login.
Moreover, when a user is authenticated in the Pool 3 types of tokens are given: The ID Token, the Access token and the Refresh token.

  • ****ID Token: It contains claims about the identity of the authenticated user, such as name, email, and phone_number. The ID token can also be used to authenticate users to your resource servers or server applications. You must verify the signature of the ID token before you can trust any claims inside the ID token if you use it in external applications.
    • The ID Token is the token that contains the attributes values of the user, even the custom ones.
  • ****Access Token: It contains claims about the authenticated user, a list of the user's groups, and a list of scopes. The purpose of the access token is to authorize API operations in the context of the user in the user pool. For example, you can use the access token to grant your user access to add, change, or delete user attributes.
  • ****Refresh Token: With refresh tokens you can get new ID Tokens and Access Tokens for the user until the refresh token is invalid. By default, the refresh token expires 30 days after your application user signs into your user pool. When you create an application for your user pool, you can set the application's refresh token expiration to any value between 60 minutes and 10 years.

ADMIN_NO_SRP_AUTH & ADMIN_USER_PASSWORD_AUTH

This is the server side authentication flow:

  • The server-side app calls the AdminInitiateAuth API operation (instead of InitiateAuth). This operation requires AWS credentials with permissions that include cognito-idp:AdminInitiateAuth and cognito-idp:AdminRespondToAuthChallenge. The operation returns the required authentication parameters.
  • After the server-side app has the authentication parameters, it calls the AdminRespondToAuthChallenge API operation. The AdminRespondToAuthChallenge API operation only succeeds when you provide AWS credentials.

This method is NOT enabled by default.

To login you need to know:

  • user pool id
  • client id
  • username
  • password
  • client secret (only if the app is configured to use a secret)

{% hint style="info" %} In order to be able to login with this method that application must allow to login with ALLOW_ADMIN_USER_PASSWORD_AUTH.
Moreover, to perform this action you need credentials with the permissions**cognito-idp:AdminInitiateAuth** and cognito-idp:AdminRespondToAuthChallenge {% endhint %}

aws cognito-idp admin-initiate-auth \
    --client-id <client-id> \
    --auth-flow ADMIN_USER_PASSWORD_AUTH \
    --region <region> \
    --auth-parameters 'USERNAME=<username>,PASSWORD=<password>,SECRET_HASH=<hash_if_needed>'
    --user-pool-id "<pool-id>"

# Check the python code to learn how to generate the hsecret_hash
Code to Login
import boto3
import botocore
import hmac
import hashlib
import base64


client_id = "<client-id>"
user_pool_id = "<user-pool-id>"
client_secret = "<client-secret>"
username = "<username>"
password = "<pwd>"

boto_client = boto3.client('cognito-idp', region_name='us-east-1')

def get_secret_hash(username, client_id, client_secret):
    key = bytes(client_secret, 'utf-8')
    message = bytes(f'{username}{client_id}', 'utf-8')
    return base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()

# If the Client App isn't configured to use a secret
## just delete the line setting the SECRET_HASH
def login_user(username_or_alias, password, client_id, client_secret, user_pool_id):
    try:
        return boto_client.admin_initiate_auth(
            UserPoolId=user_pool_id,
            ClientId=client_id,
            AuthFlow='ADMIN_USER_PASSWORD_AUTH',
            AuthParameters={
                'USERNAME': username_or_alias,
                'PASSWORD': password,
                'SECRET_HASH': get_secret_hash(username_or_alias, client_id, client_secret)
            }
        )
    except botocore.exceptions.ClientError as e:
        return e.response

print(login_user(username, password, client_id, client_secret, user_pool_id))

USER_PASSWORD_AUTH

This method is another simple and traditional user & password authentication flow. It's recommended to migrate a traditional authentication method to Cognito and recommended to then disable it and use then ALLOW_USER_SRP_AUTH method instead (as that one never sends the password over the network).
This method is NOT enabled by default.

The main difference with the previous auth method inside the code is that you don't need to know the user pool ID and that you don't need extra permissions in the Cognito User Pool.

To login you need to know:

  • client id
  • username
  • password
  • client secret (only if the app is configured to use a secret)

{% hint style="info" %} In order to be able to login with this method that application must allow to login with ALLOW_USER_PASSWORD_AUTH. {% endhint %}

aws cognito-idp initiate-auth  --client-id <client-id> \
    --auth-flow USER_PASSWORD_AUTH --region <region> \
    --auth-parameters 'USERNAME=<username>,PASSWORD=<password>,SECRET_HASH=<hash_if_needed>'
    
# Check the python code to learn how to generate the secret_hash
Python code to Login
import boto3
import botocore
import hmac
import hashlib
import base64


client_id = "<client-id>"
user_pool_id = "<user-pool-id>"
client_secret = "<client-secret>"
username = "<username>"
password = "<pwd>"

boto_client = boto3.client('cognito-idp', region_name='us-east-1')

def get_secret_hash(username, client_id, client_secret):
    key = bytes(client_secret, 'utf-8')
    message = bytes(f'{username}{client_id}', 'utf-8')
    return base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()

# If the Client App isn't configured to use a secret
## just delete the line setting the SECRET_HASH
def login_user(username_or_alias, password, client_id, client_secret, user_pool_id):
    try:
        return boto_client.initiate_auth(
            ClientId=client_id,
            AuthFlow='ADMIN_USER_PASSWORD_AUTH',
            AuthParameters={
                'USERNAME': username_or_alias,
                'PASSWORD': password,
                'SECRET_HASH': get_secret_hash(username_or_alias, client_id, client_secret)
            }
        )
    except botocore.exceptions.ClientError as e:
        return e.response

print(login_user(username, password, client_id, client_secret, user_pool_id))

USER_SRP_AUTH

This is scenario is similar to the previous one but instead of of sending the password through the network to login a challenge authentication is performed (so no password navigating even encrypted through he net).
This method is enabled by default.

To login you need to know:

  • user pool id
  • client id
  • username
  • password
  • client secret (only if the app is configured to use a secret)
Code to login
from warrant.aws_srp import AWSSRP
import os

USERNAME='xxx'
PASSWORD='yyy'
POOL_ID='us-east-1_zzzzz'
CLIENT_ID = '12xxxxxxxxxxxxxxxxxxxxxxx'
CLIENT_SECRET = 'secreeeeet'
os.environ["AWS_DEFAULT_REGION"] = "<region>"

aws = AWSSRP(username=USERNAME, password=PASSWORD, pool_id=POOL_ID, 
    client_id=CLIENT_ID, client_secret=CLIENT_SECRET)
tokens = aws.authenticate_user()
id_token = tokens['AuthenticationResult']['IdToken']
refresh_token = tokens['AuthenticationResult']['RefreshToken']
access_token = tokens['AuthenticationResult']['AccessToken']
token_type = tokens['AuthenticationResult']['TokenType']

REFRESH_TOKEN_AUTH & REFRESH_TOKEN

This method is always going to be valid (it cannot be disabled) but you need to have a valid refresh token.

aws cognito-idp initiate-auth \
    --client-id 3ig6h5gjm56p1ljls1prq2miut \
    --auth-flow REFRESH_TOKEN_AUTH \
    --region us-east-1 \
    --auth-parameters 'REFRESH_TOKEN=<token>'
Code to refresh
import boto3
import botocore
import hmac
import hashlib
import base64

client_id = "<client-id>"
token = '<token>'

boto_client = boto3.client('cognito-idp', region_name='<region>')

def refresh(client_id, refresh_token):
    try:
        return boto_client.initiate_auth(
            ClientId=client_id,
            AuthFlow='REFRESH_TOKEN_AUTH',
            AuthParameters={
                'REFRESH_TOKEN': refresh_token
            }
        )
    except botocore.exceptions.ClientError as e:
        return e.response


print(refresh(client_id, token))

CUSTOM_AUTH

In this case the authentication is going to be performed through the execution of a lambda function.

Extra Security

Advanced Security

By default it's disabled, but if enabled, Cognito could be able to find account takeovers. To minimise the probability you should login from a network inside the same city, using the same user agent (and IP is thats possible).

MFA Remember device

If the user logins from the same device, the MFA might be bypassed, therefore try to login from the same browser with the same metadata (IP?) to try to bypass the MFA protection.

User Pool Groups IAM Roles

It's possible to add users to User Pool groups that are related to one or several IAM roles. In order to be able to access IAM credentials of those groups it's needed that the User Pool is assigned to an Identity Pool.
In this case, you could just login with your user, get the ID token and query the identity pool to get the credentials.

TODO: Find how to get AWS credentials of the role/s assigned to the user of the User Pool (if you know how to do this send a PR please)

Support HackTricks and get benefits!