Cognito 用户池

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

基本信息

用户池是 Amazon Cognito 中的用户目录。通过用户池,您的用户可以通过 Amazon Cognito 登录到您的网页或移动应用或通过 第三方 身份提供者 (IdP) 进行联合身份验证。无论您的用户是直接登录还是通过第三方,所有用户池的成员都有一个可以通过 SDK 访问的目录配置文件。

用户池提供:

  • 注册和登录服务。
  • 内置的可定制网页 UI 以登录用户。
  • 通过 Facebook、Google、Amazon 登录和 Apple 登录的社交登录,以及通过 SAML 和 OIDC 身份提供者的用户池。
  • 用户目录管理和用户配置文件。
  • 安全功能,如多因素身份验证 (MFA)、对被泄露凭证的检查、账户接管保护,以及电话和电子邮件验证。
  • 通过 AWS Lambda 触发器定制的工作流和用户迁移。

应用程序的源代码 通常还会包含 用户池 ID客户端应用程序 ID(有时还有 应用程序密钥?),这些都是 用户登录 Cognito 用户池所需的。

潜在攻击

  • 注册:默认情况下,用户可以自我注册,因此他可以为自己创建一个用户。
  • 用户枚举:注册功能可用于查找已存在的用户名。这些信息对于暴力破解攻击可能很有用。
  • 登录暴力破解:在 身份验证 部分,您可以找到用户 登录 的所有 方法,您可以尝试暴力破解它们以 找到有效凭证

渗透测试工具

  • Pacu 现在包括 cognito__enumcognito__attack 模块,这些模块自动枚举帐户中的所有 Cognito 资产并标记弱配置、用于访问控制的用户属性等,同时还自动创建用户(包括 MFA 支持)和基于可修改自定义属性、可用身份池凭证、可假设角色的 ID 令牌等的权限提升。
    有关模块功能的描述,请参见 博客文章 的第二部分。有关安装说明,请参见主 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 是一个用 Python 实现的 CLI 工具,执行对 Cognito 的不同攻击,包括不必要的账户创建和账户 oracle。有关更多信息,请查看 this link
# Install
pip install cognito-scanner
# Run
cognito-scanner --help
python cognito-attribute-enu.py -client_id 16f1g98bfuj9i0g3f8be36kkrl

注册

用户池默认允许注册新用户

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

如果任何人都可以注册

您可能会发现一个错误,指示您需要提供更多关于用户的详细信息

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

您可以提供所需的详细信息,格式为 JSON,例如:

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

您还可以使用此功能来枚举现有用户。 当已存在该名称的用户时,错误消息为:

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

Note

注意在之前的命令中,自定义属性以 “custom:” 开头
还要知道,在注册时,您无法为用户创建新的自定义属性。您只能为默认属性(即使它们不是必需的)和指定的自定义属性赋值。

或者只是测试客户端 ID 是否存在。如果 client-id 不存在,则会出现以下错误:

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

如果只有管理员可以注册用户

您将发现此错误,您将无法注册或枚举用户:

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

验证注册

Cognito 允许通过验证用户的电子邮件或电话号码来验证新用户。因此,在创建用户时,通常至少需要用户名和密码,以及电子邮件和/或电话号码。只需设置一个您控制的,这样您就会收到代码来验证您的新创建的用户帐户,如下所示:

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

Warning

即使看起来你可以使用相同的电子邮件和电话号码,当你需要验证创建的用户时,Cognito会抱怨使用相同的信息,并且不会让你验证账户

权限提升 / 更新属性

默认情况下,用户可以修改其属性的值,使用类似于:

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

自定义属性权限提升

Caution

您可能会发现使用 custom attributes(例如 isAdmin),因为默认情况下您可以 更改自己属性的值,您可能能够 通过自己更改值来提升权限

邮箱/用户名修改权限提升

您可以使用此功能 修改用户的电子邮件和电话号码,但即使帐户保持验证状态,这些属性也会 设置为未验证状态(您需要再次验证它们)。

Warning

无法使用电子邮件或电话号码登录,直到您验证它们,但您可以 使用用户名登录
请注意,即使电子邮件已被修改且未验证,它仍会出现在 ID Token 的 email 字段中,并且字段 email_verified 将为 false,但如果应用程序 没有检查,您可能会冒充其他用户。

此外,请注意,您可以在 name 字段中放入任何内容,只需修改 name attribute。如果应用程序 出于某种原因检查 该字段 而不是 email(或任何其他属性),您可能能够 冒充其他用户

无论如何,如果出于某种原因您将电子邮件更改为可以访问的新电子邮件,您可以 使用您在该电子邮件地址收到的代码确认电子邮件

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

使用 phone_number 替代 email 来更改/验证 新电话号码

Note

管理员还可以启用 使用用户首选用户名登录 的选项。请注意,您将无法将此值更改为 任何已被使用的用户名或首选用户名 以冒充其他用户。

恢复/更改密码

只需 知道用户名(或电子邮件或电话也可以接受)并且能够访问它,因为代码将发送到那里:

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

Note

服务器的响应总是会是积极的,比如用户名存在。您无法使用此方法枚举用户

使用以下代码可以更改密码:

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

要更改密码,您需要知道之前的密码

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

认证

用户池支持不同的认证方式。如果您有用户名和密码,也支持不同的方法进行登录。
此外,当用户在池中被认证时,会发放3种类型的令牌ID令牌访问令牌刷新令牌

  • ID令牌:它包含关于已认证用户身份的声明,例如nameemailphone_number。ID令牌还可以用于对您的资源服务器或服务器应用程序进行用户认证。如果您在外部应用程序中使用ID令牌,您必须验证ID令牌的签名,才能信任ID令牌中的任何声明。
  • ID令牌是包含用户属性值的令牌,甚至包括自定义属性。
  • 访问令牌:它包含关于已认证用户的声明、用户组的列表作用域的列表。访问令牌的目的是在用户池中授权API操作。例如,您可以使用访问令牌授予用户访问添加、修改或删除用户属性的权限。
  • 刷新令牌:使用刷新令牌,您可以为用户获取新的ID令牌和访问令牌,直到刷新令牌失效。默认情况下,刷新令牌在您的应用程序用户登录用户池后30天过期。当您为用户池创建应用程序时,可以将应用程序的刷新令牌过期时间设置为60分钟到10年之间的任何值

ADMIN_NO_SRP_AUTH & ADMIN_USER_PASSWORD_AUTH

这是服务器端认证流程:

  • 服务器端应用调用**AdminInitiateAuth API操作**(而不是InitiateAuth)。此操作需要具有包括**cognito-idp:AdminInitiateAuthcognito-idp:AdminRespondToAuthChallenge**权限的AWS凭证。该操作返回所需的认证参数。
  • 在服务器端应用获得认证参数后,它调用**AdminRespondToAuthChallenge API操作**。只有在提供AWS凭证时,AdminRespondToAuthChallenge API操作才会成功。

方法默认情况下未启用

登录,您需要知道:

  • 用户池ID
  • 客户端ID
  • 用户名
  • 密码
  • 客户端密钥(仅在应用配置为使用密钥时)

Note

为了能够使用此方法登录,该应用程序必须允许使用ALLOW_ADMIN_USER_PASSWORD_AUTH进行登录。
此外,要执行此操作,您需要具有**cognito-idp:AdminInitiateAuthcognito-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
登录代码 ```python import boto3 import botocore import hmac import hashlib import base64

client_id = “” user_pool_id = “” client_secret = “” username = “” password = “

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

此方法是另一种简单的**传统用户和密码认证**流程。建议将**传统**认证方法**迁移到Cognito**,并建议随后**禁用**它,改为使用**ALLOW_USER_SRP_AUTH**方法(因为该方法从不通过网络发送密码)。\
此**方法默认情况下未启用**。

与代码中的**前一种认证方法**的主要**区别**在于您**不需要知道用户池ID**,并且您**不需要额外的权限**在Cognito用户池中。

要**登录**,您**需要**知道:

- client id
- username
- password
- client secret(仅在应用程序配置为使用密钥时)

> [!NOTE]
> 为了**能够使用此方法登录**,该应用程序必须允许使用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
登录的Python代码 ```python import boto3 import botocore import hmac import hashlib import base64

client_id = “” user_pool_id = “” client_secret = “” username = “” password = “

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

这个场景与之前的类似,但**不是通过网络发送密码**来登录,而是**执行挑战认证**(因此没有密码即使加密也不会在网络中传输)。\
这个**方法是默认启用**的。

要**登录**,您**需要**知道:

- 用户池 ID
- 客户端 ID
- 用户名
- 密码
- 客户端密钥(仅在应用程序配置为使用密钥时)

<details>

<summary>登录代码</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']

REFRESH_TOKEN_AUTH & REFRESH_TOKEN

方法始终有效(无法禁用),但您需要拥有有效的刷新令牌。

aws cognito-idp initiate-auth \
--client-id 3ig6h5gjm56p1ljls1prq2miut \
--auth-flow REFRESH_TOKEN_AUTH \
--region us-east-1 \
--auth-parameters 'REFRESH_TOKEN=<token>'
刷新代码 ```python import boto3 import botocore import hmac import hashlib import base64

client_id = “” token = ‘

boto_client = boto3.client(‘cognito-idp’, region_name=‘’)

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

在这种情况下,**身份验证**将通过**执行一个lambda函数**来进行。

## 额外安全性

### 高级安全性

默认情况下是禁用的,但如果启用,Cognito可能能够**发现账户接管**。为了最小化这种可能性,您应该从**同一城市的网络登录,使用相同的用户代理**(如果可能的话,使用相同的IP)。

### **MFA 记住设备**

如果用户从同一设备登录,MFA可能会被绕过,因此尝试从相同的浏览器使用相同的元数据(IP?)登录,以尝试绕过MFA保护。

## 用户池组 IAM 角色

可以将**用户添加到与一个**IAM角色**相关的用户池组中。\
此外,**用户**可以被分配到**多个具有不同IAM角色**的组中。

请注意,即使一个组在一个附有IAM角色的组内,要能够访问该组的IAM凭证,需要**用户池被身份池信任**(并了解该身份池的详细信息)。

另一个要求是在用户在用户池中经过身份验证时获取**IdToken中指示的IAM角色**(`aws cognito-idp initiate-auth...`),需要**身份提供者身份验证提供者**指示**角色必须从令牌中选择**。

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

用户可以访问的**角色**在**`IdToken`**中,用户可以使用**`--custom-role-arn`**从`aws cognito-identity get-credentials-for-identity`选择他希望获取凭证的**角色**。\
然而,如果**默认选项**是**配置的**(`使用默认角色`),并且您尝试从IdToken访问一个角色,您将会得到**错误**(这就是为什么需要之前的配置):

An error occurred (InvalidParameterException) when calling the GetCredentialsForIdentity operation: Only SAML providers and providers with RoleMappings support custom role ARN.

> [!WARNING]
> 请注意,分配给 **User Pool Group** 的角色需要 **被信任 User Pool 的身份提供者可访问**(因为 IAM 角色的 **会话凭证将从中获取**)。
```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

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks