前言
Elastic 威胁研究与检测工程 (TRADE) 团队成员最近将注意力转向了针对 Microsoft Entra ID(以前称为 Azure AD)中 OAuth 工作流的一类新兴威胁。这项研究的灵感来自 Volexity 最近的博客《网络钓鱼代码:俄罗斯威胁行为者瞄准微软 365 OAuth 工作流》 ,该博客将针对非政府组织的复杂 OAuth 网络钓鱼活动归咎于指定为UTA0352的威胁行为者。
Volexity 的调查提供了令人信服的法医证据,证明攻击者如何滥用受信任的第一方 Microsoft 应用程序来绕过传统防御措施。使用合法的 OAuth 流程和开源工具ROADtools ,攻击者制作了定制的 Microsoft 身份验证 URL、获取安全令牌并利用它们冒充用户、提升权限并通过 Microsoft Graph 窃取数据——包括下载 Outlook 电子邮件和访问 SharePoint 网站。
虽然他们的报告详细记录了攻击的内容,但我们 Elastic 的团队专注于了解攻击的方式。我们在受控环境中模拟了攻击链,以亲自探索令牌滥用、设备注册和令牌丰富的机制。这次亲身实践的实验让我们对微软 OAuth 实施的内部运作、ROADtools 的实际用途、推荐的缓解措施以及最重要的,有效的检测策略(用于识别和应对类似活动)有了更深入的了解。
Microsoft Entra ID 中的 OAuth
Microsoft Entra ID 实施 OAuth 2.0,以启用对 Microsoft 365 服务(例如 Outlook、SharePoint 和 Graph API)的委托访问。虽然 OAuth 规范是标准化的( RFC6749 ),但 Entra ID 引入了独特的行为和令牌类型,这些行为和令牌类型会影响委托访问的工作方式以及对手如何利用它们。
在委托访问中,应用程序被授权代表已登录用户行事,但受到应用程序请求的范围(权限)以及用户或管理员同意的限制。这种模型在企业环境中很常见,在企业环境中,应用程序会检索用户的电子邮件、文件或目录数据,而无需每次都提示输入凭据。
典型的委托授权流程包括:
授权请求(OAuth 2.0 授权码授予) :应用程序请求访问具有特定范围(例如,Mail.Read、offline_access)的资源(例如,Graph)。这些作为参数添加到 URI。
- client_id :应用程序的 ID(例如 VSCode)
- Response_type :确定授权类型 OAuth 工作流程(例如设备代码、授权码)
- 范围:针对目标资源请求的权限(例如邮件.阅读,离线访问)
- Redirect_uri :将我们的授权码发送到哪里
- 状态:CSRF保护
- Login_hint :预填用户名
用户身份验证(OpenID Connect) :Entra ID 通过策略(密码、MFA、设备信任)对用户进行身份验证。
- 单因素身份验证(SFA)
- 多重身份验证 (MFA)
- 设备信任(混合加入、Intune 合规性)
- 条件访问策略 (CAP)
- 单点登录 (SSO)
同意:同意决定应用程序是否可以接收授权码以及允许的范围。
- 用户同意的范围(例如邮件.阅读,离线访问)
- 管理员同意所需的范围(例如Directory.ReadWrite )需要提升的批准。
令牌发行:应用程序收到授权码,然后将其兑换为:
- 访问令牌——用于调用 Graph 等 API 的短期令牌。
- 刷新令牌 (RT) – 寿命更长的令牌,用于静默获取新的访问令牌。
- 身份令牌 - 描述经过身份验证的用户;存在于 OpenID 流中。
- (可选)主刷新令牌:如果用户在加入域或注册的设备上,主刷新令牌 (PRT) 可能会启用静默 SSO 和其他令牌流,而无需用户交互。
- 令牌声明:声明是嵌入在 JWT 令牌中的键值对,用于描述身份验证的用户、应用程序、设备、范围和上下文。
MSFT OAuth 网络钓鱼 URL 的定义
在深入研究 Volexity 报告中有助于制定检测策略的关键发现之前,重要的是先分解一下 Microsoft OAuth 网络钓鱼 URL 的具体定义。
如前所述,Microsoft Entra ID 依靠这些 URL 来确定哪个应用程序(客户端)代表哪个用户主体请求访问什么资源,以及使用什么权限。大部分上下文直接嵌入在 OAuth 授权请求的查询参数中,使其成为对手和防御者的关键元数据来源。
以下是与授权码授予流程一致的网络钓鱼 URL 示例,改编自 Volexity 的博客:
https://login.microsoftonline[.]com/organizations/oauth2/v2.0/authorize?state=https://mae.gov[.]ro/[REMOVED]&client_id=aebc6443-996d-45c2-90f0-388ff96faa56&scope=https://graph.microsoft.com/.default&response_type=code&redirect_uri=https://insiders.vscode.dev/redirect&login_hint=<EMAIL HERE>
让我们分解一下其中的一些关键组件:
- login.microsoftonline.com – 全球 Microsoft Entra ID 身份验证端点。
- /oauth2/v2.0/authorize - 用于授权工作流的 MSFT Entra ID OAuth v2.0 端点
- state – 可选值,用于防止 CSRF 并维护应用程序状态。有时会被滥用来混淆网络钓鱼重定向。
- client_id – 发出请求的应用程序 ID。这可能属于 Microsoft 第一方应用程序(如 VSCode、Teams)或对手注册的恶意第三方应用程序。
- 范围 – 定义应用程序请求的权限(例如,Mail.Read、offline_access)。.default 范围通常用于客户端凭证流以获取预先同意的权限。
- response_type=code – 表示流程正在请求授权码,该授权码稍后可以交换访问和/或刷新令牌。
- redirect_uri – 用户验证后,Entra ID 将发送响应的位置。如果攻击者控制此 URI,他们就会获得代码,或者它是有效的 MSFT 管理的 URI。
- login_hint – 指定目标用户(例如,alice @ tenant.onmicrosoft.com)。通常预先填充以降低网络钓鱼期间的摩擦。
注意:虽然此示例说明了常见的 Microsoft Entra ID OAuth 网络钓鱼 URL,但还存在许多变体。对手可能会根据其特定目标调整客户端 ID、范围、授予类型或重定向 URI 等参数,无论是获得持久访问权限、泄露电子邮件还是通过更广泛的同意授予来提升权限。
为什么这很重要?
由于这些参数是可定制的,因此对手可以轻松地交换值以适合他们的操作。例如:
- 他们可能会使用合法的 Microsoft 客户端 ID 与良性应用程序混合。
- 他们可能会使用 .default范围以绕过特定的同意提示。
- 他们会将redirect_uri指向他们控制的站点来收集授权码。
- 他们可以针对在侦察过程中可能已识别的特定用户主体。
- 他们可以根据运营需求调整目标资源的权限。
一旦目标通过身份验证,目标就很简单——获取授权码。然后,该代码会被交换(通常使用 ROADtools 之类的工具)为刷新令牌和/或访问令牌,从而使攻击者能够进行 Graph API 调用或转向其他 Microsoft 365 服务,所有这些都无需进一步的用户交互。
Volexity 主要发现摘要
对于威胁检测,了解 OAuth 等协议、Microsoft Entra ID 中的工作流实现以及有关对手在此操作中采取的行为和/或步骤的上下文元数据至关重要。
从 Volexity 的调查和研究中,我们可以输入报告的 OAuth 网络钓鱼的不同变体。为了便于理解,我们决定将其分解如下:
OAuth 网络钓鱼以 VSCode 客户端身份代表目标用户主体访问 Graph API :这些 URL 类似于我们的示例“什么定义 MSFT OAuth 网络钓鱼 URL”——最终目标是具有默认权限的 Graph API 访问令牌。
- OAuth 钓鱼 URL 是自定义的,指向“授权”端点
- 客户端 ID 是 VSCode(“aebc6443-996d-45c2-90f0-388ff96faa56”)
- 资源/范围是 MSFT Graph(“ https://graph.microsoft.com/.default ”)使用 .default权限
- 令牌授予流程是授权码(response_type=code)
- 重定向 URI 是针对合法的 MSFT 域名(insiders[.]vscode[.]dev或 vscode-redirect[.]azurewebsites[.]net)
- 登录提示是针对特定的用户主体(而不是服务主体)
- 攻击者要求目标打开 URL、进行身份验证并共享授权码(1.AXg….)
从这里,对手将能够向 MSFT 的 OAuth 令牌端点( https://login.microsoftonline.com/[tenant_id]/oauth2/v2.0/token )发出请求,并将刷新令牌交换为访问令牌。这足以让对手访问 Graph API 并访问用户通常可用的资源。这些指标对于我们稍后在本博客中提出的检测和搜索策略至关重要。
作为 MSFT Auth Broker 进行设备注册的 OAuth 网络钓鱼:这些 URL 是唯一的,因为它们与后续 ROADtools 使用链接在一起,以注册虚拟设备、将 RT 交换为 PRT,并需要 PRT 丰富才能通过 Graph API 和 Sharepoint 访问完成电子邮件访问。
- OAuth 网络钓鱼 URL 是自定义的,指向授权( https://login.microsoftonline.com/[tenant_id]/oauth2/v2.0/authorize )端点
- 客户端 ID 是 MSFT 身份验证代理(“29d9ed98-a469-4536-ade2-f981bc1d605e”)
- 资源/范围是设备注册服务(DRS)(“01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9”)
- 令牌授予流程是授权码(response_type=code)
- 重定向 URI 包括基于云的域加入端点(通常在 Windows 安装或 Autopilot 期间使用)
- 登录提示包含用户主要电子邮件地址(目标)
- 请求最终是为了 ADRS 令牌
如果用户被钓鱼并打开 URL,身份验证将提供一个 ADRS 令牌,这是攻击者注册设备并随后获取带有设备私钥和 PEM 文件的 PRT 所必需的。
Volexity 的博客还包含有关通过注册的设备 ID 跟踪入侵身份活动以及在确定已批准的 2FA 请求后的入侵后活动的附加信息,从而允许对手通过与新注册的设备绑定的会话下载目标的电子邮件。
通过对每次网络钓鱼尝试的了解,我们的下一个目标是尽可能准确地在我们自己的 MSFT 租户中复制这一点,以收集合理检测的数据。
我们的模拟努力
好的 - 到目前为止,我们已经介绍了 OAuth 的基础知识以及 Microsoft Entra ID 如何实现它。我们分解了 Microsoft OAuth 网络钓鱼 URL 的定义,解码了其关键参数,并从 Volexity 的出色调查中提取了关键见解,以识别与这些网络钓鱼工作流程一致的指标。
但理论和对 Volexity 笔记本的了解只能让我们走这么远。
为了真正了解攻击者的观点、完整的执行链、工具怪癖、微妙的陷阱和滥用的机会,我们决定亲自进行白盒测试。我们在自己的租户中重新创建了 OAuth 网络钓鱼流程,模拟了从令牌收集到资源访问的所有内容。目标是什么?超越静态指标,揭示防御者能够可靠检测到的行为线索。
让我们开始吧。
准备工作
首先,我们很乐意分享一些有关我们在 Azure 中的威胁研究和检测环境的详细信息。
- 已建立 Azure 租户:TENANT.onmicrosoft.com
- 已建立 Sharepoint 域:DOMAIN.sharepoint.com
- Native IdP Microsoft Entra ID – 启用我们的 IAM
- 适用于所有用户的 Microsoft 365 许可证 (P2)
- Azure 活动日志流式传输到 EventHub
- Microsoft Entra ID 登录日志流式传输至 EventHub
- Microsoft Entra ID 审计日志流式传输至 EventHub
- Microsoft Graph 审计日志流式传输至 EventHub
- Microsoft 365 审核日志流式传输至 EventHub
- Elastic Azure 与 M365 集成,支持从 EventHub 进行日志消化
- 启用需要 MFA 的 CAP 的基本管理员用户
- 移动设备上的 MSFT Authenticator 应用,用于模拟 2FA
- 带有 NordVPN(Adversary Box)的 Windows 10 桌面
- macOS 端点(受害者盒子)
请注意,虽然我们可以从单个端点遵循工作流程,但通常我们需要反映单独源地址的数据,以便开发人员检测不可能旅行的变化。
场景 1:VSCode 客户端的 OAuth 钓鱼
仿真
为了模拟 Volexity 记录的网络钓鱼技术,我们构建了一个 Python 脚本,使用 Microsoft Entra ID 生成 OAuth 2.0 授权 URL。该 URL 启动授权代码授予流程,模拟第一方 Visual Studio Code 应用来请求对 Microsoft Graph API 的委托访问权限。
我们使用以下参数配置了 URL:
{
"client_id": "aebc6443-996d-45c2-90f0-388ff96faa56",
"response_type": "code",
"redirect_uri": "insiders.vscode.dev/redirect",
"scope": "https://graph.microsoft.com/.default",
"login_hint": "user @ tenant.onmicrosoft.com",
"prompt": "select_account",
"state": "nothingtoseehere"
}
图 1:OAuth 钓鱼 URL 的参数
此 URL 与目标(在我们的例子中是 MacOS 测试用户)共享。打开后,它会对用户进行身份验证并完成 OAuth 工作流程。使用浏览器开发工具,我们捕获了重定向 URI 中返回的授权码,这正是攻击者要求受害者发回的内容。
收到代码后,我们发出 POST 请求至:
{token_url: "https://login.microsoftonline.com/organizations/oauth2/v2.0/token"}
此交换使用 authorization_code 授予类型,传递代码、客户端 ID 和重定向 URI。微软返回了访问令牌,但没有刷新令牌。你可能会问这是为什么?
范围https://graph.microsoft.com/.default指示 Microsoft 代表用户为已授予 VSCode 应用的所有 Graph 权限颁发承载令牌。这是一个静态范围,从应用程序注册中提取,它不包括像 Mail.Read 或 Offline_access 这样的动态范围。
微软的文档指出:
““客户端不能在单个请求中组合静态(.default)同意和动态同意。 ””
因此,尝试将 Offline_access 与.default一起包含导致错误。如果攻击者想要刷新令牌,他们必须避免使用.default而是明确请求offline_access和所需的委托范围(例如Mail.Read)——假设应用程序注册支持这些。
有了访问令牌,我们转向第二个脚本来与 Microsoft Graph API 进行交互。其目标就是像攻击者一样,从受害者的账户中提取电子邮件信息。
为此,我们将访问令牌作为 Bearer JWT 包含在授权标头中,并向以下端点发出 GET 请求:
{graph_url: "https://graph.microsoft.com/v1.0/me/messages"}
响应返回电子邮件对象的 JSON 数组。从这里开始,我们只需遍历结果并解析出有用的元数据,例如发件人、主题和接收时间。
为了测试令牌的更广泛的权限,我们还尝试使用以下方法枚举 SharePoint 网站:
{graph_search_url: "https://graph.microsoft.com/v1.0/sites?search=*"}
请求因访问被拒绝错误而失败——这引出了一个重要的问题:为什么电子邮件访问可以,而 SharePoint 访问却不可以?原因是第一方客户端(VSCode:aebc6443-996d-45c2-90f0-388ff96faa56)没有 Microsoft 预定义的 Graph for Sharepoint 默认委派权限。因此,我们知道对手能够访问的内容是有限的。
为了确保准确性,我们解码了访问令牌,以使用.default来识别与 VSCode 关联的 SCPGraph 的权限 – 正在验证无站点。 *已获得微软许可。
这是 Volexity 描述的变体之一,但确实有助于我们更多地了解对手幕后的过程 - 以及资源、OAuth 以及 Microsoft Entra ID 的更多内容。
模拟完成后,我们现在转向识别可用于 SIEM 检测和威胁搜寻的高保真信号。我们的重点是 Microsoft Entra ID 和 Microsoft Graph 日志中的行为可观察内容。
检测
信号 1 - 以 Visual Studio Code 客户端身份进行 Microsoft Entra ID OAuth 网络钓鱼
使用第一方 Microsoft 应用程序 Visual Studio Code (VSCode) 成功完成了 OAuth 2.0(授权)和 OpenID Connect(身份验证)流程。登录以钓鱼用户主体的名义进行,从而导致使用.default委托访问 Microsoft Graph权限。
event.dataset: "azure.signinlogs" and
event.action: "Sign-in activity" and
event.outcome: "success" and
azure.signinlogs.properties.user_type: "Member" and
azure.signinlogs.properties.authentication_processing_details: *Oauth* and
azure.signinlogs.category: "NonInteractiveUserSignInLogs" and
(
azure.signinlogs.properties.resource_display_name: "Microsoft Graph" or
azure.signinlogs.properties.resource_id: "00000003-0000-0000-c000-000000000000"
) and (
azure.signinlogs.properties.app_id: "aebc6443-996d-45c2-90f0-388ff96faa56" or
azure.signinlogs.properties.app_display_name: "Visual Studio Code"
)
信号 2 - Microsoft Entra 会话重用与可疑图表访问
虽然 KQL 等传统查询语言非常适合过滤和可视化单个日志事件,但当检测依赖于跨数据集、时间和标识符关联多个记录时,它们就会遇到困难。这就是 ES|QL(Elasticsearch 查询语言)变得至关重要的地方。如果不编写多个不连贯的查询并在事后手动关联它们,那么这些类型的多事件关联、时间逻辑和字段规范化在基于静态过滤器的查询语言(如 KQL)中是困难的或完全不可能的。
此检测依赖于关联发生时间相近但来自不同数据源的多个事件,即登录日志和 Microsoft Graph 活动。目标是找到跨多个 IP 的相同会话 ID 的可疑重用,这可能表明会话劫持或令牌滥用。为了节省本出版物的空间,您可以在“检测规则”部分查看实际的检测规则。为了更好的说明查询的流程和含义,下面是一个更高层次的图表来说明。
[ FROM logs-azure.* ]
|
| ← Pulls events from all relevant Microsoft Cloud datasets:
| - azure.signinlogs (authentication)
| - azure.graphactivitylogs (resource access)
↓
[ WHERE session_id IS NOT NULL AND IP NOT MICROSOFT ASN ]
|
| ← Filters out Microsoft-owned infrastructure (e.g., internal proxy,
| Graph API relays) using ASN checks.
| ← Ensures session ID exists so events can be correlated together.
↓
[ EVAL session_id, event_type, time_window, etc. ]
|
| ← Normalizes key fields across datasets:
| - session_id (from signin or Graph)
| - user ID, app ID, event type ("signin" or "graph")
| ← Buckets events into 5-minute windows using DATE_TRUNC()
↓
[ KEEP selected fields ]
|
| ← Retains only what's needed:
| session_id, timestamp, IP, user, client ID, etc.
↓
[ STATS BY session_id + time_window ]
|
| ← Groups by session and time window to compute:
| - unique IPs used
| - apps involved
| - first and last timestamps
| - whether both signin and graph occurred
↓
[ EVAL time_diff + signin_to_graph_delay ]
|
| ← Calculates:
| - time_diff: full session duration
| - delay: gap between signin and Graph access
↓
[ WHERE types_count > 1 AND unique_ips > 1 AND delay <= 5 ]
|
| ← Flags sessions where:
| - multiple event types (signin + graph)
| - multiple IPs used
| - all occurred within 5 minutes
↓
[ Output = Suspicious Session Reuse Detected ]
信号 3 - Microsoft Entra ID 并发登录存在可疑属性
此检测可识别 Microsoft Entra ID 中的可疑登录,其中用户使用无需 MFA 的设备代码流进行身份验证或使用 VSCode 客户端登录。当同一身份在短时间内使用任一方法从两个或多个不同的 IP 登录时,可能表示存在令牌重放、OAuth 网络钓鱼或中间人 (AitM) 活动。
[ FROM logs-azure.signinlogs* ]
|
| ← Pulls only Microsoft Entra ID sign-in logs
↓
[ WHERE @timestamp > NOW() - 1h AND event.outcome == "success" ]
|
| ← Filters to the last hour and keeps only successful sign-ins
↓
[ WHERE source.ip IS NOT NULL AND identity IS NOT NULL ]
|
| ← Ensures the sign-in is tied to a user and IP for correlation
↓
[ KEEP fields: identity, app_id, auth_protocol, IP, etc. ]
|
| ← Retains app/client, IP, auth method, and resource info
↓
[ EVAL detection flags ]
|
| ← Labels events as:
| - device_code: if MFA not required
| - visual_studio: if VS Code client used
| - other: everything else
↓
[ STATS BY identity ]
|
| ← Aggregates all sign-ins per user, calculates:
| - IP count
| - Device Code or VSCode usage
| - App/client/resource details
↓
[ WHERE src_ip >= 2 AND (device_code_count > 0 OR vsc > 0) ]
|
| ← Flags users with:
| - Sign-ins from multiple IPs
| - And either:
| - Device Code w/o MFA
| - Visual Studio Code app
↓
[ Output = Potential OAuth Phishing or Token Misuse ]
虽然这种 OAuth 网络钓鱼变体缺乏刷新令牌或 PRT 所提供的完全持久性,但它仍然为对手提供了通过合法渠道对敏感用户数据(例如电子邮件)进行一次性访问的宝贵机会。这个练习帮助我们理解静态.default的局限性和功能范围、应用程序注册的影响以及 Microsoft Graph 如何在后期身份验证中发挥关键作用。它还强化了一个更广泛的教训:并非所有 OAuth 网络钓鱼攻击都是一样的。一些旨在通过刷新令牌或设备注册实现长寿(我们稍后会看到),而另一些则专注于通过第一方客户端立即窃取数据。理解细微差别对于准确的检测逻辑至关重要。
场景2:利用OAuth钓鱼手段进行设备注册
正如我们之前所说,Volexity 还报告了一个针对受害者的单独网络钓鱼剧本,这次的目标是注册虚拟设备并获取 PRT。虽然这种方法需要对手采取更多步骤,但回报是授予令牌的令牌,为完成他们的操作提供了更多的实用性。对于我们的模拟工作,我们需要扩展我们的工具集并依赖 ROADtools,就像对手一样保持准确性,但是,还制作了其他几个 Python 脚本用于初始网络钓鱼和后期入侵行动。
仿真
从最初的网络钓鱼开始,我们调整了 Python 脚本来制作一个不同的 OAuth URL 发送给受害者。这次,重点是我们的第一方客户端 ID 是 Microsoft 身份验证代理,请求带有Offline_access的刷新令牌并重定向到 Entra ID 的云域设备加入端点 URI。
{
"client_id": "29d9ed98-a469-4536-ade2-f981bc1d605e",
"response_type": "code",
"response_mode": "query",
"redirect_uri": "https://login.microsoftonline.com/WebApp/CloudDomainJoin/8",
"resource": "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9",
"state": "nothingtoseehere"
}
如果成功并且我们的受害者通过了身份验证,OAuth 工作流程将完成,并且用户将被重定向到指定的 URI,并在查询参数中附加授权码。再次强调,此代码是关键部分,必须与对手共享才能将其交换为令牌。在我们的案例中,一旦打开网络钓鱼 URL 并且目标进行身份验证,我们就会捕获重定向中嵌入的授权代码,并使用它从 Microsoft Entra ID 令牌端点请求令牌。
现在,事情变得有趣起来。作为对令牌请求的响应,我们收到三种类型的令牌:访问令牌、刷新令牌和 ID 令牌。您可能会问——为什么我们获得的不仅仅是一个访问令牌?答案在于我们最初请求的范围: openid 、 offline_access和profile 。
- openid授予我们一个 ID 令牌,它是 OpenID Connect 层的一部分,用于确认用户的身份——这是您的身份验证 (authN) 工件。
- Offline_access提供了一个刷新令牌,使我们能够维护会话并请求新的访问令牌而无需重新进行身份验证,这支持持久访问,但对于我们使用 ROADtx 至关重要。
- 而访问令牌本身用于授权对受保护的 API (如 Microsoft Graph)的请求,这代表授权(authZ)。
有了这三个令牌,我们就有了一切:身份验证、授权和长期会话连续性。这足以从简单的 OAuth 网络钓鱼游戏转变为更持久的立足点——就像在 Microsoft Entra ID 中注册新设备一样。
现在让我们把这些点连接起来。PRT 需要注册一个有效的设备,Entra ID 可以通过设备证书和私钥识别该设备。这就是 ROADtx 发挥作用的地方。因为我们最初的 OAuth 网络钓鱼模拟了加入的设备流,并且使用的客户端是 Microsoft 身份验证代理(与设备注册服务交互的第一方客户端),所以我们已经拥有正确的访问令牌来与 DRS 交互。请注意,在我们返回的对象中,范围是adrs_access ,它表示 Azure DRS 访问权限,对于以后的检测很重要。
从这里,我们只需将从令牌交换收到的 JSON 对象放入.roadtool_auth文件。该文件由 ROADtools 原生使用,它使用存储的令牌执行设备注册,完成对手的持久化转移并为获取有效的 PRT 奠定基础。
获取令牌后,我们通过重新格式化 JSON 为 ROADtx 做准备。ROADtx 要求密钥采用驼峰式命名法,并且我们还必须将 Microsoft 身份验证代理的客户端 ID 作为_clientId包含在内。此设置允许我们运行refreshtokento命令,该命令获取我们的刷新令牌并将其交换为范围为 DRS 的新 JWT — — 具体来说,是服务主体urn:ms-drs:enterpriseregistration.windows.net 。
一旦完成,我们就使用设备命令来模拟新的设备注册。此操作不需要任何实际的虚拟机或物理主机,因为它是一个后端注册,只需在 Entra ID 中创建一个条目。成功后,我们会获得有效的设备 ID、PEM 编码证书和私钥 - 所有这些都是在 Microsoft 生态系统中模拟有效混合加入设备所必需的。
建立设备身份后,我们调用prt命令。它使用刷新令牌、设备证书和私钥来创建新的 PRT——一种高度特权的凭证,可以有效地将用户和设备对 Microsoft Entra ID 的信任联系在一起。
就这样——哇啦!— 我们有一个 PRT。
但为什么要经历这一切呢?当我们已经有访问令牌、ID 令牌和刷新令牌时,为什么还要注册设备、生成证书并获取 PRT?
因为 PRT 是完整用户和设备身份模拟的关键。可以将其视为 Entra ID 世界中类似 Kerberos 的票证授予令牌,但实际上它是令牌授予令牌。使用有效的 PRT:
- 对手无需用户交互即可请求 Outlook、SharePoint 或 Teams 等第一方应用的新访问权限和 ID 令牌。
- PRT 支持跨多个服务的无缝单点登录 SSO,绕过通常会重新提示用户的 MFA 和其他条件访问策略 (CAP)。这对于持久性至关重要,因为 CAP 和 MFA 通常会给对手带来巨大的障碍。
- 它支持长期持久性,因为只要设备身份仍然受信任,PRT 就可以在会话之间静默更新和利用。
也许最危险的是 - PRT 允许对手模仿完全兼容、加入域的设备和用户组合,从而有效地绕过大多数常规检测和响应控制,使得猎人和分析师无法区分良性和可疑性。
这使得 PRT 成为一项极其宝贵的资产,能够实现隐蔽的横向移动、权限提升以及对 Microsoft 365 服务的深度访问。这不再仅仅是入侵的关键,而是要保持不被发现。
我们不要忘记妥协后的活动……
ROADtx 提供了一些对手经常使用的强大命令 - prtenrich和browserprtauth 。例如,我们可以通过提供包含身份验证和授权所需元数据的 PRT 来访问 Microsoft 套件中的大多数基于浏览器的 UI 服务 - 这些元数据最初属于我们的网络钓鱼受害者(我),但实际上是代表他们行事的 Microsoft 身份验证代理。
Volexity 还报告称,在设备注册和 PRT 获取之后,系统会向初始受害者发送 2FA 请求,该请求获得批准后,便可用于通过 SharePoint 访问电子邮件。虽然他们没有具体说明请求是如何提出的,但可以合理地假设对手使用 PRT 通过第一方 Microsoft 客户端进行身份验证,而实际的数据访问是通过 Microsoft Graph 进行的。Graph 在受到攻击后仍然是一个热门目标,因为它是大多数 Microsoft 365 资源的中央 API 枢纽。
首先,让我们利用 ROADtx 对我们的 PRT 进行身份验证,其中 Microsoft Teams 是我们的客户端,Microsoft Graph 是我们的资源。当将prtauth命令与我们的 PRT 一起使用时,我们能够获得新的访问令牌和刷新令牌 - 清楚地证明了 PRT 作为 Microsoft 身份结构中的令牌授予令牌的实用性。
一旦获得访问令牌,我们就会将其插入自定义 Python 脚本中,开始枚举我们的 SharePoint 站点、驱动器、项目,这使我们能够识别感兴趣的文件并下载其内容。
通过这种模拟,我们展示了对手如何将 OAuth 网络钓鱼与 Microsoft 身份验证代理链接起来并获取必要的凭证材料以利用 ROADtx 获取 PRT。这个 PRT 是一个重要的实用工具,可以在攻击后访问敏感文件、枚举租户资源等等。
现在,让我们转移焦点:检测这种活动的合理且准确的信号是什么?
检测
信号 1 - 冒充 Microsoft 身份验证代理的 Microsoft Entra ID OAuth 网络钓鱼
识别用户主体使用 Microsoft 身份验证代理 (MAB) 作为客户端、使用设备注册服务 (DRS) 作为目标资源启动 OAuth 授权代码流的实例。此检测主要针对在短时间内在两个或多个不同的 IP 地址上重复使用单个会话 ID 的情况,并且至少一个请求来自浏览器 - 这种行为通常与网络钓鱼有关。
[ FROM logs-azure.signinlogs-* ]
|
| ← Pulls all Microsoft Entra ID sign-in logs
↓
[ WHERE app_id == MAB AND resource_id == DRS ]
|
| ← Filters to OAuth auth code requests targeting
| Microsoft Authentication Broker + Device Reg Service
↓
[ EVAL session_id + is_browser ]
|
| ← Extracts session ID and flags browser-based activity
↓
[ STATS BY 30-minute window, user, session_id ]
|
| ← Groups logins within same session and time window,
| then aggregates:
| - user/session/token identifiers
| - distinct IPs and geo info
| - user agent, browser presence
| - app/resource/client info
↓
[ WHERE ip_count ≥ 2 AND session_id_count == 1 ]
|
| ← Identifies reuse of a single session ID
| across ≥ 2 different IP addresses
↓
[ AND has_browser ≥ 1 AND auth_count ≥ 2 ]
|
| ← Requires at least one browser-based request
| and at least two total sign-in events
↓
[ Output = Suspicious OAuth Flow with Auth Broker for DRS ]
信号 2 - Microsoft Auth Broker 发出的可疑 ADRS 令牌请求
识别 Microsoft Entra ID 登录事件,其中用户主体使用颁发给 Microsoft 身份验证代理 (MAB) 客户端的刷新令牌进行身份验证,以adrs_access OAuth 范围为目标的设备注册服务 (DRS)。此模式可能表示在初始授权码网络钓鱼或设备注册流程之后基于令牌访问 DRS。
event.dataset: "azure.signinlogs" and azure.signinlogs.properties.app_id : "29d9ed98-a469-4536-ade2-f981bc1d605e" and azure.signinlogs.properties.resource_id : "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9" and azure.signinlogs.properties.authentication_processing_details.`Oauth Scope Info`: *adrs_access* and azure.signinlogs.properties.incoming_token_type: "refreshToken" and azure.signinlogs.properties.user_type: "Member"
信号 3 - Entra ID 中的设备注册异常
检测一系列 Entra ID 审计日志事件,表明使用刷新令牌的潜在恶意设备注册活动,通常在 OAuth 网络钓鱼后出现。此模式模仿 ROADtx 等工具的行为,其中新注册的 Windows 设备(具有硬编码的操作系统版本 10.0.19041.928)由设备注册服务添加,然后分配用户和所有者。所有事件必须共享相同的关联 ID 并在一分钟内发生,强烈暗示自动化或脚本驱动的注册而不是合法的用户行为。
sequence by azure.correlation_id with maxspan=1m
[any where event.dataset == "azure.auditlogs" and azure.auditlogs.identity == "Device Registration Service" and azure.auditlogs.operation_name == "Add device" and azure.auditlogs.properties.additional_details.value like "Microsoft.OData.Client/*" and (
azure.auditlogs.properties.target_resources.`0`.modified_properties.`1`.display_name == "CloudAccountEnabled" and
azure.auditlogs.properties.target_resources.`0`.modified_properties.`1`.new_value: "[true]") and azure.auditlogs.properties.target_resources.`0`.modified_properties.`3`.new_value like "*10.0.19041.928*"]
[any where event.dataset == "azure.auditlogs" and azure.auditlogs.operation_name == "Add registered users to device" and azure.auditlogs.properties.target_resources.`0`.modified_properties.`2`.new_value like "*urn:ms-drs:enterpriseregistration.windows.net*"]
[any where event.dataset == "azure.auditlogs" and azure.auditlogs.operation_name == "Add registered owner to device"]
信号 3 - 从同一用户和设备将 Entra ID RT 转换为 PRT
此检测可识别 Microsoft Entra ID 用户何时首次使用颁发给 Microsoft 身份验证代理 (MAB) 的刷新令牌进行身份验证,随后不久使用来自同一设备的主刷新令牌 (PRT)。这种序列在正常用户行为中很少见,可能表明对手已成功注册设备并使用 ROADtx 等工具升级为持续访问。通过在第二步中过滤掉与设备注册服务 (DRS) 相关的活动,该规则重点关注 PRT 的注册后使用情况,以访问其他 Microsoft 365 服务。
这种行为强烈表明了基于令牌的妥协和长期会话模拟,特别是在设备信任悄悄建立时。捕捉从刷新令牌到 PRT 的这种转变对于发现 OAuth 网络钓鱼和入侵后持久性的高保真信号至关重要。
sequence by azure.signinlogs.properties.user_id, azure.signinlogs.properties.device_detail.device_id with maxspan=1d
[authentication where
event.dataset == "azure.signinlogs" and
azure.signinlogs.category == "NonInteractiveUserSignInLogs" and
azure.signinlogs.properties.app_id == "29d9ed98-a469-4536-ade2-f981bc1d605e" and
azure.signinlogs.properties.incoming_token_type == "refreshToken" and
azure.signinlogs.properties.device_detail.trust_type == "Azure AD joined" and
azure.signinlogs.properties.device_detail.device_id != null and
azure.signinlogs.properties.token_protection_status_details.sign_in_session_status == "unbound" and
azure.signinlogs.properties.user_type == "Member" and
azure.signinlogs.result_signature == "SUCCESS"
]
[authentication where
event.dataset == "azure.signinlogs" and
azure.signinlogs.properties.incoming_token_type == "primaryRefreshToken" and
azure.signinlogs.properties.resource_display_name != "Device Registration Service" and
azure.signinlogs.result_signature == "SUCCESS"
]
信号 4 - 异常的 PRT 使用和用户主体的注册设备
当 Microsoft Entra ID 用户注册过去 7 天内未见过的新设备时,就会出现此检测 - 行为通常与链接到基于 ROADtx 的设备注册的 OAuth 网络钓鱼活动有关。在这些攻击中,攻击者诱骗用户授权针对 DRS 的 Microsoft 身份验证代理 (MAB) 访问,获取 RT,然后使用 ROADtx 悄悄注册一个虚假的 Windows 设备并创建 PRT。当用户主体从新观察到的设备 ID 进行身份验证时,此规则会发出警报,特别是当会话未绑定时,这是令牌重放或设备欺骗的特征。由于 PRT 需要注册且受信任的设备,因此该信号在识别对手何时从基本令牌滥用转变为与长期妥协相一致的持续、隐秘访问方面起着关键作用。
event.dataset: "azure.signinlogs" and
event.category: "authentication" and
azure.signinlogs.properties.user_type: "Member" and
azure.signinlogs.properties.token_protection_status_details.sign_in_session_status: "unbound" and
not azure.signinlogs.properties.device_detail.device_id: "" and
azure.signinlogs.properties.user_principal_name: *
新条款价值观:
- azure.signinlogs.properties.用户主体名称
- azure.signinlogs.properties.device_detail.device_id
这种模拟帮助我们验证了完整的攻击者工作流程——从网络钓鱼获取同意到建立设备信任以及创建 PRT 以实现长期持久性。通过将 OAuth 滥用与设备注册结合起来,对手可以满足 CAP、冒充合规端点并在云环境中横向移动——通常不会触发传统的安全控制。
这些细微差别很重要。单独来看,诸如令牌发行或设备注册之类的个别事件可能看起来是良性的。但是,当登录日志、审计数据和令牌元数据相互关联时,它们就会暴露出明显的身份泄露痕迹。
检测和滥用的关键遥测细节
在我们的整个模拟和检测工作中,特定的遥测工件始终被证明对于区分良性 OAuth 活动与恶意滥用至关重要。了解这些字段在 Microsoft Entra ID 日志中的显示方式以及攻击者如何操纵它们对于有效的搜寻和检测工程至关重要。从客户端 ID 和授权类型到设备合规性、令牌类型和条件访问结果,这些信号讲述了基于身份的攻击的故事。下面我们列出了最重要的内容以及它们如何帮助我们。
客户端应用程序 ID (client_id) :标识发起 OAuth 请求的应用程序。第一方客户端(例如VSCode、Auth Broker 等工具可能会被滥用来混淆。第三方客户端可能是恶意的或未经审查的——通常代表同意授予攻击。主要用于识别有风险或意外的应用程序使用情况。
目标资源 (resource_id / resource_display_name) :定义正在访问的 MSFT 服务(例如MSFT Graph 或 Teams)。高价值目标包括 - Graph API、SharePoint、Outlook、Teams 和目录服务。资源目标通常由攻击者的目标决定。
主体类型(user_type) :指示登录是由成员(用户)还是服务主体进行的。网络钓鱼活动几乎总是针对会员账户。这使得检测逻辑中的过滤变得容易,但有助于代表用户主体配对不寻常的第一方客户端请求。
OAuth 授权类型(authentication_processing_details) :了解令牌如何获取的关键——授权码、刷新令牌、设备代码、客户端凭据等。而刷新令牌和设备代码重用是事后泄露的高保真信号。
地理位置:使我们能够识别非典型登录(例如罕见的国家)或不可能的旅行(同一用户在短时间内从遥远的地方出发)。结合会话 ID 和关联 ID,这些可以揭示令牌劫持、身份泄露或横向移动。
设备元数据(device_detail、trust_type、compliance_state) :包括设备 ID、操作系统、信任类型、合规性、管理状态等。设备注册和 PRT 颁发与此元数据相关。对手的目标通常是满足 CAP 并获得持久的可信访问。
身份验证协议和类型(authentication_protocol / incoming_token_type) :显示会话是否基于 OAuth 或是否使用了 MFA。传入的令牌源是用于此请求的提供 authN 或 authZ 的令牌源。对于检测令牌重用、非交互式登录很有用。
身份验证材料和会话上下文:可以通过传入的令牌类型、令牌保护状态和会话 ID 推断所使用的令牌。会话重用、长会话持续时间或多个 IP 绑定到单个会话通常表明存在滥用。
条件访问策略状态:在令牌发行期间进行评估 - 但它严重影响是否授予访问权限。这有助于识别 CAP 逃避行为、意外的政策结果或可能影响风险的因素。
范围和同意行为:请求的范围出现在登录日志中捕获的 SCP 或 OAuth 参数中。滥用指标包括offline_access 、 .default 、或者像Mail.ReadWrite这样的广泛范围。如果用户批准了可疑的应用程序,同意遥测可以帮助转变或关联。
结论
Microsoft Entra ID 的 OAuth 实现是一把双刃剑:它实现了强大、无缝的身份验证体验,但也为对手利用信任、会话持久性和设备注册攻击路径提供了新的机会。
通过复制 Volexity 观察到的 OAuth 网络钓鱼技术,我们的团队能够验证攻击者如何滥用合法的 Microsoft 应用程序、令牌流和开源工具来秘密访问敏感数据。我们通过实际模拟扩展了这项工作,深入研究了 OAuth 网络钓鱼和工作流、安全令牌元数据和获取的机制,帮助发现防御者可以检测到的行为指标。
我们的发现强化了一个关键点:OAuth 滥用并不依赖于恶意软件或代码执行。它将身份、同意和令牌重用武器化——使传统的安全控制成为一项挑战——以及为什么基于日志的检测、关联和行为分析如此重要。
我们希望这里分享的仿真工件、检测规则和经验教训能够帮助整个社区的防御者更好地理解并检测/搜寻这种不断演变的基于云的身份威胁。
如果您正在使用 Elastic,我们已经开源了本博客中讨论的所有检测规则,以帮助您入门。如果您正在另一个 SIEM 中搜索,我们鼓励您调整逻辑并相应地适应您的环境。
身份是新的边界——现在是我们这样对待它的时候了。保持安全并快乐狩猎!
检测规则
- 通过可疑图形访问重用 Microsoft Entra ID 会话
- 通过 Visual Studio Code 客户端进行 Microsoft Entra ID OAuth 网络钓鱼
- 通过 Auth Broker 到 DRS 的可疑 Microsoft OAuth 流程
- Microsoft Auth Broker 发出的可疑 ADRS 令牌请求
- Entra ID 中的异常设备注册
- 从同一用户和设备将 Entra ID RT 转换为 PRT
- 用户主体注册的设备异常
参考:
- https://www.volexity.com/blog/2025/04/22/phishing-for-codes-russian-threat-actors-target-microsoft-365-oauth-workflows/
- https://dirkjanm.io/abusing-azure-ad-sso-with-the-primary-refresh-token/
- https://posts.specterops.io/requesting-azure-ad-request-tokens-on-azure-ad-joined-machines-for-browser-sso-2b0409caad30
- https://learn.microsoft.com/en-us/entra/identity/devices/concept-primary-refresh-token
- https://learn.microsoft.com/en-us/entra/identity-platform/refresh-tokens
- https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow
- https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc#the-default-scope