CVE-2021-23352: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: This affects the package madge before 4.0.1. It is possible to specify a custom Graphviz path via the graphVizPath option parameter which when the .image(), .svg() or .dot() functions are called, is executed by the childprocess.exec function.
CWE description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
CVSS v3 base score: 9.8
Published: 2021-03-09T19:15Z
Repository: https://github.com/pahen/madge
Language: JavaScript
Files changed: lib/graph.js, test/api.js
Commit date: 2021-03-05 15:42:51 +0100
Commit message: Fix potential command injection vulnerability
Diff: diff --git a/lib/graph.js b/lib/graph.js
index 4e64c65..f25b6f5 100644
--- a/lib/graph.js
+++ b/lib/graph.js
@@ -2,11 +2,11 @@
 
 const path = require('path');
 const {promisify} = require('util');
 const graphviz = require('graphviz');
 
-const exec = promisify(require('child_process').exec);
+const exec = promisify(require('child_process').execFile);
 const writeFile = promisify(require('fs').writeFile);
 
 /**
  * Set color on a node.
  * @param  {Object} node
@@ -20,23 +20,18 @@ function setNodeColor(node, color) {
 /**
  * Check if Graphviz is installed on the system.
  * @param  {Object} config
  * @return {Promise}
  */
-function checkGraphvizInstalled(config) {
-	if (config.graphVizPath) {
-		const cmd = path.join(config.graphVizPath, 'gvpr -V');
-		return exec(cmd)
-			.catch(() => {
-				throw new Error('Could not execute ' + cmd);
-			});
-	}
+async function checkGraphvizInstalled(config) {
+	const cmd = config.graphVizPath ? path.join(config.graphVizPath, 'gvpr') : 'gvpr';
 
-	return exec('gvpr -V')
-		.catch((error) => {
-			throw new Error('Graphviz could not be found. Ensure that "gvpr" is in your $PATH.\n' + error);
-		});
+	try {
+		await exec(cmd, ['-V']);
+	} catch (err) {
+		throw new Error(`Graphviz could not be found. Ensure that "gvpr" is in your $PATH. ${err}`);
+	}
 }
 
 /**
  * Return options to use with graphviz digraph.
  * @param  {Object} config
diff --git a/test/api.js b/test/api.js
index 8595d25..2afd3f7 100644
--- a/test/api.js
+++ b/test/api.js
@@ -311,11 +311,11 @@ describe('API', () => {
 
 		it('rejects if graphviz is not installed', (done) => {
 			madge(__dirname + '/cjs/a.js', {graphVizPath: '/invalid/path'})
 				.then((res) => res.image('image.png'))
 				.catch((err) => {
-					err.message.should.match(/Could not execute .*gvpr \-V/);
+					err.message.should.eql('Graphviz could not be found. Ensure that "gvpr" is in your $PATH. Error: spawn /invalid/path/gvpr ENOENT');
 					done();
 				});
 		});
 
 		it('writes image to file', (done) => {

Vulnerable code: const exec = promisify(require('child_process').exec);
function checkGraphvizInstalled(config) {
	if (config.graphVizPath) {
		const cmd = path.join(config.graphVizPath, 'gvpr -V');
		return exec(cmd)
			.catch(() => {
				throw new Error('Could not execute ' + cmd);
			});
	}
	return exec('gvpr -V')
		.catch((error) => {
			throw new Error('Graphviz could not be found. Ensure that "gvpr" is in your $PATH.\n' + error);
		});
					err.message.should.match(/Could not execute .*gvpr \-V/);
Fixed code: const exec = promisify(require('child_process').execFile);
async function checkGraphvizInstalled(config) {
	const cmd = config.graphVizPath ? path.join(config.graphVizPath, 'gvpr') : 'gvpr';
	try {
		await exec(cmd, ['-V']);
	} catch (err) {
		throw new Error(`Graphviz could not be found. Ensure that "gvpr" is in your $PATH. ${err}`);
	}
					err.message.should.eql('Graphviz could not be found. Ensure that "gvpr" is in your $PATH. Error: spawn /invalid/path/gvpr ENOENT');