CVE-2020-36650: Improper Neutralization of Special Elements used in a Command ('Command Injection') (CWE-77)
value: A vulnerability, which was classified as critical, was found in IonicaBizau node-gry up to 5.x. This affects an unknown part. The manipulation leads to command injection. Upgrading to version 6.0.0 is able to address this issue. The patch is named 5108446c1e23960d65e8b973f1d9486f9f9dbd6c. It is recommended to upgrade the affected component. The associated identifier of this vulnerability is VDB-218019.
CWE description: The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
CVSS v3 base score: 8
Published: 2023-01-11T18:15Z
Repository: https://github.com/IonicaBizau/node-gry
Language: JavaScript
Files changed: example/index.js, lib/index.js
Commit date: 2020-04-03 23:58:55 +0200
Commit message: fix command injection vulnerability

Diff: diff --git a/example/index.js b/example/index.js
index de78aea..0a86e73 100644
--- a/example/index.js
+++ b/example/index.js
@@ -14,11 +14,11 @@ oneByOne([
         console.log("> Created the repository.");
         fs.writeFile("./foo/README", "Hello World!", cb);
     }
   , cb => {
         console.log("> Created README.md");
-        myRepo.exec("add .", cb);
+        myRepo.exec(['add', '.'], cb);
     }
   , cb => {
         console.log("> Added the files.");
         myRepo.commit("Initial commit.", cb);
     }
diff --git a/lib/index.js b/lib/index.js
index 012b191..0691556 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -50,33 +50,26 @@ class Gry {
      * @param {Array} args An array of options passed to the spawned process. This is optional (if not provided, `exec` will be used instead).
      * @param {Function} callback The callback function.
      * @return {Gry} The `Gry` instance.
      */
     exec (command, args, callback) {
-
         var eargs = [];
         if (typeof args === "function") {
             callback = args;
             args = null;
         }
 
-        // Handle spawn
-        if (Array.isArray(args)) {
-            eargs.push("git", [command].concat(args));
-        } else {
-            eargs.push("git " + command.trim());
-        }
-
         eargs.push({ cwd: this.cwd });
 
         // Add the callback function
         eargs.push((err, stdout) => {
             if (err) { return callback(err); }
             callback(null, stdout.trimRight());
         });
+        console.log({command, eargs, callback})
 
-        el.add.apply(el, eargs);
+        el.add('git', command, eargs[0], eargs[1]);
         return this;
     }
 
     /**
      * init
@@ -86,11 +79,11 @@ class Gry {
      * @function
      * @param {Function} callback The callback function.
      * @return {Gry} The `Gry` instance.
      */
     init (callback) {
-        return this.exec("init", callback);
+        return this.exec(['init'], callback);
     }
 
     /**
      * create
      * Creates a git repository.
@@ -126,11 +119,11 @@ class Gry {
         message = message.replace(/\"/g, "\\");
         if (typeof options === "function") {
             callback = options;
             options = "";
         }
-        return this.exec("commit -m \"" + message + "\" " + options, callback)
+        return this.exec(['commit', '-m', message, ...options.split(' ').filter(a => a)], callback)
     }
 
     /**
      * pull
      * Runs `git pull`.
@@ -144,11 +137,11 @@ class Gry {
     pull (options, callback) {
         if (typeof options === "function") {
             callback = options;
             options = "";
         }
-        return this.exec("pull " + options, callback);
+        return this.exec(['pull', ...options.split(' ')], callback);
     }
 
     /**
      * add
      * Runs `git add`.
@@ -162,11 +155,11 @@ class Gry {
     add (options, callback) {
         if (typeof options === "function") {
             callback = options;
             options = ".";
         }
-        return this.exec("add " + options, callback);
+        return this.exec(['add', ...options.split(' ')], callback);
     }
 
     /**
      * branch
      * Runs `git branch`.
@@ -180,11 +173,11 @@ class Gry {
     branch (options, callback) {
         if (typeof options === "function") {
             callback = options;
             options = "";
         }
-        return this.exec("branch " + options, callback);
+        return this.exec(['branch', ...options.split(' ')], callback);
     }
 
     /**
      * checkout
      * Runs `git checkout`.
@@ -198,11 +191,11 @@ class Gry {
     checkout (options, callback) {
         if (typeof options === "function") {
             callback = options;
             options = "";
         }
-        return this.exec("checkout " + options, callback);
+        return this.exec(['checkout', ...options.split(' ')], callback);
     }
 
     /**
      * clone
      * Runs `git clone`.
@@ -217,11 +210,11 @@ class Gry {
     clone (gitUrl, options, callback) {
         if (typeof options === "function") {
             callback = options;
             options = "";
         }
-        return this.exec("clone " + gitUrl + " " + options, callback);
+        return this.exec(['clone', gitUrl, ...options.split(' ')], callback);
     }
 }
 
 Gry.limiter = el;
 

Vulnerable code:         myRepo.exec("add .", cb);

        // Handle spawn
        if (Array.isArray(args)) {
            eargs.push("git", [command].concat(args));
        } else {
            eargs.push("git " + command.trim());
        }

        el.add.apply(el, eargs);
        return this.exec("init", callback);
        return this.exec("commit -m \"" + message + "\" " + options, callback)
        return this.exec("pull " + options, callback);
        return this.exec("add " + options, callback);
        return this.exec("branch " + options, callback);
        return this.exec("checkout " + options, callback);
        return this.exec("clone " + gitUrl + " " + options, callback);
Fixed code:         myRepo.exec(['add', '.'], cb);
        console.log({command, eargs, callback})
        el.add('git', command, eargs[0], eargs[1]);
        return this.exec(['init'], callback);
        return this.exec(['commit', '-m', message, ...options.split(' ').filter(a => a)], callback)
        return this.exec(['pull', ...options.split(' ')], callback);
        return this.exec(['add', ...options.split(' ')], callback);
        return this.exec(['branch', ...options.split(' ')], callback);
        return this.exec(['checkout', ...options.split(' ')], callback);
        return this.exec(['clone', gitUrl, ...options.split(' ')], callback);