Cognito User Pools
Reading time: 17 minutes
tip
Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d'abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
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, 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, inclut maintenant les modules
cognito__enum
etcognito__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. Pour des instructions d'installation, voir la page principale de Pacu.
# 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 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 pour plus d'infos.
# Install
pip install cognito-scanner
# Run
cognito-scanner --help
- CognitoAttributeEnum : Ce script permet d'énumérer les attributs valides pour les utilisateurs.
python cognito-attribute-enu.py -client_id 16f1g98bfuj9i0g3f8be36kkrl
Inscription
User Pools permet par défaut de s'inscrire de nouveaux utilisateurs.
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 :
--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 :
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 :
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'
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 :
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é :
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 :
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 :
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 : Il contient des revendications sur l'identité de l'utilisateur authentifié, telles que
name
,email
etphone_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 : 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 : 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 deInitiateAuth
). Cette opération nécessite des identifiants AWS avec des autorisations incluantcognito-idp:AdminInitiateAuth
etcognito-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 APIAdminRespondToAuthChallenge
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
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 pour se connecter
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
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.
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
Code Python pour se connecter
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
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)
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
Cette méthode sera toujours valide (elle ne peut pas être désactivée) mais vous devez avoir un jeton de rafraîchissement valide.
aws cognito-idp initiate-auth \
--client-id 3ig6h5gjm56p1ljls1prq2miut \
--auth-flow REFRESH_TOKEN_AUTH \
--region us-east-1 \
--auth-parameters 'REFRESH_TOKEN=<token>'
Code pour rafraîchir
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
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.
.png)
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).
{
"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
tip
Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d'abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.