CVE-2023-30547 (published 2023-04-17T22:15Z)

vm2 is a sandbox that can run untrusted code with whitelisted Node's built-in modules. There exists a vulnerability in exception sanitization of vm2 for versions up to 3.9.16, allowing attackers to raise an unsanitized host exception inside `handleException()` which can be used to escape the sandbox and run arbitrary code in host context. This vulnerability was patched in the release of version `3.9.17` of `vm2`. There are no known workarounds for this vulnerability. Users are advised to upgrade.

CVSS v3 base score: 10.0, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/patriksimek/vm2
commit_details: repo_url: https://github.com/patriksimek/vm2
author: XmiliaH
author_date: 2023-04-12 10:27:04+02:00
msg: Handle host errors captured in Promises
repository: repo_url: https://github.com/patriksimek/vm2
repo_name: patriksimek/vm2
description: Advanced vm/sandbox for Node.js
repo_language: JavaScript
owner: patriksimek
file_changes: filename: setup-sandbox.js
change_type: ModificationType.MODIFY
diff: @@ -439,23 +439,36 @@ global.eval = new LocalProxy(localEval, EvalHandler);
  * Promise sanitization
  */
 
-if (localPromise && !allowAsync) {
+if (localPromise) {
 
 	const PromisePrototype = localPromise.prototype;
 
-	overrideWithProxy(PromisePrototype, 'then', PromisePrototype.then, AsyncErrorHandler);
-	// This seems not to work, and will produce
-	// UnhandledPromiseRejectionWarning: TypeError: Method Promise.prototype.then called on incompatible receiver [object Object].
-	// This is likely caused since the host.Promise.prototype.then cannot use the VM Proxy object.
-	// Contextify.connect(host.Promise.prototype.then, Promise.prototype.then);
+	if (!allowAsync) {
+
+		overrideWithProxy(PromisePrototype, 'then', PromisePrototype.then, AsyncErrorHandler);
+		// This seems not to work, and will produce
+		// UnhandledPromiseRejectionWarning: TypeError: Method Promise.prototype.then called on incompatible receiver [object Object].
+		// This is likely caused since the host.Promise.prototype.then cannot use the VM Proxy object.
+		// Contextify.connect(host.Promise.prototype.then, Promise.prototype.then);
+
+	} else {
+
+		overrideWithProxy(PromisePrototype, 'then', PromisePrototype.then, {
+			__proto__: null,
+			apply(target, thiz, args) {
+				if (args.length > 1) {
+					const onRejected = args[1];
+					if (typeof onRejected === 'function') {
+						args[1] = function wrapper(error) {
+							error = ensureThis(error);
+							return localReflectApply(onRejected, this, [error]);
+						};
+					}
+				}
+				return localReflectApply(target, thiz, args);
+			}
+		});
 
-	if (PromisePrototype.finally) {
-		overrideWithProxy(PromisePrototype, 'finally', PromisePrototype.finally, AsyncErrorHandler);
-		// Contextify.connect(host.Promise.prototype.finally, Promise.prototype.finally);
-	}
-	if (Promise.prototype.catch) {
-		overrideWithProxy(PromisePrototype, 'catch', PromisePrototype.catch, AsyncErrorHandler);
-		// Contextify.connect(host.Promise.prototype.catch, Promise.prototype.catch);
 	}
 
 }

programming_language: JavaScript
repo_url: https://github.com/patriksimek/vm2
commit_details: repo_url: https://github.com/patriksimek/vm2
author: XmiliaH
author_date: 2023-04-12 10:25:46+02:00
msg: Ensure every catch block is protected
repository: repo_url: https://github.com/patriksimek/vm2
repo_name: patriksimek/vm2
description: Advanced vm/sandbox for Node.js
repo_language: JavaScript
owner: patriksimek
file_changes: filename: transformer.js
change_type: ModificationType.MODIFY
diff: @@ -113,12 +113,23 @@ function transformer(args, body, isAsync, isGenerator, filename) {
 		if (nodeType === 'CatchClause') {
 			const param = node.param;
 			if (param) {
-				if (param.type === 'ObjectPattern') {
+				if (param.type === 'Identifier') {
+					const name = assertType(param, 'Identifier').name;
+					const cBody = assertType(node.body, 'BlockStatement');
+					if (cBody.body.length > 0) {
+						insertions.push({
+							__proto__: null,
+							pos: cBody.body[0].start,
+							order: TO_LEFT,
+							coder: () => `${name}=${INTERNAL_STATE_NAME}.handleException(${name});`
+						});
+					}
+				} else {
 					insertions.push({
 						__proto__: null,
 						pos: node.start,
 						order: TO_RIGHT,
-						coder: () => `catch(${tmpname}){try{throw(${tmpname}=${INTERNAL_STATE_NAME}.handleException(${tmpname}));}`
+						coder: () => `catch(${tmpname}){${tmpname}=${INTERNAL_STATE_NAME}.handleException(${tmpname});try{throw ${tmpname};}`
 					});
 					insertions.push({
 						__proto__: null,
@@ -126,17 +137,6 @@ function transformer(args, body, isAsync, isGenerator, filename) {
 						order: TO_LEFT,
 						coder: () => `}`
 					});
-				} else {
-					const name = assertType(param, 'Identifier').name;
-					const cBody = assertType(node.body, 'BlockStatement');
-					if (cBody.body.length > 0) {
-						insertions.push({
-							__proto__: null,
-							pos: cBody.body[0].start,
-							order: TO_LEFT,
-							coder: () => `${name}=${INTERNAL_STATE_NAME}.handleException(${name});`
-						});
-					}
 				}
 			}
 		} else if (nodeType === 'WithStatement') {

programming_language: JavaScript