CVE-2021-21384: Improper Neutralization of Argument Delimiters in a Command ('Argument Injection') (CWE-88)
value: shescape is a simple shell escape package for JavaScript. In shescape before version 1.1.3, anyone using _Shescape_ to defend against shell injection may still be vulnerable against shell injection if the attacker manages to insert a into the payload. For an example see the referenced GitHub Security Advisory. The problem has been patched in version 1.1.3. No further changes are required.
CWE description: The product constructs a string for a command to be executed by a separate component
in another control sphere, but it does not properly delimit the
intended arguments, options, or switches within that command string.
CVSS v3 base score: 7.8
Published: 2021-03-19T00:15Z
Repository: https://github.com/ericcornelissen/shescape
Language: JavaScript
Files changed: CHANGELOG.md, src/unix.js, src/win.js, test/unix.test.js, test/win.test.js
Commit date: 2021-03-13 17:25:41 +0100
Commit message: Strip null characters from arguments

Update both the Unix and Windows `escapeShellArg` implementation to
strip null characters from input. Null characters can be used to at
least cause errors and potentially execute arbitrary commands (depending
on the shell).

This bug was found due to the new fuzzing logic.

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc59581..8597237 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,11 +5,11 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog], and this project adheres to [Semantic
 Versioning].
 
 ## [Unreleased]
 
-- _No changes yet_
+- Strip null characters from arguments.
 
 ## [1.1.2] - 2021-01-07
 
 - Add in-source JSDoc documentation.
 
diff --git a/src/unix.js b/src/unix.js
index 89fc3bd..d0671b5 100644
--- a/src/unix.js
+++ b/src/unix.js
@@ -9,9 +9,9 @@
  *
  * @param {string} arg The argument to escape.
  * @returns {string} The escaped argument.
  */
 function escapeShellArg(arg) {
-  return arg.replace(/'/g, `'\\''`);
+  return arg.replace(/\u{0}/gu, "").replace(/'/g, `'\\''`);
 }
 
 module.exports.escapeShellArg = escapeShellArg;
diff --git a/src/win.js b/src/win.js
index 0283eda..4e4009a 100644
--- a/src/win.js
+++ b/src/win.js
@@ -9,9 +9,9 @@
  *
  * @param {string} arg The argument to escape.
  * @returns {string} The escaped argument.
  */
 function escapeShellArg(arg) {
-  return arg.replace(/"/g, `""`);
+  return arg.replace(/\u{0}/gu, "").replace(/"/g, `""`);
 }
 
 module.exports.escapeShellArg = escapeShellArg;
diff --git a/test/unix.test.js b/test/unix.test.js
index 94e6be2..d33a3ff 100644
--- a/test/unix.test.js
+++ b/test/unix.test.js
@@ -20,6 +20,22 @@ describe("unix.js", function () {
       const input = `' & echo 'Hello world!'`;
       const output = escapeShellArg(input);
       assert.strictEqual(output, `'\\'' & echo '\\''Hello world!'\\''`);
     });
   });
+
+  describe("null characters", function () {
+    const nullChar = String.fromCharCode(0);
+
+    it("removes one null character", function () {
+      const input = `foo' && ls${nullChar} -al ; echo 'bar`;
+      const output = escapeShellArg(input);
+      assert.strictEqual(output, `foo'\\'' && ls -al ; echo '\\''bar`);
+    });
+
+    it("removes multiple null character", function () {
+      const input = `foo'${nullChar}&&ls -al${nullChar};echo 'bar`;
+      const output = escapeShellArg(input);
+      assert.strictEqual(output, `foo'\\''&&ls -al;echo '\\''bar`);
+    });
+  });
 });
diff --git a/test/win.test.js b/test/win.test.js
index ce24a85..1970e25 100644
--- a/test/win.test.js
+++ b/test/win.test.js
@@ -20,6 +20,22 @@ describe("win.js", function () {
       const input = `" & echo "Hello world!`;
       const output = escapeShellArg(input);
       assert.strictEqual(output, `"" & echo ""Hello world!`);
     });
   });
+
+  describe("null characters", function () {
+    const nullChar = String.fromCharCode(0);
+
+    it("removes one null character", function () {
+      const input = `foo" && ls${nullChar} -al ; echo "bar`;
+      const output = escapeShellArg(input);
+      assert.strictEqual(output, `foo"" && ls -al ; echo ""bar`);
+    });
+
+    it("removes multiple null character", function () {
+      const input = `foo"${nullChar}&&ls -al${nullChar};echo "bar`;
+      const output = escapeShellArg(input);
+      assert.strictEqual(output, `foo""&&ls -al;echo ""bar`);
+    });
+  });
 });

Vulnerable code: - _No changes yet_
  return arg.replace(/'/g, `'\\''`);
  return arg.replace(/"/g, `""`);
Fixed code: - Strip null characters from arguments.
  return arg.replace(/\u{0}/gu, "").replace(/'/g, `'\\''`);
  return arg.replace(/\u{0}/gu, "").replace(/"/g, `""`);

  describe("null characters", function () {
    const nullChar = String.fromCharCode(0);

    it("removes one null character", function () {
      const input = `foo' && ls${nullChar} -al ; echo 'bar`;
      const output = escapeShellArg(input);
      assert.strictEqual(output, `foo'\\'' && ls -al ; echo '\\''bar`);
    });

    it("removes multiple null character", function () {
      const input = `foo'${nullChar}&&ls -al${nullChar};echo 'bar`;
      const output = escapeShellArg(input);
      assert.strictEqual(output, `foo'\\''&&ls -al;echo '\\''bar`);
    });
  });

  describe("null characters", function () {
    const nullChar = String.fromCharCode(0);

    it("removes one null character", function () {
      const input = `foo" && ls${nullChar} -al ; echo "bar`;
      const output = escapeShellArg(input);
      assert.strictEqual(output, `foo"" && ls -al ; echo ""bar`);
    });

    it("removes multiple null character", function () {
      const input = `foo"${nullChar}&&ls -al${nullChar};echo "bar`;
      const output = escapeShellArg(input);
      assert.strictEqual(output, `foo""&&ls -al;echo ""bar`);
    });
  });