CVE-2022-21191 (published 2023-01-13T05:15Z)

Versions of the package global-modules-path before 3.0.0 are vulnerable to Command Injection due to missing input sanitization or other checks and sandboxes being employed to the getPath  function.\r\r

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

Weakness information:
cwe_id: NVD-CWE-Other
cwe_name: Other
description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/rosen-vladimirov/global-modules-path
commit_details: repo_url: https://github.com/rosen-vladimirov/global-modules-path
author: rosen-vladimirov
author_date: 2023-01-08 19:05:12+02:00
msg: fix: do not allow command injection

Due to usage of execSync, callers may inject commands, like getPath("something & touch abc", "somethingElse & touch def").
To fix this, replace execSync with spawnSync, which is safe for command injection.
repository: repo_url: https://github.com/rosen-vladimirov/global-modules-path
repo_name: rosen-vladimirov/global-modules-path
description: Returns path to globally installed package
repo_language: JavaScript
owner: rosen-vladimirov
file_changes: filename: index.js
change_type: ModificationType.MODIFY
diff: @@ -25,9 +25,26 @@ const getNpmExecutable = (platform) => {
 	return npmExecutableName;
 };
 
+const spawnSyncWrapper = (command, commandArgs) => {
+	const result = childProcess.spawnSync(command, commandArgs);
+	if (!result) {
+		return null;
+	}
+
+	if (result.error) {
+		throw result.error;
+	}
+
+	if (result.stdout) {
+		return result.stdout.toString().trim();
+	}
+
+	return null;
+};
+
 const getNpmPrefix = (pathToNpm) => {
 	try {
-		const npmPrefixStdout = childProcess.execSync(`${pathToNpm} config get prefix`);
+		const npmPrefixStdout = spawnSyncWrapper(pathToNpm, ["config", "get", "prefix"]);
 		return npmPrefixStdout && npmPrefixStdout.toString().trim();
 	} catch (err) {
 		console.error(err.message);
@@ -49,6 +66,7 @@ const getPathFromNpmConfig = (platform, packageName) => {
 
 		const packagePath = path.join(nodeModulesPath, packageName);
 		const verifiedPath = getVerifiedPath(packagePath, packageName);
+
 		if (verifiedPath) {
 			return verifiedPath;
 		}
@@ -107,7 +125,8 @@ const getVerifiedPath = (suggestedPath, packageName) => {
 
 const getPathFromExecutableNameOnWindows = (packageName, executableName) => {
 	try {
-		const whereResult = (childProcess.execSync(`where ${executableName}`) || "").toString().split("\n");
+		const whereResult = (spawnSyncWrapper("where", executableName) || "").split("\n");
+
 		for (const line of whereResult) {
 			const pathToExecutable = line && line.trim();
 
@@ -145,8 +164,8 @@ const getPathFromExecutableNameOnNonWindows = (packageName, executableName) => {
 
 		// whichResult: /usr/local/nvm/versions/node/v4.2.1/bin/mobile-cli-lib
 		// lsLResult: lrwxrwxrwx 1 rvladimirov rvladimirov 52 Oct 20 14:51 /usr/local/nvm/versions/node/v4.2.1/bin/mobile-cli-lib -> ../lib/node_modules/mobile-cli-lib/bin/common-lib.js
-		const whichResult = (childProcess.execSync(`which ${executableName}`) || "").toString().trim(),
-			lsLResult = (childProcess.execSync(`ls -l \`which ${executableName}\``) || "").toString().trim();
+		const whichResult = spawnSyncWrapper("which", [executableName]);
+		const lsLResult = spawnSyncWrapper("ls", ["-l", whichResult]);
 
 		if (whichResult && lsLResult) {
 			const regex = new RegExp(`${whichResult}\\s+->\\s+(.*?)$`),

programming_language: Go
filename: index.js
change_type: ModificationType.MODIFY
diff: @@ -12,7 +12,7 @@ let processWrapper = require("../lib/process-wrapper");
 let fs = require("fs"),
 	childProcess = require("child_process");
 
-const originalExecSync = childProcess.execSync;
+const originalspawnSync = childProcess.spawnSync;
 const originalConsoleError = console.error;
 const originalExistsSync = fs.existsSync;
 const originalReadFileSync = fs.readFileSync;
@@ -21,7 +21,7 @@ describe("getPath", () => {
 
 	afterEach(() => {
 		processWrapper.getProcessPlatform = () => process.platform;
-		childProcess.execSync = originalExecSync;
+		childProcess.spawnSync = originalspawnSync;
 		console.error = originalConsoleError;
 		fs.existsSync = originalExistsSync;
 		fs.readFileSync = originalReadFileSync;
@@ -41,9 +41,9 @@ describe("getPath", () => {
 					const expectedErrorMessage = "npm throws";
 					let errors = [];
 					console.error = (message) => errors.push(message);
-					childProcess.execSync = (command) => {
-						if (command.indexOf("get prefix") !== -1) {
-							throw new Error(expectedErrorMessage);
+					childProcess.spawnSync = (command, commandArgs) => {
+						if (commandArgs.indexOf("get") !== -1 && commandArgs.indexOf("prefix") !== -1) {
+							return { error: new Error(expectedErrorMessage) };
 						}
 					};
 
@@ -59,7 +59,7 @@ describe("getPath", () => {
 
 					fs.existsSync = () => false;
 
-					childProcess.execSync = (command) => {
+					childProcess.spawnSync = (command) => {
 						if (command.indexOf("get prefix") !== -1) {
 							return npmConfigPrefix;
 						}
@@ -75,7 +75,7 @@ describe("getPath", () => {
 					const packageName = "test1",
 						executableName = "test1.js";
 
-					childProcess.execSync = () => null;
+					childProcess.spawnSync = () => null;
 
 					const result = index.getPath(packageName, executableName);
 					assert.deepEqual(result, null);
@@ -87,9 +87,10 @@ describe("getPath", () => {
 	const getCorrectResultFromNpmPrefix = (npmConfigPrefix, packageName) => {
 		fs.existsSync = () => true;
 		fs.readFileSync = () => JSON.stringify({ name: packageName });
-		childProcess.execSync = (command) => {
-			if (command.indexOf("get prefix") !== -1) {
-				return npmConfigPrefix;
+		fs.realpathSync = (p) => p; 
+		childProcess.spawnSync = (command, commandArgs) => {
+			if (commandArgs.indexOf("get") !== -1 && commandArgs.indexOf("prefix") !== -1) {
+				return { stdout: npmConfigPrefix };
 			}
 		};
 
@@ -105,8 +106,8 @@ describe("getPath", () => {
 		describe("works correctly when npm prefix is used", () => {
 			it("uses npm.cmd for npm executable", () => {
 				let commands = [];
-				childProcess.execSync = (command) => {
-					commands.push(command);
+				childProcess.spawnSync = (command, commandArgs) => {
+					commands.push(`${command} ${(commandArgs || []).join(" ")}`);
 				};
 
 				index.getPath("test1");
@@ -141,9 +142,9 @@ describe("getPath", () => {
 
 				fs.existsSync = () => true;
 
-				childProcess.execSync = (command) => {
+				childProcess.spawnSync = (command) => {
 					if (command.indexOf("where") !== -1) {
-						throw new Error(expectedErrorMessage);
+						return { error: new Error(expectedErrorMessage) };
 					}
 
 					return null;
@@ -163,9 +164,9 @@ describe("getPath", () => {
 
 				fs.existsSync = () => true;
 
-				childProcess.execSync = (command) => {
+				childProcess.spawnSync = (command) => {
 					if (command.indexOf("where") !== -1) {
-						return whereResult;
+						return { stdout: whereResult };
 					}
 
 					return null;
@@ -194,9 +195,9 @@ describe("getPath", () => {
 
 				fs.existsSync = (pathToCheck) => pathToCheck !== path.join(executableDirName, "node_modules", packageName);
 
-				childProcess.execSync = (command) => {
+				childProcess.spawnSync = (command) => {
 					if (command.indexOf("where") !== -1) {
-						return whereResult;
+						return { stdout: whereResult };
 					}
 
 					return null;
@@ -233,9 +234,9 @@ describe("getPath", () => {
 
 				fs.existsSync = (pathToCheck) => pathToCheck !== path.join(executableDirName, "node_modules", packageName);
 
-				childProcess.execSync = (command) => {
+				childProcess.spawnSync = (command) => {
 					if (command.indexOf("where") !== -1) {
-						return whereResult;
+						return { stdout: whereResult };
 					}
 
 					return null;
@@ -284,9 +285,9 @@ describe("getPath", () => {
 					return "";
 				};
 
-				childProcess.execSync = (command) => {
+				childProcess.spawnSync = (command) => {
 					if (command.indexOf("where") !== -1) {
-						return whereResult;
+						return { stdout: whereResult };
 					}
 
 					return null;
@@ -316,9 +317,9 @@ describe("getPath", () => {
 					return "";
 				};
 
-				childProcess.execSync = (command) => {
+				childProcess.spawnSync = (command) => {
 					if (command.indexOf("where") !== -1) {
-						return whereResult;
+						return { stdout: whereResult };
 					}
 
 					return null;
@@ -354,9 +355,9 @@ describe("getPath", () => {
 					return "";
 				};
 
-				childProcess.execSync = (command) => {
+				childProcess.spawnSync = (command) => {
 					if (command.indexOf("where") !== -1) {
-						return whereResult;
+						return { stdout: whereResult };
 					}
 
 					return null;
@@ -380,8 +381,8 @@ describe("getPath", () => {
 
 			it("uses npm for npm executable", () => {
 				let commands = [];
-				childProcess.execSync = (command) => {
-					commands.push(command);
+				childProcess.spawnSync = (command, commandArgs) => {
+					commands.push(`${command} ${(commandArgs || []).join(" ")}`);
 				};
 
 				index.getPath("test1");
@@ -416,7 +417,7 @@ describe("getPath", () => {
 
 					fs.existsSync = () => true;
 
-					childProcess.execSync = (command) => {
+					childProcess.spawnSync = (command) => {
 						if (command.indexOf("which") !== -1) {
 							throw new Error(expectedErrorMessage);
 						}
@@ -440,9 +441,9 @@ describe("getPath", () => {
 
 					fs.existsSync = () => true;
 
-					childProcess.execSync = (command) => {
-						if (command.indexOf("ls -l") !== -1) {
-							throw new Error(expectedErrorMessage);
+					childProcess.spawnSync = (command) => {
+						if (command === "ls") {
+							return { error: new Error(expectedErrorMessage) };
 						}
 
 						return null;
@@ -479,14 +480,14 @@ describe("getPath", () => {
 							return filePath;
 						};
 
-						childProcess.execSync = (command) => {
+						childProcess.spawnSync = (command) => {
 
-							if (command.indexOf("ls -l") !== -1) {
-								return lsLResult;
+							if (command === "ls") {
+								return { stdout: lsLResult };
 							}
 
-							if (command.indexOf("which") !== -1) {
-								return whichResult;
+							if (command == "which") {
+								return { stdout: whichResult };
 							}
 
 							return null;
@@ -544,14 +545,14 @@ describe("getPath", () => {
 							return filePath.indexOf("package.json") !== -1;
 						};
 
-						childProcess.execSync = (command) => {
+						childProcess.spawnSync = (command) => {
 
-							if (command.indexOf("ls -l") !== -1) {
-								return lsLResult;
+							if (command === "ls") {
+								return { stdout: lsLResult };
 							}
 
-							if (command.indexOf("which") !== -1) {
-								return whichResult;
+							if (command == "which") {
+								return { stdout: whichResult };
 							}
 
 							return null;
@@ -582,14 +583,14 @@ describe("getPath", () => {
 							return filePath.indexOf("package.json") !== -1;
 						};
 
-						childProcess.execSync = (command) => {
+						childProcess.spawnSync = (command) => {
 
-							if (command.indexOf("ls -l") !== -1) {
-								return lsLResult;
+							if (command === "ls") {
+								return { stdout: lsLResult };
 							}
 
-							if (command.indexOf("which") !== -1) {
-								return whichResult;
+							if (command == "which") {
+								return { stdout: whichResult };
 							}
 
 							return null;

programming_language: TypeScript