policy: handle Module.constructor and main.extensions bypass

Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com>
PR-URL: https://github.com/nodejs-private/node-private/pull/417
Refs: https://hackerone.com/bugs?subject=nodejs&report_id=1960870
Refs: https://hackerone.com/bugs?subject=nodejs&report_id=2043807
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
CVE-ID: CVE-2023-32002,CVE-2023-32006
This commit is contained in:
RafaelGSS 2023-05-29 19:45:33 -03:00
parent 3868ae0f4f
commit b68e5e7981
9 changed files with 97 additions and 2 deletions

View File

@ -152,8 +152,8 @@ const isWindows = process.platform === 'win32';
const relativeResolveCache = { __proto__: null };
let requireDepth = 0;
let statCache = null;
let isPreloading = false;
let statCache = null;
function internalRequire(module, id) {
validateString(id, 'id');
@ -1429,5 +1429,14 @@ Module.syncBuiltinESMExports = function syncBuiltinESMExports() {
}
};
ObjectDefineProperty(Module.prototype, 'constructor', {
__proto__: null,
get: function() {
return policy() ? undefined : Module;
},
configurable: false,
enumerable: false,
});
// Backwards compatibility
Module.Module = Module;

View File

@ -14,6 +14,7 @@ const {
StringPrototypeStartsWith,
} = primordials;
const {
ERR_INVALID_ARG_TYPE,
ERR_MANIFEST_DEPENDENCY_MISSING,
ERR_UNKNOWN_BUILTIN_MODULE,
} = require('internal/errors').codes;
@ -68,12 +69,22 @@ function loadBuiltinModule(filename, request) {
return mod;
}
let $Module = null;
function lazyModule() {
$Module = $Module || require('internal/modules/cjs/loader').Module;
return $Module;
}
// Invoke with makeRequireFunction(module) where |module| is the Module object
// to use as the context for the require() function.
// Use redirects to set up a mapping from a policy and restrict dependencies
const urlToFileCache = new SafeMap();
function makeRequireFunction(mod, redirects) {
const Module = mod.constructor;
// lazy due to cycle
const Module = lazyModule();
if (mod instanceof Module !== true) {
throw new ERR_INVALID_ARG_TYPE('mod', 'Module', mod);
}
let require;
if (redirects) {

View File

@ -0,0 +1,2 @@
const os = module.constructor.createRequire('file:///os-access-module.js')('os')
os.cpus()

View File

View File

@ -0,0 +1,2 @@
const m = new require.main.constructor();
m.require('./invalid-module')

View File

@ -0,0 +1,2 @@
const m = new require.main.constructor();
require.extensions['.js'](m, './invalid-module')

View File

@ -0,0 +1,13 @@
{
"resources": {
"./createRequire-bypass.js": {
"integrity": true
},
"/os-access-module.js": {
"integrity": true,
"dependencies": {
"os": true
}
}
}
}

View File

@ -0,0 +1 @@
module.constructor._load('node:child_process');

View File

@ -81,3 +81,58 @@ const fixtures = require('../common/fixtures.js');
assert.match(stderr, /ERR_MANIFEST_DEPENDENCY_MISSING/);
assert.match(stderr, /does not list os as a dependency specifier for conditions: require, node, node-addons/);
}
{
const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json');
const mainModuleBypass = fixtures.path('policy-manifest', 'module-constructor-bypass.js');
const result = spawnSync(process.execPath, [
'--experimental-policy',
policyFilepath,
mainModuleBypass,
]);
assert.notStrictEqual(result.status, 0);
const stderr = result.stderr.toString();
assert.match(stderr, /TypeError/);
}
{
const policyFilepath = fixtures.path('policy-manifest', 'manifest-impersonate.json');
const createRequireBypass = fixtures.path('policy-manifest', 'createRequire-bypass.js');
const result = spawnSync(process.execPath, [
'--experimental-policy',
policyFilepath,
createRequireBypass,
]);
assert.notStrictEqual(result.status, 0);
const stderr = result.stderr.toString();
assert.match(stderr, /TypeError/);
}
{
const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json');
const mainModuleBypass = fixtures.path('policy-manifest', 'main-constructor-bypass.js');
const result = spawnSync(process.execPath, [
'--experimental-policy',
policyFilepath,
mainModuleBypass,
]);
assert.notStrictEqual(result.status, 0);
const stderr = result.stderr.toString();
assert.match(stderr, /TypeError/);
}
{
const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json');
const mainModuleBypass = fixtures.path('policy-manifest', 'main-constructor-extensions-bypass.js');
const result = spawnSync(process.execPath, [
'--experimental-policy',
policyFilepath,
mainModuleBypass,
]);
assert.notStrictEqual(result.status, 0);
const stderr = result.stderr.toString();
assert.match(stderr, /TypeError/);
}