# Cognito User Pools

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

## Informazioni di base

Un user pool è un directory di utenti in Amazon Cognito. Con un user pool, i tuoi utenti possono **accedere alla tua app web o mobile** tramite Amazon Cognito, **o federarsi** tramite un **fornitore di identità** di terze parti (IdP). Che i tuoi utenti accedano direttamente o tramite un terzo, tutti i membri del user pool hanno un profilo di directory a cui puoi accedere tramite un SDK.

I user pool forniscono:

- Servizi di registrazione e accesso.
- Un'interfaccia web personalizzabile integrata per accedere agli utenti.
- Accesso sociale con Facebook, Google, Login con Amazon e Accedi con Apple, e tramite fornitori di identità SAML e OIDC dal tuo user pool.
- Gestione della directory degli utenti e profili utente.
- Funzionalità di sicurezza come l'autenticazione a più fattori (MFA), controlli per credenziali compromesse, protezione contro il takeover dell'account e verifica di telefono ed email.
- Flussi di lavoro personalizzati e migrazione degli utenti tramite trigger AWS Lambda.

Il **codice sorgente** delle applicazioni conterrà solitamente anche l'**ID del user pool** e l'**ID dell'applicazione client**, (e a volte il **segreto dell'applicazione**?) necessari per un **utente per accedere** a un Cognito User Pool.

### Attacchi potenziali

- **Registrazione**: Per impostazione predefinita, un utente può registrarsi, quindi potrebbe creare un utente per se stesso.
- **Enumerazione degli utenti**: La funzionalità di registrazione può essere utilizzata per trovare nomi utente che già esistono. Queste informazioni possono essere utili per un attacco di forza bruta.
- **Forza bruta di accesso**: Nella sezione [**Autenticazione**](cognito-user-pools.md#authentication) hai tutti i **metodi** che un utente ha per **accedere**, potresti provare a forzare in modo brutale **trovare credenziali valide**.

### Strumenti per il pentesting

- [Pacu](https://github.com/RhinoSecurityLabs/pacu), ora include i moduli `cognito__enum` e `cognito__attack` che automatizzano l'enumerazione di tutte le risorse Cognito in un account e segnalano configurazioni deboli, attributi utente utilizzati per il controllo degli accessi, ecc., e automatizzano anche la creazione di utenti (incluso il supporto MFA) e l'escalation dei privilegi basata su attributi personalizzati modificabili, credenziali di pool di identità utilizzabili, ruoli assunti nei token id, ecc.\
Per una descrizione delle funzioni dei moduli, vedere la parte 2 del [post del blog](https://rhinosecuritylabs.com/aws/attacking-aws-cognito-with-pacu-p2). Per le istruzioni di installazione, vedere la pagina principale di [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) è uno strumento CLI in python che implementa diversi attacchi su Cognito, inclusa la creazione non desiderata di account e l'oracolo degli account. Controlla [this link](https://github.com/padok-team/cognito-scanner) per ulteriori informazioni.
```bash
# Install
pip install cognito-scanner
# Run
cognito-scanner --help
```
- [CognitoAttributeEnum](https://github.com/punishell/CognitoAttributeEnum): Questo script consente di enumerare gli attributi validi per gli utenti.
```bash
python cognito-attribute-enu.py -client_id 16f1g98bfuj9i0g3f8be36kkrl
```
## Registrazione

User Pools consente per **default** di **registrare nuovi utenti**.
```bash
aws cognito-idp sign-up --client-id <client-id> \
--username <username> --password <password> \
--region <region> --no-sign-request
```
#### Se chiunque può registrarsi

Potresti trovare un errore che indica che devi **fornire più dettagli** sull'utente:
```
An error occurred (InvalidParameterException) when calling the SignUp operation: Attributes did not conform to the schema: address: The attribute is required
```
Puoi fornire i dettagli necessari con un JSON come:
```json
--user-attributes '[{"Name": "email", "Value": "carlospolop@gmail.com"}, {"Name":"gender", "Value": "M"}, {"Name": "address", "Value": "street"}, {"Name": "custom:custom_name", "Value":"supername&\"*$"}]'
```
Puoi utilizzare questa funzionalità anche per **enumerare gli utenti esistenti.** Questo è il messaggio di errore quando un utente esiste già con quel nome:
```
An error occurred (UsernameExistsException) when calling the SignUp operation: User already exists
```
> [!NOTE]
> Nota nel comando precedente come i **custom attributes iniziano con "custom:"**.\
> Sappi anche che durante la registrazione **non puoi creare nuovi custom attributes per l'utente**. Puoi solo assegnare un valore ai **default attributes** (anche se non sono richiesti) e ai **custom attributes specificati**.

O semplicemente per testare se un client id esiste. Questo è l'errore se il client-id non esiste:
```
An error occurred (ResourceNotFoundException) when calling the SignUp operation: User pool client 3ig612gjm56p1ljls1prq2miut does not exist.
```
#### Se solo l'amministratore può registrare utenti

Troverai questo errore e non sarai in grado di registrare o enumerare utenti:
```
An error occurred (NotAuthorizedException) when calling the SignUp operation: SignUp is not permitted for this user pool
```
### Verifica della Registrazione

Cognito consente di **verificare un nuovo utente verificando la sua email o il numero di telefono**. Pertanto, quando si crea un utente, di solito sarà richiesto almeno il nome utente e la password e l'**email e/o il numero di telefono**. Imposta solo uno **che controlli** in modo da ricevere il codice per **verificare il** tuo nuovo **account** utente in questo modo:
```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]
> Anche se **sembra che tu possa usare la stessa email** e numero di telefono, quando devi verificare l'utente creato Cognito si lamenterà per l'uso delle stesse informazioni e **non ti permetterà di verificare l'account**.

### Privilege Escalation / Aggiornamento degli Attributi

Per impostazione predefinita, un utente può **modificare il valore dei suoi attributi** con qualcosa come:
```bash
aws cognito-idp update-user-attributes \
--region us-east-1 --no-sign-request \
--user-attributes Name=address,Value=street \
--access-token <access token>
```
#### Privilegi di accesso agli attributi personalizzati

> [!CAUTION]
> Potresti trovare **attributi personalizzati** utilizzati (come `isAdmin`), poiché per impostazione predefinita puoi **cambiare i valori dei tuoi attributi** potresti essere in grado di **escalare i privilegi** cambiando il valore tu stesso!

#### Modifica dell'email/nome utente per privilegi di accesso

Puoi utilizzare questo per **modificare l'email e il numero di telefono** di un utente, ma poi, anche se l'account rimane verificato, quegli attributi sono **impostati in stato non verificato** (devi verificarli di nuovo).

> [!WARNING]
> Non **sarai in grado di accedere con email o numero di telefono** fino a quando non li verifichi, ma sarai **in grado di accedere con il nome utente**.\
> Nota che anche se l'email è stata modificata e non verificata apparirà nel Token ID all'interno del **campo** **`email`** e il campo **`email_verified`** sarà **false**, ma se l'app **non sta controllando questo potresti impersonare altri utenti**.

> Inoltre, nota che puoi mettere qualsiasi cosa all'interno del campo **`name`** semplicemente modificando l'**attributo name**. Se un'app **controlla** **quello** campo per qualche motivo **invece di `email`** (o qualsiasi altro attributo) potresti essere in grado di **impersonare altri utenti**.

Comunque, se per qualche motivo hai cambiato la tua email, ad esempio con una nuova a cui puoi accedere, puoi **confermare l'email con il codice che hai ricevuto a quell'indirizzo email**:
```bash
aws cognito-idp verify-user-attribute \
--access-token <access_token> \
--attribute-name email --code <code> \
--region <region> --no-sign-request
```
Usa **`phone_number`** invece di **`email`** per cambiare/verificare un **nuovo numero di telefono**.

> [!NOTE]
> L'amministratore potrebbe anche abilitare l'opzione per **accedere con un nome utente preferito dall'utente**. Tieni presente che non potrai cambiare questo valore in **nessun nome utente o preferred_username già in uso** per impersonare un altro utente.

### Recupera/Cambia Password

È possibile recuperare una password semplicemente **conoscendo il nome utente** (o l'email o il telefono sono accettati) e avendo accesso ad esso poiché un codice verrà inviato lì:
```bash
aws cognito-idp forgot-password \
--client-id <client_id> \
--username <username/email/phone> --region <region>
```
> [!NOTE]
> La risposta del server sarà sempre positiva, come se il nome utente esistesse. Non puoi utilizzare questo metodo per enumerare gli utenti

Con il codice puoi cambiare la password con:
```bash
aws cognito-idp confirm-forgot-password \
--client-id <client_id> \
--username <username> \
--confirmation-code <conf_code> \
--password <pwd> --region <region>
```
Per cambiare la password è necessario **conoscere la password precedente**:
```bash
aws cognito-idp change-password \
--previous-password <value> \
--proposed-password <value> \
--access-token <value>
```
## Autenticazione

Un pool di utenti supporta **diversi modi per autenticarsi**. Se hai un **nome utente e una password**, ci sono anche **diversi metodi** supportati per accedere.\
Inoltre, quando un utente è autenticato nel Pool, **vengono forniti 3 tipi di token**: il **Token ID**, il **Token di Accesso** e il **Token di Aggiornamento**.

- [**Token ID**](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-id-token.html): Contiene affermazioni riguardanti l'**identità dell'utente autenticato**, come `name`, `email` e `phone_number`. Il token ID può anche essere utilizzato per **autenticare gli utenti ai tuoi server di risorse o applicazioni server**. Devi **verificare** la **firma** del token ID prima di poter fidarti di qualsiasi affermazione all'interno del token ID se lo usi in applicazioni esterne.
- Il Token ID è il token che **contiene i valori degli attributi dell'utente**, anche quelli personalizzati.
- [**Token di Accesso**](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-access-token.html): Contiene affermazioni riguardanti l'utente autenticato, un elenco dei **gruppi dell'utente e un elenco di scope**. Lo scopo del token di accesso è di **autorizzare le operazioni API** nel contesto dell'utente nel pool di utenti. Ad esempio, puoi utilizzare il token di accesso per **concedere al tuo utente l'accesso** per aggiungere, modificare o eliminare attributi utente.
- [**Token di Aggiornamento**](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-refresh-token.html): Con i token di aggiornamento puoi **ottenere nuovi Token ID e Token di Accesso** per l'utente fino a quando il **token di aggiornamento non è invalido**. Per **impostazione predefinita**, il token di aggiornamento **scade 30 giorni dopo** che l'utente della tua applicazione accede al tuo pool di utenti. Quando crei un'applicazione per il tuo pool di utenti, puoi impostare la scadenza del token di aggiornamento dell'applicazione a **qualsiasi valore tra 60 minuti e 10 anni**.

### ADMIN_NO_SRP_AUTH & ADMIN_USER_PASSWORD_AUTH

Questo è il flusso di autenticazione lato server:

- L'applicazione lato server chiama l'**operazione API `AdminInitiateAuth`** (invece di `InitiateAuth`). Questa operazione richiede credenziali AWS con permessi che includono **`cognito-idp:AdminInitiateAuth`** e **`cognito-idp:AdminRespondToAuthChallenge`**. L'operazione restituisce i parametri di autenticazione richiesti.
- Dopo che l'applicazione lato server ha i **parametri di autenticazione**, chiama l'**operazione API `AdminRespondToAuthChallenge`**. L'operazione API `AdminRespondToAuthChallenge` ha successo solo quando fornisci credenziali AWS.

Questo **metodo NON è abilitato** per impostazione predefinita.

Per **accedere** devi **conoscere**:

- id del pool di utenti
- id del client
- nome utente
- password
- segreto del client (solo se l'app è configurata per utilizzare un segreto)

> [!NOTE]
> Per poter **accedere con questo metodo**, l'applicazione deve consentire l'accesso con `ALLOW_ADMIN_USER_PASSWORD_AUTH`.\
> Inoltre, per eseguire questa azione hai bisogno di credenziali con i permessi **`cognito-idp:AdminInitiateAuth`** e **`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>Codice per il Login</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

Questo metodo è un altro semplice e **tradizionale flusso di autenticazione utente e password**. È consigliato **migrare un metodo di autenticazione tradizionale** **a Cognito** e **consigliato** poi **disabilitarlo** e **utilizzare** invece il metodo **ALLOW_USER_SRP_AUTH** (poiché quest'ultimo non invia mai la password attraverso la rete).\
Questo **metodo NON è abilitato** per impostazione predefinita.

La principale **differenza** con il **metodo di autenticazione precedente** all'interno del codice è che **non è necessario conoscere l'ID del pool utenti** e che **non sono necessarie autorizzazioni extra** nel Cognito User Pool.

Per **accedere** è **necessario** conoscere:

- client id
- username
- password
- client secret (solo se l'app è configurata per utilizzare un segreto)

> [!NOTE]
> Per **poter accedere con questo metodo**, quell'applicazione deve consentire l'accesso con 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>Codice Python per il login</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

Questo scenario è simile al precedente ma **invece di inviare la password** attraverso la rete per effettuare il login, viene eseguita **un'autenticazione di sfida** (quindi nessuna password naviga nemmeno criptata attraverso la rete).\
Questo **metodo è abilitato** per impostazione predefinita.

Per **effettuare il login** è **necessario** conoscere:

- user pool id
- client id
- username
- password
- client secret (solo se l'app è configurata per utilizzare un segreto)

<details>

<summary>Codice per il 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

Questo **metodo sarà sempre valido** (non può essere disabilitato) ma è necessario avere un refresh token valido.
```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>Codice per aggiornare</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

In questo caso, l'**autenticazione** verrà eseguita tramite l'**esecuzione di una funzione lambda**.

## Sicurezza Extra

### Sicurezza Avanzata

Per impostazione predefinita è disabilitata, ma se abilitata, Cognito potrebbe essere in grado di **trovare takeover di account**. Per ridurre la probabilità, dovresti accedere da una **rete all'interno della stessa città, utilizzando lo stesso user agent** (e IP se possibile)**.**

### **MFA Ricorda dispositivo**

Se l'utente accede dallo stesso dispositivo, la MFA potrebbe essere bypassata, quindi prova ad accedere dallo stesso browser con gli stessi metadati (IP?) per cercare di bypassare la protezione MFA.

## Ruoli IAM dei Gruppi di User Pool

È possibile aggiungere **utenti ai gruppi di User Pool** che sono correlati a uno **IAM role**.\
Inoltre, **gli utenti** possono essere assegnati a **più di 1 gruppo con diversi IAM role** associati.

Nota che anche se un gruppo è all'interno di un gruppo con un IAM role associato, per poter accedere alle credenziali IAM di quel gruppo è necessario che il **User Pool sia fidato da un Identity Pool** (e conoscere i dettagli di quel Identity Pool).

Un altro requisito per ottenere il **ruolo IAM indicato nell'IdToken** quando un utente è autenticato nel User Pool (`aws cognito-idp initiate-auth...`) è che il **provider di autenticazione Identity Provider** deve indicare che il **ruolo deve essere selezionato dal token.**

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

I **ruoli** a cui un utente ha accesso sono **all'interno dell'`IdToken`**, e un utente può **selezionare quale ruolo desidera le credenziali** con il **`--custom-role-arn`** da `aws cognito-identity get-credentials-for-identity`.\
Tuttavia, se l'**opzione predefinita** è quella **configurata** (`use default role`), e provi ad accedere a un ruolo dall'IdToken, riceverai un'**errore** (ed è per questo che è necessaria la configurazione precedente):
```
An error occurred (InvalidParameterException) when calling the GetCredentialsForIdentity operation: Only SAML providers and providers with RoleMappings support custom role ARN.
```
> [!WARNING]
> Nota che il ruolo assegnato a un **User Pool Group** deve essere **accessibile dal Provider di Identità** che **si fida del User Pool** (poiché le **credenziali di sessione del ruolo IAM verranno ottenute da esso**).
```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}}
