在 Graph 日志中检测 Entra 攻击工具:基于 Sentinel 的 ROADtools / AADInternals / AzureHound 猎捕实战
【免费下载链接】Anthropic-Cybersecurity-Skills817 structured cybersecurity skills for AI agents · Mapped to 6 frameworks: MITRE ATT&CK, NIST CSF 2.0, MITRE ATLAS, D3FEND, NIST AI RMF & MITRE F3 (Fight Fraud) · agentskills.io standard · Works with Claude Code, GitHub Copilot, Codex CLI, Cursor, Gemini CLI & 20+ platforms · 29 security domains · Apache 2.0项目地址: https://gitcode.com/GitHub_Trending/an/Anthropic-Cybersecurity-Skills
这篇技术指南聚焦于本项目 detecting-entra-offensive-tools-in-graph-logs 技能所覆盖的核心议题:利用 Microsoft Sentinel / Log Analytics 中的AADGraphActivityLogs与MicrosoftGraphActivityLogs两张目录 API 活动表,通过 KQL 对 ROADtools(roadrecon)、AADInternals、AzureHound 等进攻性 Entra ID 枚举工具进行指纹识别与行为猎捕。读完本文,你将掌握三类检测手段——User-Agent 字符串指纹、可抵御 UA 伪造的端点扫描(endpoint-sweep)行为指纹、以及高量枚举离群点分析,并能够把可疑 Graph 调用关联回原始登录会话,最终将高保真查询固化为 Sentinel 计划性分析规则。
背景:AAD Graph 的十年盲区与两张新表
过去近十年,传统Azure AD Graph API(graph.windows.net)一直是防御方的盲区:对它的请求不会产生一级活动日志。ROADtools(roadrecon)与 AADInternals 这类重度依赖 AAD Graph 的工具,可以在几乎不留痕的情况下枚举整个租户。这一局面随着微软正式发布AADGraphActivityLogs(本项目文档注明其 2026 年达到正式可用 GA 状态)而改变——它是对已可用的MicrosoftGraphActivityLogs(graph.microsoft.com)的补充。
两张表共同为 SOC 提供目录 API 流量的请求级可见性,包括:调用者身份、应用、源 IP、HTTP 方法、请求 URI,以及关键的User-Agent。这些检测既能暴露正在进行的入侵,也能反过来验证配套红队技能中的进攻技术在遥测上是可观测的——本项目仓库中同时收录了 attacking-entra-id-with-roadtools、auditing-entra-id-with-aadinternals 与 exploiting-active-directory-with-bloodhound 等进攻/审计技能,本技能正是它们的防御性对照面。
工具为什么会留下指纹
- ROADtools构建于 Python 的
aiohttp之上,未伪装时 User-Agent 形如Python/3.12 aiohttp/3.10.4; - AADInternals常在 User-Agent 中遗留
AADInternals或库字符串; - AzureHound使用 Go HTTP 客户端,agent 特征明显。
即使攻击者伪造了 User-Agent,工具仍会通过特征性的端点扫描模式暴露自己:roadrecon gather会在极短时间窗内拉取users、groups、applications、serviceprincipals、devices、directoryroles、roledefinitions、oauth2permissiongrants等大量目录资源——这种特征在头字段伪装下依然存活。
何时使用本技能
- 构建或调优覆盖 Entra ID 的 Microsoft Sentinel / Log Analytics 检测;
- 在疑似凭证失窃、设备代码钓鱼(device-code phishing)或 OAuth 同意滥用后进行威胁猎捕;
- 紫队演练中验证 ROADtools / AADInternals / AzureHound 的活动是否可被检测;
- 调查告警时需要把 Graph API 调用关联回某个登录/会话;
- 启用 AADGraphActivityLogs 后,收口传统 Azure AD Graph 的可见性缺口。
前置条件
- 一个正在摄入以下数据的 Microsoft Sentinel 工作区(或 Log Analytics):
- MicrosoftGraphActivityLogs(Microsoft Entra ID →
graph.microsoft.com的诊断设置); - AADGraphActivityLogs(Microsoft Entra ID → 传统 Azure AD Graph 的诊断设置);
- MicrosoftGraphActivityLogs(Microsoft Entra ID →
- 用于关联的
SigninLogs与AADNonInteractiveUserSignInLogs; - 运行 KQL 所需的 Microsoft Sentinel Reader/Responder(或 Log Analytics Reader)RBAC 权限;
- 熟悉 Kusto Query Language(KQL)。
启用诊断设置
通过 Azure 门户(Azure Portal → Microsoft Entra ID → Diagnostic settings → 将MicrosoftGraphActivityLogs与AADGraphActivityLogs发送到工作区),或通过 CLI 启用:
az monitor diagnostic-settings create \ --name "entra-graph-logs" \ --resource "/providers/microsoft.aadiam/diagnosticSettings" \ --logs '[{"category":"MicrosoftGraphActivityLogs","enabled":true},{"category":"AADGraphActivityLogs","enabled":true}]' \ --workspace "<log-analytics-workspace-id>"两张活动表的 Schema 与关联键
api-reference.md 对两张表的字段做了完整梳理,是编写查询时的速查手册:
| 表 | 覆盖范围 | 关键字段 |
|---|---|---|
MicrosoftGraphActivityLogs | graph.microsoft.com请求 | RequestUri、RequestMethod、UserAgent、UserId、AppId、IPAddress/CallerIpAddress、ResponseStatusCode、SignInActivityId、TimeGenerated |
AADGraphActivityLogs | 传统graph.windows.net请求 | RequestUri、RequestMethod、UserAgent、UserId、AppId、CallerIpAddress、SignInActivityId、TimeGenerated |
跨源关联键
| 字段 | 关联目标 | 注意事项 |
|---|---|---|
SignInActivityId | SigninLogs.UniqueTokenIdentifier | AADGraphActivityLogs 中的值可能带==填充,关联前需要修剪 |
SessionId | SigninLogs / MicrosoftGraphActivityLogs / Unified Audit Log | 跨源会话关联 |
UserId | SigninLogs.UserId | 账户实体 |
AppId | SigninLogs.AppId | 调用应用 |
这里最容易踩的坑是SignInActivityId的==填充——下文 Step 6 的 KQL 会演示如何用trim_end("=", ...)处理后再 JOIN。
标准与框架映射
本技能按 standards.md 映射到两套框架:
NIST CSF 2.0
| ID | 名称 | 在本技能中的体现 |
|---|---|---|
| DE.CM-09 | 对计算硬件、软件、运行时环境及其数据进行监控以发现潜在不利事件 | 持续猎捕两张 Graph 活动表,监控目录 API 使用以发现攻击工具活动 |
MITRE ATT&CK
| ID | 名称 | 在本技能中的体现 |
|---|---|---|
| T1078.004 | Valid Accounts: Cloud Accounts | 核心技术:攻击者使用有效云凭证/令牌查询 Graph API,本技能的猎捕即检测这类使用 |
| T1087.004 | Account Discovery: Cloud Account | 被检测到的枚举扫描会枚举云账户 |
| T1069.003 | Permission Groups Discovery: Cloud Groups | 扫描会枚举云组与角色 |
| T1526 | Cloud Service Discovery | 广泛的目录枚举即云服务发现 |
其中核心检测目标对应T1078.004:攻击者使用合法(常为钓鱼所得或令牌被盗)云凭证,经由 Graph API 对租户进行枚举与操作。
猎捕工作流:7 个步骤
Step 1:确认两张表均已摄入
正式猎捕前,先确认数据存在,并检查你将作为枢纽使用的 schema 字段:
union withsource=Tbl MicrosoftGraphActivityLogs, AADGraphActivityLogs | where TimeGenerated > ago(1d) | summarize Records=count(), LastSeen=max(TimeGenerated) by Tbl预期看到两张表都有记录。若AADGraphActivityLogs为空,说明诊断设置未生效或 AAD Graph 流量本就稀疏,需要回到前置条件检查配置。
Step 2:猎捕 User-Agent 指纹(ROADtools / aiohttp)
ROADtools 使用aiohttp;未伪装的运行会在 User-Agent 中同时出现python与aiohttp:
AADGraphActivityLogs | where TimeGenerated > ago(7d) | where RequestMethod == "GET" | where UserAgent contains "python" and UserAgent contains "aiohttp" | summarize RequestCount = count() by CallerIpAddress, AppId, UserAgent, UserId | sort by RequestCount desc按调用方 IP、AppId、UserAgent、UserId 聚合请求数并降序排列,可快速定位哪个身份以该 UA 发起了多少请求。
Step 3:猎捕 AADInternals 与 AzureHound 的 Agent
AADInternals 会遗留工具/库字符串;AzureHound 的 Go HTTP 客户端与 BloodHound 工具链 agent 特征明显:
union MicrosoftGraphActivityLogs, AADGraphActivityLogs | where TimeGenerated > ago(7d) | where UserAgent has_any ("AADInternals", "aad-internals", "azurehound", "BloodHound", "python-requests", "Go-http-client") | project TimeGenerated, UserAgent, CallerIpAddress, AppId, UserId, RequestUri | sort by TimeGenerated desc注意这里用了has_any(子串匹配,性能优于contains组合),并覆盖了两张表,避免漏掉走 AAD Graph 的 AADInternals 与走 MS Graph 的 AzureHound。
Step 4:行为猎捕——roadrecon 端点扫描(抗 UA 伪造)
即使伪造了 User-Agent,roadrecon gather也会在短时间内触达一组可识别的目录资源。按用户与 5 分钟时间窗分桶,当同一身份命中完整扫描集合时告警:
AADGraphActivityLogs | where TimeGenerated > ago(1d) | where RequestMethod == "GET" | extend TopLevelResource = tolower(tostring(split(split(RequestUri, "?")[0], "/")[3])) | summarize TopLevelResources = make_set(TopLevelResource), AppIds = make_set(AppId), CallerIPs = make_set(CallerIpAddress), UserAgents = make_set(UserAgent), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserId, bin(TimeGenerated, 5m) | where TopLevelResources has_all ("users", "tenantdetails", "groups", "applications", "serviceprincipals", "devices", "directoryroles", "roledefinitions", "contacts", "oauth2permissiongrants", "authorizationpolicy") | project StartTime, EndTime, UserId, AppIds, CallerIPs, UserAgents这条查询的技术要点在于TopLevelResource的提取:split(RequestUri, "?")[0]去掉查询参数,再按/切分取索引[3]得到顶层资源名,从而把形形色色的目录 URI 归一到可比较的集合。has_all要求 5 分钟窗口内同一UserId完整命中 11 类目录资源的 GET 请求——这是roadrecon gather的标志性行为,正常人工操作几乎不可能在如此短窗口内覆盖全部资源。
Step 5:高量枚举离群点
捕获那些只是短时间内发起了远超人类正常频率目录读取的工具:
MicrosoftGraphActivityLogs | where TimeGenerated > ago(1d) | where RequestMethod == "GET" | where RequestUri has_any ("/users", "/groups", "/servicePrincipals", "/applications", "/directoryRoles", "/roleManagement") | summarize Reads=count(), Resources=dcount(RequestUri) by UserId, AppId, CallerIpAddress, bin(TimeGenerated, 10m) | where Reads > 200 | sort by Reads desc10 分钟窗口内同一身份对目录资源的 GET 读取超过 200 次即视为可疑。阈值200可根据租户基线调优(下文 agent.py 也把该值设计为可配置参数)。
Step 6:将 Graph 活动关联回源登录
把可疑的 Graph 调用者透视回登录记录,恢复设备、位置、MFA 与条件访问结果。注意AADGraphActivityLogs中的SignInActivityId可能携带==填充,与SigninLogs.UniqueTokenIdentifier不同:
AADGraphActivityLogs | where TimeGenerated > ago(1d) | where UserAgent contains "aiohttp" | extend TokenId = trim_end("=", tostring(SignInActivityId)) | join kind=leftouter ( SigninLogs | extend TokenId = tostring(UniqueTokenIdentifier) | project TokenId, UserPrincipalName, IPAddress, AppDisplayName, ConditionalAccessStatus, DeviceDetail ) on TokenId | project TimeGenerated, UserId, UserPrincipalName, CallerIpAddress, IPAddress, AppDisplayName, ConditionalAccessStatus, UserAgenttrim_end("=", ...)正是 api-reference.md 中强调的关联键预处理,缺失这一步会导致 JOIN 大面积失败。关联成功后,可同时看到 Graph 调用侧的CallerIpAddress与登录侧的IPAddress、条件访问状态与设备详情,足以支撑告警定级。
Step 7:固化为计划性分析规则
将保真度最高的查询(Step 2–4)提升为 Sentinel 计划性分析规则:
- 设置查询周期/频率(例如每 1h 运行一次、回看 1d);
- 将规则映射到 T1078.004;
- 配置实体映射(Entity =
UserId,IP =CallerIpAddress,Host/App =AppId),使事件自动富化; - 上线前通过 watchlist 排除已知的自动化/服务主体 AppId 与已批准的扫描器 IP,控制误报。
api-reference.md 还给出了通过 REST API 创建/更新计划的 Sentinal 分析规则的端点大纲:
| 方法 | 端点 | 用途 |
|---|---|---|
| PUT | /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.OperationalInsights/workspaces/{ws}/providers/Microsoft.SecurityInsights/alertRules/{ruleId}?api-version=2023-02-01 | 创建/更新计划性分析规则(请求体承载 KQLquery、queryFrequency、queryPeriod、tactics、techniques: ["T1078.004"]与entityMappings) |
可复用的 KQL 积木块
api-reference.md 将本技能中的常用表达式提炼为可直接复用的积木块,适合在构建新规则时拼接:
// 确认摄入 union withsource=Tbl MicrosoftGraphActivityLogs, AADGraphActivityLogs | where TimeGenerated > ago(1d) | summarize count() by Tbl // aiohttp / python UA(ROADtools) AADGraphActivityLogs | where UserAgent contains "python" and UserAgent contains "aiohttp" // 工具 UA 字符串 union MicrosoftGraphActivityLogs, AADGraphActivityLogs | where UserAgent has_any ("AADInternals","azurehound","BloodHound","Go-http-client") // 从 RequestUri 提取顶层 Graph 资源 | extend TopLevelResource = tolower(tostring(split(split(RequestUri,"?")[0],"/")[3])) // 为关联修剪 SignInActivityId 填充 | extend TokenId = trim_end("=", tostring(SignInActivityId))用 agent.py 脚本化猎捕
仓库中 scripts/agent.py 把上述五条核心 KQL 猎捕查询封装成了可重复运行的蓝队工具。它通过 Azure Monitor Query REST API(https://api.loganalytics.io/v1/workspaces/{ws}/query)执行查询,用 AAD bearer token 认证。获取 token 的方式:
az account get-access-token --resource https://api.loganalytics.io \ --query accessToken -o tsv脚本内置的HUNTS字典对应 SKILL.md 的五个核心猎捕步骤:
ingestion—— Step 1 的摄入确认;roadtools-ua—— Step 2 的 python/aiohttp UA 指纹;tool-agents—— Step 3 的 AADInternals/AzureHound agent 字符串;endpoint-sweep—— Step 4 的抗伪造端点扫描(完整保留has_all的 11 类资源集合);volume-outlier—— Step 5 的高量读取离群点。
运行方式:
python3 agent.py --workspace "<workspace-guid>" \ --token "$(az account get-access-token --resource https://api.loganalytics.io --query accessToken -o tsv)" \ --hunt all --days 7 --threshold 200从源码结构看,脚本的参数设计体现了工程化考量的几个要点:
--hunt支持单条或all批量执行,便于先跑全量再聚焦;--days与--threshold均为可配置项,其中--threshold默认 200,与 SKILL.md Step 5 的Reads > 200保持一致,允许按租户基线调优;- 错误处理对
HTTPError与URLError分别输出前 300 字符的响应体或网络错误,便于在 Sentinel/LA 权限不足或网络隔离环境下快速定位问题; - 结果表格最多打印 50 行并提示剩余行数,避免超大结果集刷屏。
该脚本是「查询即代码」的体现:把本文档中所有关键 KQL 从文档落到可执行文件,适合纳入日常狩猎的定时任务或 SOAR 编排。
检测指纹速查
| 工具 | 主要指纹 | 数据表 |
|---|---|---|
| ROADtools(roadrecon) | python+aiohttpUA;5 分钟内完整目录端点扫描 | AADGraphActivityLogs |
| AADInternals | UA 中的AADInternals/ 工具库字符串;AAD Graph 读取 | AADGraphActivityLogs |
| AzureHound | Go HTTP 客户端 UA;广泛的 MS Graph 枚举 | MicrosoftGraphActivityLogs |
| 通用侦察 | 短窗口内对 users/groups/apps/SPs 的高量 GET | 两张表 |
验证标准
按以下清单确认本技能已落地生效:
- MicrosoftGraphActivityLogs 与 AADGraphActivityLogs 均已确认摄入
- 已执行 ROADtools/aiohttp 的 User-Agent 指纹猎捕
- 已执行 AADInternals/AzureHound agent 猎捕
- 已执行行为端点扫描猎捕,并针对误报完成调优
- 已执行高量枚举离群点查询
- 至少一个发现已关联回登录/会话与源身份
- 高保真查询已提升为计划性分析规则,映射 T1078.004 并配置实体映射
- 已通过 watchlist 排除已知良好的服务主体/IP,控制误报
小结
AADGraphActivityLogs 的上线终结了传统 Azure AD Graph 的可见性盲区,而本技能给出了把两张 Graph 活动表变成检测力的完整路径:从确认摄入、字符串指纹猎捕,到抗 UA 伪造的行为端点扫描、高量离群点分析,再到登录关联与规则固化。它与仓库中的进攻性技能(attacking-entra-id-with-roadtools、auditing-entra-id-with-aadinternals、exploiting-active-directory-with-bloodhound)互为镜像,构成攻防闭环;配合 scripts/agent.py 可将整套猎捕流程脚本化、可重复化,最终让 T1078.004 云账户滥用在这一数据源上真正可检测、可响应。
【免费下载链接】Anthropic-Cybersecurity-Skills817 structured cybersecurity skills for AI agents · Mapped to 6 frameworks: MITRE ATT&CK, NIST CSF 2.0, MITRE ATLAS, D3FEND, NIST AI RMF & MITRE F3 (Fight Fraud) · agentskills.io standard · Works with Claude Code, GitHub Copilot, Codex CLI, Cursor, Gemini CLI & 20+ platforms · 29 security domains · Apache 2.0项目地址: https://gitcode.com/GitHub_Trending/an/Anthropic-Cybersecurity-Skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考