# Cognito User Pools

{{#include ../../../../banners/hacktricks-training.md}}

## Informations de base

Un pool d'utilisateurs est un annuaire d'utilisateurs dans Amazon Cognito. Avec un pool d'utilisateurs, vos utilisateurs peuvent **se connecter à votre application web ou mobile** via Amazon Cognito, **ou se fédérer** via un **fournisseur d'identité** tiers (IdP). Que vos utilisateurs se connectent directement ou via un tiers, tous les membres du pool d'utilisateurs ont un profil dans l'annuaire auquel vous pouvez accéder via un SDK.

Les pools d'utilisateurs fournissent :

- Des services d'inscription et de connexion.
- Une interface utilisateur web intégrée et personnalisable pour connecter les utilisateurs.
- Connexion sociale avec Facebook, Google, Login with Amazon, et Sign in with Apple, ainsi que via des fournisseurs d'identité SAML et OIDC de votre pool d'utilisateurs.
- Gestion de l'annuaire des utilisateurs et des profils utilisateurs.
- Fonctionnalités de sécurité telles que l'authentification multi-facteurs (MFA), des vérifications pour des identifiants compromis, la protection contre la prise de contrôle de compte, et la vérification par téléphone et par email.
- Flux de travail personnalisés et migration des utilisateurs via des déclencheurs AWS Lambda.

Le **code source** des applications contiendra généralement également l'**ID du pool d'utilisateurs** et l'**ID de l'application cliente**, (et parfois le **secret de l'application** ?) qui sont nécessaires pour qu'un **utilisateur se connecte** à un pool d'utilisateurs Cognito.

### Attaques potentielles

- **Inscription** : Par défaut, un utilisateur peut s'inscrire lui-même, il pourrait donc créer un utilisateur pour lui-même.
- **Énumération des utilisateurs** : La fonctionnalité d'inscription peut être utilisée pour trouver des noms d'utilisateur qui existent déjà. Cette information peut être utile pour une attaque par force brute.
- **Force brute de connexion** : Dans la section [**Authentification**](cognito-user-pools.md#authentication), vous avez tous les **méthodes** qu'un utilisateur a pour **se connecter**, vous pourriez essayer de les forcer à **trouver des identifiants valides**.

### Outils pour le pentesting

- [Pacu](https://github.com/RhinoSecurityLabs/pacu), inclut maintenant les modules `cognito__enum` et `cognito__attack` qui automatisent l'énumération de tous les actifs Cognito dans un compte et signalent les configurations faibles, les attributs des utilisateurs utilisés pour le contrôle d'accès, etc., et automatisent également la création d'utilisateurs (y compris le support MFA) et l'escalade de privilèges basée sur des attributs personnalisables modifiables, des identifiants de pool d'identité utilisables, des rôles assumables dans les jetons d'identité, etc.\
Pour une description des fonctions des modules, voir la partie 2 du [blog](https://rhinosecuritylabs.com/aws/attacking-aws-cognito-with-pacu-p2). Pour des instructions d'installation, voir la page principale de [Pacu](https://github.com/RhinoSecurityLabs/pacu).
```bash
# Run cognito__enum usage to gather all user pools, user pool clients, identity pools, users, etc. visible in the current AWS account
Pacu (new:test) > run cognito__enum

# cognito__attack usage to attempt user creation and all privesc vectors against a given identity pool and user pool client:
Pacu (new:test) > run cognito__attack --username randomuser --email XX+sdfs2@gmail.com --identity_pools
us-east-2:a06XXXXX-c9XX-4aXX-9a33-9ceXXXXXXXXX --user_pool_clients
59f6tuhfXXXXXXXXXXXXXXXXXX@us-east-2_0aXXXXXXX
```
- [Cognito Scanner](https://github.com/padok-team/cognito-scanner) est un outil CLI en python qui implémente différentes attaques sur Cognito, y compris la création de comptes non désirés et l'oracle de compte. Consultez [ce lien](https://github.com/padok-team/cognito-scanner) pour plus d'infos.
```bash
# Install
pip install cognito-scanner
# Run
cognito-scanner --help
```
- [CognitoAttributeEnum](https://github.com/punishell/CognitoAttributeEnum) : Ce script permet d'énumérer les attributs valides pour les utilisateurs.
```bash
python cognito-attribute-enu.py -client_id 16f1g98bfuj9i0g3f8be36kkrl
```
## Inscription

User Pools permet par **défaut** de **s'inscrire de nouveaux utilisateurs**.
```bash
aws cognito-idp sign-up --client-id <client-id> \
--username <username> --password <password> \
--region <region> --no-sign-request
```
#### Si quelqu'un peut s'inscrire

Vous pourriez trouver une erreur indiquant que vous devez **fournir plus de détails** sur l'utilisateur :
```
An error occurred (InvalidParameterException) when calling the SignUp operation: Attributes did not conform to the schema: address: The attribute is required
```
Vous pouvez fournir les détails nécessaires avec un JSON tel que :
```json
--user-attributes '[{"Name": "email", "Value": "carlospolop@gmail.com"}, {"Name":"gender", "Value": "M"}, {"Name": "address", "Value": "street"}, {"Name": "custom:custom_name", "Value":"supername&\"*$"}]'
```
Vous pouvez également utiliser cette fonctionnalité pour **énumérer les utilisateurs existants.** Voici le message d'erreur lorsqu'un utilisateur existe déjà avec ce nom :
```
An error occurred (UsernameExistsException) when calling the SignUp operation: User already exists
```
> [!NOTE]
> Notez dans la commande précédente comment les **attributs personnalisés commencent par "custom:"**.\
> Sachez également que lors de l'enregistrement, vous **ne pouvez pas créer de nouveaux attributs personnalisés pour l'utilisateur**. Vous ne pouvez donner de valeur qu'aux **attributs par défaut** (même s'ils ne sont pas requis) et aux **attributs personnalisés spécifiés**.

Ou juste pour tester si un identifiant client existe. Voici l'erreur si l'identifiant client n'existe pas :
```
An error occurred (ResourceNotFoundException) when calling the SignUp operation: User pool client 3ig612gjm56p1ljls1prq2miut does not exist.
```
#### Si seul l'administrateur peut enregistrer des utilisateurs

Vous trouverez cette erreur et vous ne pourrez pas enregistrer ou énumérer des utilisateurs :
```
An error occurred (NotAuthorizedException) when calling the SignUp operation: SignUp is not permitted for this user pool
```
### Vérification de l'inscription

Cognito permet de **vérifier un nouvel utilisateur en vérifiant son email ou son numéro de téléphone**. Par conséquent, lors de la création d'un utilisateur, vous devrez généralement fournir au moins le nom d'utilisateur et le mot de passe ainsi que l'**email et/ou le numéro de téléphone**. Il suffit de définir un **que vous contrôlez** afin de recevoir le code pour **vérifier votre** compte **d'utilisateur** nouvellement créé **comme ceci :**
```bash
aws cognito-idp confirm-sign-up --client-id <cliet_id> \
--username aasdasd2 --confirmation-code <conf_code> \
--no-sign-request --region us-east-1
```
> [!WARNING]
> Même si **il semble que vous pouvez utiliser le même email** et le même numéro de téléphone, lorsque vous devez vérifier l'utilisateur créé, Cognito se plaindra d'utiliser les mêmes informations et **ne vous permettra pas de vérifier le compte**.

### Escalade de privilèges / Mise à jour des attributs

Par défaut, un utilisateur peut **modifier la valeur de ses attributs** avec quelque chose comme :
```bash
aws cognito-idp update-user-attributes \
--region us-east-1 --no-sign-request \
--user-attributes Name=address,Value=street \
--access-token <access token>
```
#### Privesc d'attributs personnalisés

> [!CAUTION]
> Vous pourriez trouver des **attributs personnalisés** utilisés (comme `isAdmin`), car par défaut vous pouvez **changer les valeurs de vos propres attributs**, vous pourriez être en mesure d'**escalader les privilèges** en changeant la valeur vous-même !

#### Privesc de modification d'email/nom d'utilisateur

Vous pouvez utiliser cela pour **modifier l'email et le numéro de téléphone** d'un utilisateur, mais ensuite, même si le compte reste vérifié, ces attributs sont **définis en statut non vérifié** (vous devez les vérifier à nouveau).

> [!WARNING]
> Vous **ne pourrez pas vous connecter avec l'email ou le numéro de téléphone** tant que vous ne les avez pas vérifiés, mais vous pourrez **vous connecter avec le nom d'utilisateur**.\
> Notez que même si l'email a été modifié et non vérifié, il apparaîtra dans le jeton ID à l'intérieur du **champ** **`email`** et le champ **`email_verified`** sera **faux**, mais si l'application **ne vérifie pas cela, vous pourriez usurper l'identité d'autres utilisateurs**.

> De plus, notez que vous pouvez mettre n'importe quoi dans le champ **`name`** en modifiant simplement l'**attribut name**. Si une application **vérifie** ce champ pour une raison quelconque **au lieu de l'`email`** (ou tout autre attribut), vous pourriez être en mesure d'**usurper l'identité d'autres utilisateurs**.

Quoi qu'il en soit, si pour une raison quelconque vous avez changé votre email par exemple pour un nouveau auquel vous pouvez accéder, vous pouvez **confirmer l'email avec le code que vous avez reçu à cette adresse email** :
```bash
aws cognito-idp verify-user-attribute \
--access-token <access_token> \
--attribute-name email --code <code> \
--region <region> --no-sign-request
```
Utilisez **`phone_number`** au lieu de **`email`** pour changer/vérifier un **nouveau numéro de téléphone**.

> [!NOTE]
> L'administrateur pourrait également activer l'option de **connexion avec un nom d'utilisateur préféré par l'utilisateur**. Notez que vous ne pourrez pas changer cette valeur pour **un nom d'utilisateur ou un preferred_username déjà utilisé** pour usurper l'identité d'un autre utilisateur.

### Récupérer/Changer le mot de passe

Il est possible de récupérer un mot de passe en **connaissant le nom d'utilisateur** (ou l'email ou le téléphone est accepté) et en y ayant accès car un code y sera envoyé :
```bash
aws cognito-idp forgot-password \
--client-id <client_id> \
--username <username/email/phone> --region <region>
```
> [!NOTE]
> La réponse du serveur sera toujours positive, comme si le nom d'utilisateur existait. Vous ne pouvez pas utiliser cette méthode pour énumérer les utilisateurs

Avec le code, vous pouvez changer le mot de passe avec :
```bash
aws cognito-idp confirm-forgot-password \
--client-id <client_id> \
--username <username> \
--confirmation-code <conf_code> \
--password <pwd> --region <region>
```
Pour changer le mot de passe, vous devez **connaître le mot de passe précédent** :
```bash
aws cognito-idp change-password \
--previous-password <value> \
--proposed-password <value> \
--access-token <value>
```
## Authentification

Un pool d'utilisateurs prend en charge **différentes manières de s'authentifier**. Si vous avez un **nom d'utilisateur et un mot de passe**, il existe également **différentes méthodes** prises en charge pour se connecter.\
De plus, lorsqu'un utilisateur est authentifié dans le Pool, **3 types de jetons sont fournis** : le **jeton ID**, le **jeton d'accès** et le **jeton d'actualisation**.

- [**Jeton ID**](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-id-token.html) : Il contient des revendications sur **l'identité de l'utilisateur authentifié**, telles que `name`, `email` et `phone_number`. Le jeton ID peut également être utilisé pour **authentifier les utilisateurs auprès de vos serveurs de ressources ou applications serveur**. Vous devez **vérifier** la **signature** du jeton ID avant de pouvoir faire confiance à des revendications à l'intérieur du jeton ID si vous l'utilisez dans des applications externes.
- Le jeton ID est le jeton qui **contient les valeurs des attributs de l'utilisateur**, même les personnalisés.
- [**Jeton d'accès**](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-access-token.html) : Il contient des revendications sur l'utilisateur authentifié, une liste des **groupes de l'utilisateur** et une liste de portées. Le but du jeton d'accès est de **autoriser les opérations API** dans le contexte de l'utilisateur dans le pool d'utilisateurs. Par exemple, vous pouvez utiliser le jeton d'accès pour **accorder à votre utilisateur l'accès** pour ajouter, modifier ou supprimer des attributs d'utilisateur.
- [**Jeton d'actualisation**](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-refresh-token.html) : Avec les jetons d'actualisation, vous pouvez **obtenir de nouveaux jetons ID et jetons d'accès** pour l'utilisateur jusqu'à ce que le **jeton d'actualisation soit invalide**. Par **défaut**, le jeton d'actualisation **expire 30 jours après** que l'utilisateur de votre application se soit connecté à votre pool d'utilisateurs. Lorsque vous créez une application pour votre pool d'utilisateurs, vous pouvez définir l'expiration du jeton d'actualisation de l'application à **n'importe quelle valeur entre 60 minutes et 10 ans**.

### ADMIN_NO_SRP_AUTH & ADMIN_USER_PASSWORD_AUTH

Voici le flux d'authentification côté serveur :

- L'application côté serveur appelle l'opération API **`AdminInitiateAuth`** (au lieu de `InitiateAuth`). Cette opération nécessite des identifiants AWS avec des autorisations incluant **`cognito-idp:AdminInitiateAuth`** et **`cognito-idp:AdminRespondToAuthChallenge`**. L'opération renvoie les paramètres d'authentification requis.
- Après que l'application côté serveur a les **paramètres d'authentification**, elle appelle l'opération API **`AdminRespondToAuthChallenge`**. L'opération API `AdminRespondToAuthChallenge` ne réussit que si vous fournissez des identifiants AWS.

Cette **méthode n'est PAS activée** par défaut.

Pour **se connecter**, vous **devez** connaître :

- l'identifiant du pool d'utilisateurs
- l'identifiant du client
- le nom d'utilisateur
- le mot de passe
- le secret du client (uniquement si l'application est configurée pour utiliser un secret)

> [!NOTE]
> Afin de **pouvoir se connecter avec cette méthode**, cette application doit autoriser la connexion avec `ALLOW_ADMIN_USER_PASSWORD_AUTH`.\
> De plus, pour effectuer cette action, vous avez besoin d'identifiants avec les autorisations **`cognito-idp:AdminInitiateAuth`** et **`cognito-idp:AdminRespondToAuthChallenge`**
```python
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
```
<details>

<summary>Code pour se connecter</summary>
```python
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))
```
</details>

### USER_PASSWORD_AUTH

Cette méthode est un autre flux simple et **traditionnel d'authentification utilisateur et mot de passe**. Il est recommandé de **migrer une méthode d'authentification traditionnelle** vers **Cognito** et **recommandé** de **la désactiver** ensuite et **d'utiliser** la méthode **ALLOW_USER_SRP_AUTH** à la place (car celle-ci n'envoie jamais le mot de passe sur le réseau).\
Cette **méthode n'est PAS activée** par défaut.

La principale **différence** avec la **méthode d'authentification précédente** dans le code est que vous **n'avez pas besoin de connaître l'ID du pool d'utilisateurs** et que vous **n'avez pas besoin de permissions supplémentaires** dans le pool d'utilisateurs Cognito.

Pour **se connecter**, vous **devez** connaître :

- client id
- nom d'utilisateur
- mot de passe
- secret client (uniquement si l'application est configurée pour utiliser un secret)

> [!NOTE]
> Afin de **pouvoir se connecter avec cette méthode**, cette application doit permettre de se connecter avec ALLOW_USER_PASSWORD_AUTH.
```python
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
```
<details>

<summary>Code Python pour se connecter</summary>
```python
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))
```
</details>

### USER_SRP_AUTH

Ce scénario est similaire au précédent mais **au lieu d'envoyer le mot de passe** à travers le réseau pour se connecter, une **authentification par défi est effectuée** (donc pas de mot de passe naviguant même crypté à travers le net).\
Cette **méthode est activée** par défaut.

Pour **se connecter**, vous **devez** connaître :

- l'identifiant du pool d'utilisateurs
- l'identifiant du client
- le nom d'utilisateur
- le mot de passe
- le secret du client (uniquement si l'application est configurée pour utiliser un secret)

<details>

<summary>Code to login</summary>
```python
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']
```
</details>

### REFRESH_TOKEN_AUTH & REFRESH_TOKEN

Cette **méthode sera toujours valide** (elle ne peut pas être désactivée) mais vous devez avoir un jeton de rafraîchissement valide.
```bash
aws cognito-idp initiate-auth \
--client-id 3ig6h5gjm56p1ljls1prq2miut \
--auth-flow REFRESH_TOKEN_AUTH \
--region us-east-1 \
--auth-parameters 'REFRESH_TOKEN=<token>'
```
<details>

<summary>Code pour rafraîchir</summary>
```python
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))
```
</details>

### CUSTOM_AUTH

Dans ce cas, l'**authentification** va être effectuée par l'**exécution d'une fonction lambda**.

## Sécurité supplémentaire

### Sécurité avancée

Par défaut, c'est désactivé, mais si activé, Cognito pourrait être capable de **détecter les prises de contrôle de compte**. Pour minimiser la probabilité, vous devriez vous connecter depuis un **réseau à l'intérieur de la même ville, en utilisant le même agent utilisateur** (et l'IP si c'est possible)**.**

### **MFA Se souvenir de l'appareil**

Si l'utilisateur se connecte depuis le même appareil, la MFA pourrait être contournée, essayez donc de vous connecter depuis le même navigateur avec les mêmes métadonnées (IP ?) pour essayer de contourner la protection MFA.

## Rôles IAM des groupes de User Pool

Il est possible d'ajouter des **utilisateurs aux groupes de User Pool** qui sont liés à un **rôle IAM**.\
De plus, des **utilisateurs** peuvent être assignés à **plus d'un groupe avec différents rôles IAM** attachés.

Notez que même si un groupe est à l'intérieur d'un groupe avec un rôle IAM attaché, pour pouvoir accéder aux identifiants IAM de ce groupe, il est nécessaire que le **User Pool soit approuvé par un Identity Pool** (et connaître les détails de cet Identity Pool).

Un autre requis pour obtenir le **rôle IAM indiqué dans l'IdToken** lorsqu'un utilisateur est authentifié dans le User Pool (`aws cognito-idp initiate-auth...`) est que le **fournisseur d'authentification Identity Provider** doit indiquer que le **rôle doit être sélectionné à partir du token.**

<figure><img src="../../../../images/image (250).png" alt=""><figcaption></figcaption></figure>

Les **rôles** auxquels un utilisateur a accès sont **dans le `IdToken`**, et un utilisateur peut **sélectionner quel rôle il aimerait obtenir des identifiants** avec le **`--custom-role-arn`** de `aws cognito-identity get-credentials-for-identity`.\
Cependant, si l'**option par défaut** est celle **configurée** (`use default role`), et que vous essayez d'accéder à un rôle à partir de l'IdToken, vous obtiendrez une **erreur** (c'est pourquoi la configuration précédente est nécessaire) :
```
An error occurred (InvalidParameterException) when calling the GetCredentialsForIdentity operation: Only SAML providers and providers with RoleMappings support custom role ARN.
```
> [!WARNING]
> Notez que le rôle attribué à un **User Pool Group** doit être **accessible par le fournisseur d'identité** qui **fait confiance au User Pool** (car les **informations d'identification de session du rôle IAM vont être obtenues à partir de celui-ci**).
```json
{
"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:2361092e-9db6-a876-1027-10387c9de439"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}js
```
{{#include ../../../../banners/hacktricks-training.md}}
