# Cognito User Pools

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

## 基本情報

ユーザープールは、Amazon Cognitoのユーザーディレクトリです。ユーザープールを使用すると、ユーザーはAmazon Cognitoを通じて**ウェブまたはモバイルアプリにサインイン**したり、**サードパーティ**のアイデンティティプロバイダー（IdP）を通じて**フェデレート**したりできます。ユーザーが直接サインインするか、サードパーティを通じてサインインするかにかかわらず、ユーザープールのすべてのメンバーには、SDKを通じてアクセスできるディレクトリプロファイルがあります。

ユーザープールは以下を提供します：

- サインアップおよびサインインサービス。
- ユーザーをサインインさせるための組み込みのカスタマイズ可能なウェブUI。
- Facebook、Google、Amazonでのログイン、Appleでのサインイン、及びユーザープールからのSAMLおよびOIDCアイデンティティプロバイダーを使用したソーシャルサインイン。
- ユーザーディレクトリ管理とユーザープロファイル。
- 多要素認証（MFA）、侵害された資格情報のチェック、アカウント乗っ取り防止、電話およびメールの確認などのセキュリティ機能。
- AWS Lambdaトリガーを通じたカスタマイズされたワークフローとユーザー移行。

アプリケーションの**ソースコード**には通常、**ユーザープールID**と**クライアントアプリケーションID**（おそらく**アプリケーションシークレット**も？）が含まれており、これらは**ユーザーがCognitoユーザープールにログイン**するために必要です。

### 潜在的な攻撃

- **登録**：デフォルトでは、ユーザーは自分自身を登録できるため、自分のためにユーザーを作成できます。
- **ユーザー列挙**：登録機能を使用して、既に存在するユーザー名を見つけることができます。この情報はブルートフォース攻撃に役立ちます。
- **ログインブルートフォース**：[**認証**](cognito-user-pools.md#authentication)セクションには、ユーザーが**ログイン**するためのすべての**方法**が記載されており、これらをブルートフォースして**有効な資格情報を見つける**ことができます。

### ペンテスト用ツール

- [Pacu](https://github.com/RhinoSecurityLabs/pacu)は、現在`cognito__enum`および`cognito__attack`モジュールを含んでおり、アカウント内のすべてのCognito資産の列挙を自動化し、弱い構成、アクセス制御に使用されるユーザー属性などをフラグ付けし、またユーザー作成（MFAサポートを含む）や、変更可能なカスタム属性に基づく権限昇格、使用可能なアイデンティティプール資格情報、IDトークン内の引き受け可能なロールなどを自動化します。\
モジュールの機能の説明については、[ブログ投稿](https://rhinosecuritylabs.com/aws/attacking-aws-cognito-with-pacu-p2)のパート2を参照してください。インストール手順については、メインの[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) は、不要なアカウント作成やアカウントオラクルを含むさまざまな攻撃をCognitoに対して実行するPythonのCLIツールです。詳細については[このリンク](https://github.com/padok-team/cognito-scanner)を確認してください。
```bash
# Install
pip install cognito-scanner
# Run
cognito-scanner --help
```
- [CognitoAttributeEnum](https://github.com/punishell/CognitoAttributeEnum): このスクリプトは、ユーザーの有効な属性を列挙することを可能にします。
```bash
python cognito-attribute-enu.py -client_id 16f1g98bfuj9i0g3f8be36kkrl
```
## 登録

User Poolsは**デフォルト**で**新しいユーザーを登録**することを許可します。
```bash
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で提供できます:
```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が存在するかどうかをテストするためです。クライアント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は**新しいユーザーのメールアドレスまたは電話番号を確認することで確認を行う**ことができます。したがって、ユーザーを作成する際には通常、少なくともユーザー名とパスワード、そして**メールアドレスおよび/または電話番号**が必要です。**あなたが管理する**ものを設定するだけで、次のように新しく作成したユーザー**アカウント**を**確認する**ためのコードを受け取ることができます:
```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]
> 同じメールアドレスと電話番号を使用できるように見えても、作成したユーザーを確認する必要があるとき、Cognitoは同じ情報を使用していることに文句を言い、**アカウントの確認を許可しません**。

### 権限昇格 / 属性の更新

デフォルトでは、ユーザーは**自分の属性の値を変更することができます**。
```bash
aws cognito-idp update-user-attributes \
--region us-east-1 --no-sign-request \
--user-attributes Name=address,Value=street \
--access-token <access token>
```
#### カスタム属性の特権昇格

> [!CAUTION]
> **カスタム属性**（例えば `isAdmin`）が使用されているのを見つけるかもしれません。デフォルトでは、自分の属性の値を**変更できるため**、自分で値を変更することで**特権を昇格させる**ことができるかもしれません！

#### メール/ユーザー名の変更による特権昇格

これを使用してユーザーの**メールと電話番号を変更**できますが、その後、アカウントが確認済みのままであっても、これらの属性は**未確認の状態に設定されます**（再度確認する必要があります）。

> [!WARNING]
> メールまたは電話番号で**ログインできなくなります**が、**ユーザー名でログインすることは可能です**。\
> メールが変更されて未確認であっても、**`email`** **フィールド**内のIDトークンに表示され、フィールド**`email_verified`**は**false**になりますが、アプリが**それを確認していない場合、他のユーザーを偽装することができるかもしれません**。

> さらに、**`name`**フィールドに何でも入れることができ、**name属性**を変更するだけです。アプリが**`email`**（または他の属性）の代わりにそのフィールドを**確認している**場合、他のユーザーを**偽装することができるかもしれません**。

とにかく、何らかの理由でメールを新しいものに変更した場合、アクセスできる場合は、そのメールアドレスで受け取ったコードで**メールを確認することができます**：
```bash
aws cognito-idp verify-user-attribute \
--access-token <access_token> \
--attribute-name email --code <code> \
--region <region> --no-sign-request
```
**`phone_number`** を使用して **新しい電話番号** を変更/確認します。

> [!NOTE]
> 管理者は **ユーザーが好むユーザー名でのログイン** オプションを有効にすることもできます。この値を **他のユーザーを偽装するために既に使用されている任意のユーザー名またはpreferred_username** に変更することはできないことに注意してください。

### パスワードの回復/変更

ユーザー名（またはメールまたは電話が受け入れられます）を知っているだけでパスワードを回復することが可能で、そこにコードが送信されるため、アクセスが必要です。
```bash
aws cognito-idp forgot-password \
--client-id <client_id> \
--username <username/email/phone> --region <region>
```
> [!NOTE]
> サーバーの応答は常に肯定的であり、ユーザー名が存在するかのように見えます。この方法を使用してユーザーを列挙することはできません。

コードを使用してパスワードを変更できます:
```bash
aws cognito-idp confirm-forgot-password \
--client-id <client_id> \
--username <username> \
--confirmation-code <conf_code> \
--password <pwd> --region <region>
```
パスワードを変更するには、**以前のパスワードを知っている必要があります**:
```bash
aws cognito-idp change-password \
--previous-password <value> \
--proposed-password <value> \
--access-token <value>
```
## 認証

ユーザープールは、**異なる方法での認証**をサポートしています。**ユーザー名とパスワード**があれば、ログインするための**異なる方法**もサポートされています。\
さらに、ユーザーがプールで認証されると、**3種類のトークンが付与されます**: **IDトークン**、**アクセストークン**、および**リフレッシュトークン**です。

- [**IDトークン**](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-id-token.html): 認証されたユーザーの**アイデンティティに関するクレーム**（`name`、`email`、`phone_number`など）が含まれています。IDトークンは、**リソースサーバーやサーバーアプリケーションにユーザーを認証するため**にも使用できます。外部アプリケーションで使用する場合は、IDトークン内のクレームを信頼する前に、**署名を検証**する必要があります。
- IDトークンは、**ユーザーの属性値**（カスタム属性を含む）を**含むトークン**です。
- [**アクセストークン**](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-access-token.html): 認証されたユーザーに関するクレーム、**ユーザーのグループのリスト**、および**スコープのリスト**が含まれています。アクセストークンの目的は、ユーザープール内のユーザーのコンテキストで**API操作を認可すること**です。たとえば、アクセストークンを使用して、ユーザー属性の追加、変更、または削除を**許可する**ことができます。
- [**リフレッシュトークン**](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-refresh-token.html): リフレッシュトークンを使用すると、**新しいIDトークンとアクセストークン**をユーザーのために取得できます。**リフレッシュトークンが無効になるまで**利用可能です。**デフォルト**では、リフレッシュトークンは、アプリケーションユーザーがユーザープールにサインインしてから**30日後に期限切れ**になります。ユーザープール用のアプリケーションを作成する際に、アプリケーションのリフレッシュトークンの有効期限を**60分から10年の間の任意の値**に設定できます。

### ADMIN_NO_SRP_AUTH & ADMIN_USER_PASSWORD_AUTH

これはサーバー側の認証フローです：

- サーバー側のアプリが**`AdminInitiateAuth` API操作**を呼び出します（`InitiateAuth`の代わりに）。この操作には、**`cognito-idp:AdminInitiateAuth`**および**`cognito-idp:AdminRespondToAuthChallenge`**を含む権限を持つAWS資格情報が必要です。この操作は、必要な認証パラメータを返します。
- サーバー側のアプリが**認証パラメータ**を取得した後、**`AdminRespondToAuthChallenge` API操作**を呼び出します。`AdminRespondToAuthChallenge` API操作は、AWS資格情報を提供した場合にのみ成功します。

この**メソッドはデフォルトで有効ではありません**。

**ログイン**するには、**次の情報が必要**です：

- ユーザープールID
- クライアントID
- ユーザー名
- パスワード
- クライアントシークレット（アプリがシークレットを使用するように構成されている場合のみ）

> [!NOTE]
> この方法で**ログインできるようにするためには**、そのアプリケーションが`ALLOW_ADMIN_USER_PASSWORD_AUTH`でのログインを許可する必要があります。\
> さらに、このアクションを実行するには、**`cognito-idp:AdminInitiateAuth`**および**`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>ログイン用コード</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

このメソッドは、別のシンプルで**従来のユーザーとパスワード認証**フローです。**従来の**認証方法を**Cognito**に**移行する**ことが推奨されており、その後**無効にし**、代わりに**ALLOW_USER_SRP_AUTH**メソッドを**使用する**ことが推奨されています（このメソッドはパスワードをネットワーク上に送信しません）。\
この**メソッドはデフォルトでは有効になっていません**。

コード内の**前の認証メソッド**との主な**違い**は、**ユーザープールIDを知る必要がない**ことと、Cognitoユーザープールで**追加の権限が必要ない**ことです。

**ログインするためには**、以下を知っている必要があります：

- クライアントID
- ユーザー名
- パスワード
- クライアントシークレット（アプリがシークレットを使用するように設定されている場合のみ）

> [!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
```
<details>

<summary>ログインするためのPythonコード</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

このシナリオは前のものと似ていますが、**パスワードを送信する代わりに**、**チャレンジ認証が実行されます**（したがって、パスワードはネットワークを通じて暗号化されても移動しません）。\
この**メソッドはデフォルトで有効**です。

**ログインするには**、次の情報を知っている必要があります：

- ユーザープール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']
```
</details>

### REFRESH_TOKEN_AUTH & REFRESH_TOKEN

この**メソッドは常に有効です**（無効にすることはできません）が、有効なリフレッシュトークンを持っている必要があります。
```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>リフレッシュするためのコード</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

この場合、**認証**は**lambda関数の実行**を通じて行われます。

## 追加のセキュリティ

### 高度なセキュリティ

デフォルトでは無効ですが、有効にするとCognitoは**アカウントの乗っ取りを検出**できる可能性があります。確率を最小限に抑えるためには、**同じ都市内のネットワークから、同じユーザーエージェントを使用して**（可能であればIPも）ログインするべきです。

### **MFAデバイスの記憶**

ユーザーが同じデバイスからログインすると、MFAがバイパスされる可能性があるため、同じメタデータ（IP？）を使用して同じブラウザからログインしてMFA保護をバイパスしようとしてください。

## ユーザープールグループのIAMロール

**ユーザープール**グループに**ユーザー**を追加することが可能で、これらは1つの**IAMロール**に関連しています。\
さらに、**ユーザー**は異なるIAMロールが付与された**複数のグループに割り当てる**ことができます。

グループがIAMロールを持つグループ内にあっても、そのグループのIAM資格情報にアクセスするためには、**ユーザープールがアイデンティティプールによって信頼されている必要があります**（そのアイデンティティプールの詳細を知っている必要があります）。

ユーザーがユーザープールで認証されるときに**IdTokenに示されたIAMロール**を取得するためのもう1つの要件は、**アイデンティティプロバイダー認証プロバイダー**が**ロールがトークンから選択される必要がある**ことを示す必要があります。

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

ユーザーがアクセスできる**ロール**は**`IdToken`内にあり**、ユーザーは**`aws cognito-identity get-credentials-for-identity`の`--custom-role-arn`**を使用して、どのロールの資格情報を希望するかを**選択**できます。\
ただし、**デフォルトオプション**が**設定されたもの**（`use default role`）であり、IdTokenからロールにアクセスしようとすると、**エラー**が発生します（そのため、前の設定が必要です）：
```
An error occurred (InvalidParameterException) when calling the GetCredentialsForIdentity operation: Only SAML providers and providers with RoleMappings support custom role ARN.
```
> [!WARNING]
> **ユーザープールグループ**に割り当てられたロールは、**ユーザープールを信頼するアイデンティティプロバイダーによってアクセス可能である必要があります**（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
```
{{#include ../../../../banners/hacktricks-training.md}}
