diff --git a/src/pentesting-cloud/azure-security/az-basic-information/az-tokens-and-public-applications.md b/src/pentesting-cloud/azure-security/az-basic-information/az-tokens-and-public-applications.md index a98323582..7acea75ec 100644 --- a/src/pentesting-cloud/azure-security/az-basic-information/az-tokens-and-public-applications.md +++ b/src/pentesting-cloud/azure-security/az-basic-information/az-tokens-and-public-applications.md @@ -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//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//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 예시일 뿐이며, 더 많은 항목이 존재합니다.
-aud examples +aud 예시 -- **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`
### 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 **** to find BroCI configured apps an the trust relationships they have. +브로커 구성된 앱과 그들이 가진 신뢰 관계를 찾으려면 웹 을 확인하세요. ### 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를 시도할 때 발생합니다.
Python BroCI refresh helper (broci_auth.py) @@ -533,30 +605,31 @@ raise SystemExit(main()) ## 토큰을 찾을 수 있는 위치 -공격자 관점에서, 예를 들어 피해자의 PC가 침해되었을 때 access and refresh tokens를 어디서 찾을 수 있는지 아는 것은 매우 흥미롭다: +공격자 관점에서, 예를 들어 피해자의 PC가 침해되었을 때 access and refresh tokens를 어디에서 찾을 수 있는지 아는 것은 매우 흥미롭습니다: - **`/.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\\AppData\Local\Microsoft\IdentityCache\*`** 내부에는 여러 `.bin` 파일이 있으며, **access tokens**, ID tokens 및 계정 정보가 사용자 DPAPI로 암호화되어 있다. -- `C:\Users\\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\\AppData\Local\Microsoft\IdentityCache\*`** 아래에는 여러 `.bin` 파일이 있으며, 사용자 DPAPI로 암호화된 **access tokens**, ID tokens 및 계정 정보가 들어 있습니다 +- **`C:\Users\\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}}