Translated ['', 'src/pentesting-cloud/azure-security/az-basic-informatio

This commit is contained in:
Translator
2026-03-31 16:56:17 +00:00
parent e9687fbf3d
commit 08b57a83ae
@@ -1,10 +1,10 @@
# Az - Tokens & Public Applications
# Az - 토큰 & 공개 애플리케이션
{{#include ../../../banners/hacktricks-training.md}}
## Basic Information
## 기본 정보
Entra ID는 Microsoft의 클라우드 기반 신원 및 접근 관리(ID and Access Management, IAM) 플랫폼으로, Microsoft 365 및 Azure Resource Manager와 같은 서비스에 대한 기본 인증 및 권한 부여 시스템 역할을 합니다. Azure AD는 리소스 접근 관리를 위해 OAuth 2.0 권한 부여 프레임워크와 OpenID Connect (OIDC) 인증 프로토콜을 구현합니다.
Entra ID는 Microsoft의 클라우드 기반 신원 및 접근 관리 (IAM) 플랫폼으로, Microsoft 365 및 Azure Resource Manager와 같은 서비스 기본 인증 및 권한 부여 시스템 역할을 합니다. Azure AD는 리소스 접근 관리를 위해 OAuth 2.0 권한 부여 프레임워크와 OpenID Connect (OIDC) 인증 프로토콜을 구현합니다.
### OAuth
@@ -12,87 +12,160 @@ Entra ID는 Microsoft의 클라우드 기반 신원 및 접근 관리(ID and Acc
1. **Resource Server (RS):** 리소스 소유자가 소유한 리소스를 보호합니다.
2. **Resource Owner (RO):** 일반적으로 보호된 리소스의 소유자인 최종 사용자입니다.
3. **Client Application (CA):** 리소스 소유자를 대신하여 리소스 접근하는 애플리케이션입니다.
4. **Authorization Server (AS):** 클라이언트 애플리케이션을 인증하고 권한을 부여한 후 접근 토큰을 발급합니다.
3. **Client Application (CA):** 리소스 소유자를 대신 리소스 접근을 요청하는 애플리케이션입니다.
4. **Authorization Server (AS):** 클라이언트 애플리케이션을 인증·승인한 후 접근 토큰을 발급합니다.
**Scopes 및 Consent:**
**스코프 및 동의:**
- **Scopes:** 리소스 서버에 정의된 세분화된 권한으로 접근 수준을 지정합니다.
- **Consent:** 리소스 소유자가 특정 scopes로 클라이언트 애플리케이션의 리소스 접근을 허용하는 과정입니다.
- **Consent:** 리소스 소유자가 특정 스코프를 가진 리소스 접근 권한을 클라이언트 애플리케이션에 부여하는 과정입니다.
**Microsoft 365 통합:**
- Microsoft 365는 IAM을 위해 Azure AD를 사용하며 여러 "first-party" OAuth 애플리케이션으로 구성됩니다.
- 이러한 애플리케이션은 깊게 통합되어 있 종종 상호 의존적인 서비스 관계를 가집니다.
- 사용자 경험을 단순화하고 기능을 유지하기 위해 Microsoft는 이러한 first-party 애플리케이션에 대해 "implied consent" 또는 "pre-consent"를 부여합니다.
- **Implied Consent:** 일부 애플리케이션은 **명시적인 사용자 또는 관리자 승인 없이 특정 scopes에 대한 액세스가 자동으로 부여됩니다**.
- 이러한 사전 동의된 scopes는 일반적으로 사용자와 관리자 모두에게 숨겨져 있 표준 관리 인터페이스에서 잘 보이지 않습니다.
- 이러한 애플리케이션은 깊게 통합되어 있으며 종종 상호 의존적인 서비스 관계를 가집니다.
- 사용자 경험을 단순화하고 기능을 유지하기 위해 Microsoft는 이러한 first-party 애플리케이션에 "implied consent" 또는 "pre-consent"를 부여합니다.
- **Implied Consent:** 특정 애플리케이션은 사용자 또는 관리자에 대한 명시적 승인 없이 특정 스코프에 대한 접근 권한을 자동으로 **부여받습니다.**
- 이러한 사전 동의된 스코프는 일반적으로 사용자와 관리자 모두에게 숨겨져 있으며 표준 관리 인터페이스에서는 덜 눈에 띕니다.
**클라이언트 애플리케이션 유형:**
1. **Confidential Clients:**
- 자체 자격증명(예: 비밀번호 또는 인증서)을 보유합니다.
- Authorization Server에 대해 **자체를 안전하게 인증할 수 있습니다**.
- 권한 서버에 **안전하게 자신을 인증할 수 있습니다.**
2. **Public Clients:**
- 고유 자격증명을 보유하지 않습니다.
- Authorization Server에 대해 안전하게 인증할 수 없습니다.
- **보안적 의미:** Authorization Server가 애플리케이션의 정당성을 확인할 수 있는 메커니즘이 없으므로 공격자 토큰 요청 public client 애플리케이션을 가장할 수 있습니다.
- 고유 자격증명이 없습니다.
- 권한 서버에 안전하게 인증할 수 없습니다.
- **보안 영향:** 권한 서버가 애플리케이션의 정당성을 검증할 메커니즘이 없기 때문에 공격자 토큰 요청할 때 public client 애플리케이션을 가장할 수 있습니다.
### ROPC / Password Grant
The OAuth2 **Resource Owner Password Credentials** (**ROPC**) flow uses a direct `POST` to `https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token` with `grant_type=password`, a **username**, **password**, a **client_id**, and the requested **scope**. In Entra ID this is mainly interesting for **public clients** because the attacker can reuse Microsoft first-party client IDs or any other allowed public client without needing a secret.
```bash
curl -X POST "https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "client_id=f05ff7c9-f75a-4acd-a3b5-f4b6a870245d" \
--data-urlencode "client_info=1" \
--data-urlencode "grant_type=password" \
--data-urlencode "username=user@corp.com" \
--data-urlencode "password=Password123!" \
--data-urlencode "scope=https://graph.microsoft.com/.default"
```
If the credentials are valid and the flow is allowed, Entra can return **access tokens** and sometimes **refresh tokens** that are immediately usable against Microsoft Graph or the target resource.
### Entra ID Sign-In Log Bypass Classes
일부 과거의 Entra ID 버그는 **password validation** 또는 심지어 **full token issuance**를 수행하면서 예상되는 **Entra ID sign-in log** 항목을 생성하지 않는 경우가 있었습니다. 이러한 문제들은 수정되었지만, 이 기법들은 auth 파이프라인이 어떻게 실패하여 **downstream token use visible**한 상태가 남고 **upstream sign-in telemetry is absent**한 상태가 될 수 있는지를 이해하는 데 여전히 유용합니다.
#### 1. Foreign-tenant endpoint for stealth password validation
요청이 다른 **tenant GUID**의 token endpoint로 전송되면, Entra는 제출된 비밀번호가 제공된 username에 대해 올바른지 여부를 여전히 검증할 수 있습니다. 그 후 흐름은 해당 외부 tenant에 사용자가 없기 때문에 실패할 수 있습니다. 역사적으로 이것은 다음을 허용했습니다:
- **Password spraying / credential validation**이 피해자 tenant에 해당하는 sign-in log 없이 수행되는 경우
- 비밀번호 단계가 성공했는지 여부를 드러내는 응답 차이
- 토큰 발급은 없지만 일반적인 실패 로그인보다 적은 텔레메트리
#### 2. Force a post-password failure
credential 검증 **후에** 사용되는 파라미터(예: 유효하지 않은 `client_id`)가 잘못된 경우, 비밀번호가 이미 올바르더라도 전체 트랜잭션이 실패할 수 있습니다. 역사적으로 이러한 경우는 비밀번호 추측이 성공했음을 숨기면서 **failed** 로그인으로 표시되었습니다.
기억할 패턴은 다음과 같습니다:
- **Password check succeeds**
- 이후의 검증 단계가 실패
- 로그는 최종 트랜잭션 상태를 나타내며 성공한 password-validation 단계를 반영하지 않습니다
#### 3. Trigger logging failure with oversized-but-valid values
가장 위험한 유형은 요청이 구문적으로 유효하고 인증이 성공하여 **tokens are returned**되지만, 일부 **logged field**가 로그 파이프라인을 중단시킬 만큼 너무 큰 경우입니다. 보고된 예로는 다음이 포함됩니다:
- `openid openid openid ...`처럼 유효한 scope를 수천 번 반복
- 지나치게 길지만 여전히 수용되는 **User-Agent** 헤더 전송
이는 일반적으로 다음과 같은 문제군을 시사합니다:
1. Entra가 자격 증명과 요청 구문을 검증한다
2. The token is issued successfully
3. 로깅이 원시 사용자 제어 필드를 영구 저장하려 시도한다
4. 길이 또는 스키마 가정 때문에 로깅 쓰기가 실패한다
5. 사용자는 해당 sign-in 레코드 없이 유효한 토큰을 얻는다
Example of the repeated-scope pattern:
```bash
curl -X POST "https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "client_id=f05ff7c9-f75a-4acd-a3b5-f4b6a870245d" \
--data-urlencode "client_info=1" \
--data-urlencode "grant_type=password" \
--data-urlencode "username=user@corp.com" \
--data-urlencode "password=Password123!" \
--data-urlencode "scope=$(for num in {1..10000}; do echo -n 'openid '; done)"
```
#### Hunting / defensive note
모든 유효한 토큰 사용이 반드시 Entra sign-in 이벤트와 일치한다고 가정하지 마세요. 의심스러운 Graph 활동을 조사할 때는 다음을 상호 연관 지어 확인하세요:
- **Non-interactive sign-in logs**
- **Graph Activity Logs**
- **IP address**, **user/object ID**, **session/correlation identifiers**, 그리고 **시간 창**
한 가지 실용적인 검증 방법은 의심되는 보이지 않는 성공 이벤트를 두 개의 정상적인 실패 로그인 사이에 끼워 넣고, 수집 지연 후에 예상되는 시퀀스 `Failed -> Successful -> Failed`의 중간 이벤트가 없는지를 확인하는 것입니다. 하위 Graph 활동은 존재하지만 sign-in 로그가 없다면 이를 잠재적인 **sign-in logging gap** 또는 **token replay** 상태로 간주하세요.
## Authentication Tokens
OIDC에서 사용되는 **세 가지 토큰 유형**이 있습니다:
OIDC에서 사용되는 **세 가지 유형의 토큰**이 있습니다:
- [**Access Tokens**](https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens)**:** 클라이언트는 이 토큰을 리소스 서버에 제시하여 **리소스에 접근**니다. 특정 사용자, 클라이언트 리소스의 조합에 대해서만 사용 가능하며 만료될 때까지 **취소(revoke)**할 수 없습니다 — 기본 만료 시간은 1시간입니다.
- **ID Tokens**: 클라이언트가 Authorization Server로부터 받는 토큰으로, 사용자에 대한 기본 정보를 포함합니다. 특정 사용자 클라이언트의 조합에 **바인딩(bound)** 됩니다.
- **Refresh Tokens**: 접근 토큰과 함께 클라이언트에 제공되며, **새로운 access 및 ID 토큰을 얻기 위해** 사용됩니다. 특정 사용자와 클라이언트의 조합에 바인딩되며 취소될 수 있습니다. 비활성 refresh token의 기본 만료 기간은 **90일**이며, 활성 토큰의 경우 **만료 없음**(refresh token에서 새로운 refresh token을 얻는 것이 가능)입니다.
- refresh token은 특정 `aud`, 일부 **scopes**, 그리고 **tenant**에 연동되어야 하며 해당 aud와 scopes( 그 이상닌)와 tenant에 대해서만 access token 생성할 수 있어야 합니다. 그러나 **FOCI applications tokens**의 경우에는 그렇지 않습니다.
- [**Access Tokens**](https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens)**:** 클라이언트 리소스 서버에 **리소스에 접근**하기 위해 제시하는 토큰입니다. 특정 사용자, 클라이언트, 리소스의 조합에 대해서만 사용될 수 있으며 만료될 때까지 **철회(revoke)할 수 없습니다** — 기본값은 1시간입니다.
- **ID Tokens**: 클라이언트가 권한 부여 서버로부터 수신하는 **토큰**입니다. 사용자에 대한 기본 정보를 포함합니다. 특정 사용자 클라이언트의 조합에 **바인딩(bound)** 됩니다.
- **Refresh Tokens**: access token과 함께 클라이언트에 제공됩니다. **새로운 access 및 ID 토큰을 얻기 위해** 사용됩니다. 특정 사용자와 클라이언트의 조합에 바인딩되며 철회될 수 있습니다. 기본 만료는 비활성 refresh token의 경우 **90일**, 활성 토큰의 경우 **만료 없음**입니다 (refresh token으로부터 새로운 refresh token을 얻을 수 있기에).
- refresh token은 특정 **`aud`**, 일부 **scopes**, 그리고 **tenant**에 묶여 있어야 하며, 그 `aud`와 scopes(그리고 그 이상님) 및 tenant에 대 access token 생성할 수 있어야 합니다. 그러나 **FOCI applications tokens**의 경우에는 그렇지 않습니다.
- refresh token은 암호화되어 있으며 Microsoft만 복호화할 수 있습니다.
- 새로운 refresh token을 는다고 해서 이전 refresh token이 취소되지는 않습니다.
- 새로운 refresh token을 는다고 해서 이전 refresh token이 폐기되지는 않습니다.
> [!WARNING]
> **conditional access**에 대한 정보는 **JWT** 내부에 **저장**됩니다. 따라서 **허용된 IP 주소에서 토큰을 요청**하면 해당 **IP**가 토큰에 **저장**되고, 이후 해당 토큰을 사용해 **허용되지 않은 IP에서도 리소스에 접근**할 수 있니다.
> **conditional access**에 대한 정보는 **JWT** 내부에 **저장**됩니다. 따라서 **허용된 IP 주소에서 토큰을 요청**하면 해당 **IP**가 토큰에 **저장**되고, 이후 토큰을 **허용 IP에서 사용하여 리소스에 접근**할 수 있게 됩니다.
### Access Tokens "aud"
"aud" 필드에 표시된 항목은 로그인 수행 사용된 **리소스 서버**(애플리케이션)입니다.
"aud" 필드에 표시된 은 로그인 수행하는 데 사용된 **resource server**(애플리케이션)입니다.
명령 `az account get-access-token --resource-type [...]`는 다음 타입을 지원하며, 각 타입은 결과 access token에 특정 "aud"를 추가합니다:
명령 `az account get-access-token --resource-type [...]`는 다음 타입을 지원하며, 각 타입은 결과 access token에 특정 "aud"를 추가합니다:
> [!CAUTION]
> 다음은 `az account get-access-token`에서 지원하는 API일 뿐이며 더 많은 API가 존재합니다.
> 다음은 `az account get-access-token`에서 지원하는 API 예시일 뿐이며, 더 많은 항목이 존재합니다.
<details>
<summary>aud examples</summary>
<summary>aud 예시</summary>
- **aad-graph (Azure Active Directory Graph API)**: 레거시 Azure AD Graph API(더 이상 권장되지 않음)에 접근하는 데 사용되며, 애플리케이션이 Azure Active Directory의 디렉터리 데이터를 읽고 쓸 수 있게 합니다.
- **aad-graph (Azure Active Directory Graph API)**: 레거시 Azure AD Graph API(사용 중단 예정)에 접근하는 데 사용됩니다. 애플리케이션이 Azure Active Directory(Azure AD)의 디렉터리 데이터를 읽고 쓸 수 있게 합니다.
- `https://graph.windows.net/`
* **arm (Azure Resource Manager)**: Azure Resource Manager API를 통해 Azure 리소스를 관리하는 데 사용됩니다. 여기에는 가상 머신, 스토리지 계정 등과 같은 리소스 생성, 업데이트, 삭제하는 작업이 포함됩니다.
- **arm (Azure Resource Manager)**: Azure Resource Manager API를 통해 Azure 리소스를 관리하는 데 사용됩니다. 예: 가상 머신, 스토리지 계정 등 리소스 생성, 업데이트, 삭제와 같은 작업.
- `https://management.core.windows.net/ or https://management.azure.com/`
- **batch (Azure Batch Services)**: 병렬 대규모 및 고성능 컴퓨팅 애플리케이션을 클라우드에서 효율적으로 실행할 수 있게 해주는 Azure Batch에 접근하는 데 사용됩니다.
- **batch (Azure Batch Services)**: 대규모 병렬 및 고성능 컴퓨팅 애플리케이션을 클라우드에서 효율적으로 실행할 수 있게 는 Azure Batch에 접근하는 데 사용됩니다.
- `https://batch.core.windows.net/`
* **data-lake (Azure Data Lake Storage)**: 확장 가능한 데이터 저장 및 분석 서비스인 Azure Data Lake Storage Gen1과 상호작용하는 데 사용됩니다.
- **data-lake (Azure Data Lake Storage)**: Azure Data Lake Storage Gen1과 상호작용하는 데 사용됩니다. 이는 확장 가능한 데이터 저장 및 분석 서비스입니다.
- `https://datalake.azure.net/`
- **media (Azure Media Services)**: 비디오 및 오디오 콘텐츠에 대한 클라우드 기반 미디어 처리 및 제공 서비스를 제공하는 Azure Media Services에 접근하는 데 사용됩니다.
- **media (Azure Media Services)**: 비디오 및 오디오 콘텐츠 클라우드 기반 미디어 처리 및 배포 서비스를 제공하는 Azure Media Services에 접근하는 데 사용됩니다.
- `https://rest.media.azure.net`
* **ms-graph (Microsoft Graph API)**: Microsoft 365 서비스 데이터에 대한 통합 엔드포인트인 Microsoft Graph API에 접근하는 데 사용됩니다. Azure AD, Office 365, Enterprise Mobility 및 보안 서비스와 같은 서비스의 데이터 인사이트에 접근할 수 있습니다.
- **ms-graph (Microsoft Graph API)**: Microsoft 365 서비스 데이터 통합 엔드포인트인 Microsoft Graph API에 접근하는 데 사용됩니다. Azure AD, Office 365, Enterprise Mobility 및 보안 서비스의 데이터 인사이트에 접근할 수 있습니다.
- `https://graph.microsoft.com`
- **oss-rdbms (Azure Open Source Relational Databases)**: MySQL, PostgreSQL, MariaDB와 같은 오픈 소스 관계형 데이터베이스 엔진을 위한 Azure Database 서비스에 접근하는 데 사용됩니다.
- **oss-rdbms (Azure Open Source Relational Databases)**: MySQL, PostgreSQL, MariaDB와 같은 오픈 소스 관계형 데이터베이스 엔진 Azure Database 서비스에 접근하는 데 사용됩니다.
- `https://ossrdbms-aad.database.windows.net`
</details>
### Access Tokens Scopes "scp"
access token의 scope는 access token JWT 내부의 scp 키에 저장됩니다. 이 scopes는 access token이 무엇에 접근할 수 있는지를 정의합니다.
access token의 scope는 access token JWT 내부의 scp 키에 저장됩니다. 이러한 scopes는 access token이 무엇에 접근할 수 있는지를 정의합니다.
JWT가 특정 API에 연락하는 것이 허용되더라도 요청된 작을 수행할 수 있는 **scope가 없다면**, 해당 JWT로는 그 작을 **수행할 수 없습니다**.
JWT가 특정 API와 통신할 수 있도록 허용되더라도 요청된 작을 수행할 수 있는 scope가 **없다면**, 해당 JWT로는 그 작**수행할 수 없습니다**.
### Get refresh & access token example
```python
@@ -144,32 +217,31 @@ scopes=["https://graph.microsoft.com/.default"],
)
pprint(new_azure_cli_bearer_tokens_for_graph_api)
```
### 기타 access token 필드
### 기타 액세스 토큰 필드
- **appid**: 토큰을 생성하는 데 사용된 Application ID
- **appidacr**: Application Authentication Context Class Reference는 클라이언트가 어떻게 인증되었는지 나타냅니다. public client 경우 값은 0이고, client secret이 사용된 경우 값은 1입니다.
- **acr**: Authentication Context Class Reference 클레임은 최종 사용자 인증이 ISO/IEC 29115 요을 충족하지 못했을 때 "0"입니다.
- **amr**: Authentication method는 토큰이 어떻게 인증되었는지 나타냅니다. 값이 “pwd”이면 비밀번호가 사용되었음을 의미합니다.
- **groups**: principal이 소속된 그룹을 나타냅니다.
- **iss**: iss는 토큰을 생성한 security token service(STS)를 식별합니다. e.g. https://sts.windows.net/fdd066e1-ee37-49bc-b08f-d0e152119b04/ (the uuid is the tenant ID)
- **oid**: principal의 object ID
- **appid**: 토큰을 생성하는 데 사용된 애플리케이션 ID
- **appidacr**: Application Authentication Context Class Reference는 클라이언트가 어떻게 인증되었는지 나타냅니다. public client 경우 값은 0이고, client secret이 사용된 경우 값은 1입니다.
- **acr**: Authentication Context Class Reference claim은 최종 사용자 인증이 ISO/IEC 29115 요구사항을 충족하지 못했을 때 "0"입니다.
- **amr**: Authentication method는 토큰이 어떻게 인증되었는지 나타냅니다. 값이 “pwd”이면 비밀번호가 사용되었음을 의미합니다.
- **groups**: 주체(principal)가 속한 그룹을 나타냅니다.
- **iss**: iss는 토큰을 생성한 security token service(STS)를 식별합니다. 예: https://sts.windows.net/fdd066e1-ee37-49bc-b08f-d0e152119b04/ (uuid는 tenant ID입니다)
- **oid**: 주체의 object ID
- **tid**: Tenant ID
- **iat, nbf, exp**: 발급 시(iat), Not before(이 시간 이전에는 사용할 수 없음, 보통 iat와 동일), 만료 시간(exp).
- **iat, nbf, exp**: 발급 시(iat), Not before(nbf: 이 시간 이전에는 사용 불가, 보통 iat와 동일), 만료 시간(exp).
## FOCI Tokens Privilege Escalation
앞서 언급했듯이 refresh tokens는 생성될 때 사용된 **scopes**, 생성된 **application**, 그리고 생성된 **tenant**에 묶여야 합니다. 이러한 경계 중 어느 하나라도 무너진다면 사용자가 접근할 수 있는 다른 리소스 테넌트에 대해 원래 의도보다 더 많은 scope를 가진 access token 생성할 수 있게 되어 privilege escalation이 발생할 수 있습니다.
앞서 언급했듯이 refresh tokens는 생성될 때 **scopes**, **application**, **tenant**에 묶여야 합니다. 이 경계 중 어느 하나라도 깨지면, 사용자가 접근 가능한 다른 리소스 테넌트에 대해 원래 의도보다 더 많은 scopes로 access tokens를 생성할 수 있게 되어 권한 상승이 발생할 수 있습니다.
더욱이, **this is possible with all refresh tokens** in the [Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/) (Microsoft Entra accounts, Microsoft personal accounts, and social accounts like Facebook and Google) because as the [**docs**](https://learn.microsoft.com/en-us/entra/identity-platform/refresh-tokens) mention: "Refresh tokens are bound to a combination of user and client, but **aren't tied to a resource or tenant**. A client can use a refresh token to acquire access tokens **across any combination of resource and tenant** where it has permission to do so. Refresh tokens are encrypted and only the Microsoft identity platform can read them."
또한, [Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/) (Microsoft Entra accounts, Microsoft personal accounts, and social accounts like Facebook and Google)에 있는 모든 refresh tokens에서 **이것이 가능**합니다. 이는 [**docs**](https://learn.microsoft.com/en-us/entra/identity-platform/refresh-tokens)에서 언급한 바와 같습니다: "Refresh tokens are bound to a combination of user and client, but **aren't tied to a resource or tenant**. A client can use a refresh token to acquire access tokens **across any combination of resource and tenant** where it has permission to do so. Refresh tokens are encrypted and only the Microsoft identity platform can read them."
또한, FOCI applications는 public applications이므로 서버에 인증하기 위해 **no secret is needed**합니다.
또한 FOCI 애플리케이션은 public applications이므로 서버에 인증하기 위해 **no secret is needed** 합니다.
그렇다면 [**original research**](https://github.com/secureworks/family-of-client-ids-research/tree/main)에서 보고된 알려진 FOCI clients는 [**found here**](https://github.com/secureworks/family-of-client-ids-research/blob/main/known-foci-clients.csv)에서 확인할 수 있습니다.
보고된 알려진 FOCI 클라이언트들은 [**original research**](https://github.com/secureworks/family-of-client-ids-research/tree/main)에 있으며 [**found here**](https://github.com/secureworks/family-of-client-ids-research/blob/main/known-foci-clients.csv)에서 확인할 수 있습니다.
### 다른 scope 얻기
### Get different scope
이전 예제 코드 이어, 이 코드에서는 다른 scope에 대한 새 토큰을 요청합니다:
앞의 예제 코드 이어, 이 코드에서는 다른 scope에 대한 새 토큰을 요청합니다:
```python
# Code from https://github.com/secureworks/family-of-client-ids-research
azure_cli_bearer_tokens_for_outlook_api = (
@@ -186,7 +258,7 @@ scopes=[
)
pprint(azure_cli_bearer_tokens_for_outlook_api)
```
### 다른 클라이언트 스코프 가져오기
### 다른 클라이언트 스코프 가져오기
```python
# Code from https://github.com/secureworks/family-of-client-ids-research
microsoft_office_client = msal.PublicClientApplication("d3590ed6-52b3-4102-aeff-aad2292ab01c")
@@ -204,58 +276,58 @@ pprint(microsoft_office_bearer_tokens_for_graph_api)
```
## NAA / BroCI (Nested App Authentication / Broker Client Injection)
A BroCI refresh tokens는 기존 refresh token을 추가 브로커 파라미터와 함께 사용하여 다른 신뢰된 first-party 앱으로서 토큰을 요청하는 브로커드 토큰 교환 패턴이다.
A BroCI refresh tokens is a brokered token exchange pattern where an existing refresh token is used with extra broker parameters to request tokens as another trusted first-party app.
이러한 refresh tokens는 해당 브로커 컨텍스트에서 발급되어야 하며(일반적인 refresh token은 보통 BroCI refresh token으로 사용할 수 없다).
These refresh tokens must be minted in that broker context (a regular refresh token usually cannot be used as a BroCI refresh token).
### Goal and purpose
BroCI의 목적은 broker-capable 앱 체인에서 유효한 사용자 세션을 재사용하 다른 신뢰된 앱/리소스 쌍에 대한 토큰을 요청하는 것다. 따라서 원래 토큰에서 권한을 상승시키는 것이 가능해진다.
BroCI의 목적은 broker-capable 앱 체인에서 유효한 사용자 세션을 재사용하 다른 신뢰된 app/resource 쌍에 대한 토큰을 요청하는 것입니다. 즉, 원래 토큰에서 권한을 "상승"시킬 수 있게 합니다.
공격 관점에서, 이는 중요하다. 왜냐하면:
공격 관점에서 중요한다음과 같습니다:
- 표준 refresh 교환으로 접근할 수 없는 사전 동의된 first-party 경로를 열 수 있다.
- 넓은 delegated 권한을 가진 앱 아이덴티티로 하여금 고가치 API(예: Microsoft Graph)에 대한 access tokens를 반환할 수 있다.
- 고전적인 FOCI client switching을 넘어선 인증 후(post-authentication) 토큰 피벗 기회를 확장다.
- 표준 refresh 교환으로 접근할 수 없는 pre-consented first-party app 경로를 열 수 있습니다.
- broad delegated permissions를 가진 앱 ID 하에서 high-value API(예: Microsoft Graph)에 대한 access tokens를 반환할 수 있습니다.
- 고전적인 FOCI client 전환을 넘어선 인증 후 토큰 피벗 기회를 확장합니다.
NAA/BroCI refresh token에서 변경되는 것은 토큰의 가시적 형식이 아니라, 브로커된 리프레시 동작 동안 Microsoft가 검증하는 **issuance context**와 브로커 관련 메타데이터다.
NAA/BroCI refresh token에서 바뀌는 것은 눈에 보이는 토큰 형식이 아니라, Microsoft가 brokered refresh 작업 중에 검증하는 **issuance context**와 브로커 관련 메타데이터입니다.
NAA/BroCI 토큰 교환은 일반적인 OAuth refresh 교환과 **같지 않**.
NAA/BroCI 토큰 교환은 일반 OAuth refresh 교환과 **다릅니**.
- 일반적인 refresh token(예: device code flow를 통해 얻은)은 보통 표준 `grant_type=refresh_token` 작업에 대해 유효하다.
- BroCI 요청은 추가 브로커 컨텍스트(`brk_client_id`, 브로커 `redirect_uri`, 및 `origin`)를 포함다.
- Microsoft는 제시된 refresh token이 일치하는 브로커드 컨텍스트에서 발급되었는지 검증다.
- 따라서 많은 "정상" refresh token은 BroCI 요청에서 `AADSTS900054`("Specified Broker Client ID does not match ID in provided grant") 같은 오류로 실패한다.
- 일반적으로 정상적인 refresh token을 코드 BroCI 유효 토큰으로 "변환"할 수 없다.
- 호환 가능한 brokered flow에서 이미 발급된 refresh token이 필요다.
- 일반적인 refresh token(예: device code flow로 획득한)은 보통 표준 `grant_type=refresh_token` 연산에 유효합니다.
- BroCI 요청은 추가 브로커 컨텍스트(`brk_client_id`, broker `redirect_uri`, 및 `origin`)를 포함합니다.
- Microsoft는 제시된 refresh token이 일치하는 brokered context에서 발급되었는지 검증합니다.
- 따라서 많은 "일반" refresh token은 `AADSTS900054`("Specified Broker Client ID does not match ID in provided grant") 같은 오류로 BroCI 요청에서 실패합니다.
- 일반적으로 정상 refresh token을 코드상에서 BroCI 유효 토큰으로 "변환"할 수 없습니다.
- 호환되는 brokered flow 이미 발급된 refresh token이 필요합니다.
Check the web **<https://entrascopes.com/>** to find BroCI configured apps an the trust relationships they have.
브로커 구성된 앱과 그들이 가진 신뢰 관계를 찾으려면 웹 <https://entrascopes.com/> 을 확인하세요.
### Mental model
BroCI를 다음과 같이 생각하:
BroCI를 다음과 같이 생각하세요:
`user session -> brokered refresh token issuance -> brokered refresh call (brk_client_id + redirect_uri + origin) -> access token for target trusted app/resource`
브로커 체인의 어느 부분이라도 일치하지 않으면, 교환은 실패다.
브로커 체인의 어느 부분이라도 일치하지 않으면 교환은 실패합니다.
### Where to find a BroCI-valid refresh token
실용적인 방법 하나는 브라우저 포털 트래픽 수집다:
실용적인 방법 하나는 브라우저 포털 트래픽 수집입니다:
1. Sign in to `https://entra.microsoft.com` (or Azure portal).
2. Open DevTools -> Network.
3. Filter for:
1. `https://entra.microsoft.com` (또는 Azure portal)에 로그인합니다.
2. DevTools -> Network를 엽니다.
3. 다음으로 필터링합니다:
- `oauth2/v2.0/token`
- `management.core.windows.net`
4. Identify the brokered token response and copy `refresh_token`.
5. Use that refresh token with matching BroCI parameters (`brk_client_id`, `redirect_uri`, `origin`) when requesting tokens for target apps (for example ADIbizaUX / Microsoft_Azure_PIMCommon scenarios).
4. brokered token 응답을 식별하고 `refresh_token`을 복사합니다.
5. 대상 앱(예: ADIbizaUX / Microsoft_Azure_PIMCommon 시나리오)에 대한 토큰을 요청할 때 일치하는 BroCI 매개변수(`brk_client_id`, `redirect_uri`, `origin`)와 함께 해당 refresh token을 사용합니다.
### Common errors
- `AADSTS900054`: refresh token 컨텍스트가 제공된 브로커 튜플 (`brk_client_id` / `redirect_uri` / `origin`)과 일치하지 않거나 토큰이 브로커드 포털 흐름에서 온 것이 아니다.
- `AADSTS7000218`: 선택된 클라이언트 흐름이 confidential credential (`client_secret`/assertion)을 기대한다. 이는 종종 device code를 non-public client와 함께 시도할 때 관찰된다.
- `AADSTS900054`: refresh token 컨텍스트가 제공된 브로커 튜플(`brk_client_id` / `redirect_uri` / `origin`)과 일치하지 않거나 토큰이 brokered portal flow에서 온 것이 아니다.
- `AADSTS7000218`: 선택된 client flow는 confidential credential(`client_secret`/assertion)을 기대하며, 보통 non-public client로 device code를 시도할 때 발생합니다.
<details>
<summary>Python BroCI refresh helper (broci_auth.py)</summary>
@@ -533,30 +605,31 @@ raise SystemExit(main())
## 토큰을 찾을 수 있는 위치
공격자 관점에서, 예를 들어 피해자의 PC가 침해되었을 때 access and refresh tokens를 어디서 찾을 수 있는지 아는 것은 매우 흥미롭다:
공격자 관점에서, 예를 들어 피해자의 PC가 침해되었을 때 access and refresh tokens를 어디서 찾을 수 있는지 아는 것은 매우 흥미롭습니다:
- **`<HOME>/.Azure`** 내부
- **`azureProfile.json`**에는 과거에 로그인한 사용자 정보가 포함되어 있다
- **`clouds.config contains`** 구독 정보
- **`service_principal_entries.json`**에는 애플리케이션 자격증명(tenant id, clients and secret)이 포함되어 있다. Linux & macOS에서만
- **`msal_token_cache.json`**에는 access tokens refresh tokens가 포함되어 있다. Linux & macOS에서만
- **`service_principal_entries.bin`** 및 msal_token_cache.bin은 Windows에서 사용되며 DPAPI로 암호화
- **`msal_http_cache.bin`**은 HTTP 요청 캐시
- 로드: `with open("msal_http_cache.bin", 'rb') as f: pickle.load(f)`
- **`AzureRmContext.json`**에는 Az PowerShell을 사용한 이전 로그인에 대한 정보가 포함되어 있다(하지만 자격 증명은 없음)
- **`C:\Users\<username>\AppData\Local\Microsoft\IdentityCache\*`** 내부에는 여러 `.bin` 파일이 있으며, **access tokens**, ID tokens 및 계정 정보가 사용자 DPAPI로 암호화되어 있다.
- `C:\Users\<username>\AppData\Local\Microsoft\TokenBroken\Cache\` 내부의 `.tbres` 파일에서 DPAPI로 암호화된 base64 형태의 access tokens를 찾을 수 있다.
- Linux 및 macOS에서는 Az PowerShell(사용된 경우)에서 `pwsh -Command "Save-AzContext -Path /tmp/az-context.json"` 실행하 **access tokens, refresh tokens and id tokens**를 얻을 수 있다.
- Windows에서는 이 id tokens만 생성한다.
- Linux 및 macOS에서 Az PowerShell 사용 여부는 `$HOME/.local/share/.IdentityService/` 존재 확인할 수 있다(단 포함된 파일은 비어 있고 쓸모없음).
- 사용자가 **logged inside Azure with the browser** 상태라면, 이 [**post**](https://www.infosecnoodle.com/p/obtaining-microsoft-entra-refresh?r=357m16&utm_campaign=post&utm_medium=web)에 따르면 인증 흐름을 **redirect to localhost**로 시작하고 브라우저가 자동으로 로그인 승인을 하도록 만들 refresh token을 받을 수 있다. 단, az cli나 powershell module과 같이 localhost 리디렉트를 허용하는 FOCI 애플리케이션은 극소수이므로 해당 애플리케이션이 허용되어 있어야 한다.
- 블로그에서 설명한 다른 옵션은 도구 [**BOF-entra-authcode-flow**](https://github.com/sudonoodle/BOF-entra-authcode-flow)를 사용하는 것다. 이 도구는 어떤 애플리케이션이든 사용할 수 있는데, 최종 인증 페이지의 제목에서 OAuth 코드를 얻어 그 코드로 refresh token을 가져오기 때문이다. 리디렉트 URI `https://login.microsoftonline.com/common/oauth2/nativeclient`.
- **`azureProfile.json`**에는 이전에 로그인한 사용자에 대한 정보가 어 있습니
- **`clouds.config`**에는 구독에 대한 정보가 들어 있습니다
- **`service_principal_entries.json`**에는 애플리케이션 자격증명(tenant id, clients secret)이 어 있습니다. Linux & macOS에서만 해당
- **`msal_token_cache.json`**에는 access tokens refresh tokens가 어 있습니다. Linux & macOS에서만 해당
- **`service_principal_entries.bin`** 및 **`msal_token_cache.bin`**은 Windows에서 사용되며 DPAPI로 암호화되어 있습니
- **`msal_http_cache.bin`**은 HTTP 요청 캐시입니
- Load it: `with open("msal_http_cache.bin", 'rb') as f: pickle.load(f)`
- **`AzureRmContext.json`**에는 Az PowerShell을 사용한 이전 로그인 정보가 들어 있습니다(다만 자격증명은 포함되어 있지 않습니다)
- **`C:\Users\<username>\AppData\Local\Microsoft\IdentityCache\*`** 아래에는 여러 `.bin` 파일이 있으며, 사용자 DPAPI로 암호화된 **access tokens**, ID tokens 및 계정 정보가 들어 있습니다
- **`C:\Users\<username>\AppData\Local\Microsoft\TokenBroken\Cache\`** 내부의 `.tbres` 파일에서 DPAPI로 암호화된 base64 형태의 access tokens를 찾을 수 있습니
- Linux 및 macOS에서는 Az PowerShell(사용된 경우)에서 `pwsh -Command "Save-AzContext -Path /tmp/az-context.json"` 명령을 실행하 **access tokens, refresh tokens id tokens**를 얻을 수 있습니
- Windows에서는 이 명령이 id tokens만 생성합니다
- Linux 및 macSO에서 Az PowerShell 사용되었는지 확인하려면 `$HOME/.local/share/.IdentityService/` 존재하는지 확인할 수 있습니다(내부 파일은 비어 있고 쓸모없음)
- 사용자가 **브라우저로 Azure에 로그인한 상태**라면, 이 [**post**](https://www.infosecnoodle.com/p/obtaining-microsoft-entra-refresh?r=357m16&utm_campaign=post&utm_medium=web)에 따르면 **redirect to localhost**로 인증 흐름을 시작해 브라우저가 자동으로 로그인을 허용하도록 만들 resh token을 받을 수 있습니다. localhost 리디렉션을 허용하는 FOCI 애플리케이션은 몇 개뿐(예: az cli 또는 the powershell module)이므로 이러한 애플리케이션이 허용되어야 합니다
- 블로그에서 설명한 다른 방법은 도구 [**BOF-entra-authcode-flow**](https://github.com/sudonoodle/BOF-entra-authcode-flow)를 사용하는 것입니다. 이 도구는 모든 애플리케이션 사용할 수 있는데, 리디렉트 URI `https://login.microsoftonline.com/common/oauth2/nativeclient`를 사용하여 최종 인증 페이지의 제목에서 OAuth 코드를 얻고 그 코드를 통해 refresh token을 획득하기 때문입니다
## 참고자료
## References
- [https://github.com/secureworks/family-of-client-ids-research](https://github.com/secureworks/family-of-client-ids-research)
- [https://github.com/Huachao/azure-content/blob/master/articles/active-directory/active-directory-token-and-claims.md](https://github.com/Huachao/azure-content/blob/master/articles/active-directory/active-directory-token-and-claims.md)
- [https://specterops.io/blog/2025/10/15/naa-or-broci-let-me-explain/](https://specterops.io/blog/2025/10/15/naa-or-broci-let-me-explain/)
- [https://specterops.io/blog/2025/08/13/going-for-brokering-offensive-walkthrough-for-nested-app-authentication/](https://specterops.io/blog/2025/08/13/going-for-brokering-offensive-walkthrough-for-nested-app-authentication/)
- [https://trustedsec.com/blog/full-disclosure-a-third-and-fourth-azure-sign-in-log-bypass-found](https://trustedsec.com/blog/full-disclosure-a-third-and-fourth-azure-sign-in-log-bypass-found)
{{#include ../../../banners/hacktricks-training.md}}