From 77c71b674e3ea4651daea5aedd7f894599b74bf1 Mon Sep 17 00:00:00 2001 From: Liran Tal Date: Mon, 22 Jul 2024 00:27:04 +0300 Subject: [PATCH] child_process: fix incomplete prototype pollution hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prior pull request (#48726) hardened against prototype pollution vulnerabilities but effectively missed some use-cases which opened a window for prototype pollution for some child_process functions such as spawn(), spawnSync(), and execFileSync(). PR-URL: https://github.com/nodejs/node/pull/53781 Reviewed-By: Vinícius Lourenço Claro Cardoso Reviewed-By: Matteo Collina Reviewed-By: Rafael Gonzaga --- lib/child_process.js | 1 + ...test-child-process-prototype-tampering.mjs | 34 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/lib/child_process.js b/lib/child_process.js index c09fca512584ce..48870b35ad0f34 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -569,6 +569,7 @@ function normalizeSpawnArguments(file, args, options) { else validateObject(options, 'options'); + options = { __proto__: null, ...options }; let cwd = options.cwd; // Validate the cwd, if present. diff --git a/test/parallel/test-child-process-prototype-tampering.mjs b/test/parallel/test-child-process-prototype-tampering.mjs index 5657458f911521..d94c4bdbc61621 100644 --- a/test/parallel/test-child-process-prototype-tampering.mjs +++ b/test/parallel/test-child-process-prototype-tampering.mjs @@ -1,7 +1,7 @@ import * as common from '../common/index.mjs'; import * as fixtures from '../common/fixtures.mjs'; import { EOL } from 'node:os'; -import { strictEqual } from 'node:assert'; +import { strictEqual, notStrictEqual, throws } from 'node:assert'; import cp from 'node:child_process'; // TODO(LiviaMedeiros): test on different platforms @@ -57,3 +57,35 @@ for (const tamperedUID of [0, 1, 999, 1000, 0n, 'gwak']) { delete Object.prototype.execPath; } + +for (const shellCommandArgument of ['-L && echo "tampered"']) { + Object.prototype.shell = true; + const cmd = 'pwd'; + let cmdExitCode = ''; + + const program = cp.spawn(cmd, [shellCommandArgument], { cwd: expectedCWD }); + program.stderr.on('data', common.mustCall()); + program.stdout.on('data', common.mustNotCall()); + + program.on('exit', common.mustCall((code) => { + notStrictEqual(code, 0); + })); + + cp.execFile(cmd, [shellCommandArgument], { cwd: expectedCWD }, + common.mustCall((err) => { + notStrictEqual(err.code, 0); + }) + ); + + throws(() => { + cp.execFileSync(cmd, [shellCommandArgument], { cwd: expectedCWD }); + }, (e) => { + notStrictEqual(e.status, 0); + return true; + }); + + cmdExitCode = cp.spawnSync(cmd, [shellCommandArgument], { cwd: expectedCWD }).status; + notStrictEqual(cmdExitCode, 0); + + delete Object.prototype.shell; +}