CVE-2022-25918 (published 2022-10-27T10:15Z)

The package shescape from 1.5.10 and before 1.6.1 are vulnerable to Regular Expression Denial of Service (ReDoS) via the escape function in index.js, due to the usage of insecure regex in the escapeArgBash function.

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

Weakness information:
cwe_id: CWE-1333
cwe_name: Inefficient Regular Expression Complexity
description: The product uses a regular expression with an inefficient, possibly exponential worst-case computational complexity that consumes excessive CPU cycles.
extended_description: Some regular expression engines have a feature called "backtracking". If the token cannot match, the engine "backtracks" to a position that may result in a different token that can match.
                Backtracking becomes a weakness if all of these conditions are met:
                
                    The number of possible backtracking attempts are exponential relative to the length of the input.
                    The input can fail to match the regular expression.
                    The input can be long enough.
                
		
		  Attackers can create crafted inputs that
		  intentionally cause the regular expression to use
		  excessive backtracking in a way that causes the CPU
		  consumption to spike.
		
            
            

Fix information:
repo_url: https://github.com/ericcornelissen/shescape
commit_details: repo_url: https://github.com/ericcornelissen/shescape
author: Eric Cornelissen
author_date: 2022-10-25 19:49:23+02:00
msg: Merge pull request from GHSA-cr84-xvw4-qx3c

* Test polynomial backtracking ReDoS attack string on Bash

Create a unit test for a (new) ReDoS string on bash. This test should be
failing in this commit.

* Fix ReDoS (as seen in ReDoS test 1) for bash

Update bash escaping to pessimistically escape opening curly braces,
similar to Zsh escaping, to fix the ReDoS due to the complex regular
expression for opening curly braces.

Update tests accordingly.

* Test against old ReDoS values and all shells

Refactor the ReDoS test such that 1) it's tested against all (supported)
shells, and 2) extensible to test against multiple potential ReDoS
strings. Regarding the latter, the scope has been updated to include
known ReDoS strings related to CVE-2022-36064.

The `redos` value is intentionally a function as this avoids building
the string unnecessarily if the file is imported but the `redos` value
isn't used.

* Update SECURITY.md

Add latest advisory and credit its finder
repository: repo_url: https://github.com/ericcornelissen/shescape
repo_name: ericcornelissen/shescape
description: Simple shell escape library for JavaScript
repo_language: JavaScript
owner: ericcornelissen
file_changes: filename: SECURITY.md
change_type: ModificationType.MODIFY
diff: @@ -27,18 +27,21 @@ report this publicly. For example, as a regular issue in the public repository.
 
 ## Advisories
 
-| ID               | Date       | Affected versions | Patched versions |
-| :--------------- | :--------- | :---------------- | :--------------- |
-| `CVE-2021-21384` | 2021-03-19 | `<1.1.3`          | `1.1.3`          |
-| `CVE-2022-24725` | 2022-03-03 | `>=1.4.0 <1.5.1`  | `1.5.1`          |
-| `CVE-2022-31179` | 2022-07-26 | `<1.5.8`          | `1.5.8`          |
-| `CVE-2022-31180` | 2022-07-26 | `>=1.4.0 <1.5.8`  | `1.5.8`          |
-| `CVE-2022-36064` | 2022-08-29 | `>=1.5.1 <1.5.10` | `1.5.10`         |
+| ID                    | Date       | Affected versions | Patched versions |
+| :-------------------- | :--------- | :---------------- | :--------------- |
+| `CVE-2021-21384`      | 2021-03-19 | `<1.1.3`          | `1.1.3`          |
+| `CVE-2022-24725`      | 2022-03-03 | `>=1.4.0 <1.5.1`  | `1.5.1`          |
+| `CVE-2022-31179`      | 2022-07-26 | `<1.5.8`          | `1.5.8`          |
+| `CVE-2022-31180`      | 2022-07-26 | `>=1.4.0 <1.5.8`  | `1.5.8`          |
+| `CVE-2022-36064`      | 2022-08-29 | `>=1.5.1 <1.5.10` | `1.5.10`         |
+| `GHSA-cr84-xvw4-qx3c` | 2022-10-25 | `>=1.5.10 <1.6.1` | `1.6.1`          |
 
 ## Acknowledgments
 
 We would like to publicly thank the following reporters:
 
-- _None yet_
+- Elliot Ward ([@mowzk]) from [Snyk]
 
+[@mowzk]: https://github.com/mowzk
 [security@ericcornelissen.dev]: mailto:security@ericcornelissen.dev?subject=SECURITY%20%28shescape%29
+[snyk]: https://snyk.io/

programming_language: Markdown
filename: unix.js
change_type: ModificationType.MODIFY
diff: @@ -48,10 +48,7 @@ function escapeArgBash(arg, interpolation, quoted) {
       .replace(/\\/gu, "\\\\")
       .replace(/\n/gu, " ")
       .replace(/(^|\s)([#~])/gu, "$1\\$2")
-      .replace(/(["$&'()*;<>?`|])/gu, "\\$1")
-      .replace(/(?<!\{)\{+(?=(?:[^{][^,.]*)?[,.][^}]*\})/gu, (curlyBraces) =>
-        curlyBraces.replace(/\{/gu, "\\{")
-      )
+      .replace(/(["$&'()*;<>?`{|])/gu, "\\$1")
       .replace(/(?<=[:=])(~)(?=[\s+\-/0:=]|$)/gu, "\\$1");
   } else if (quoted) {
     result = result.replace(/'/gu, `'\\''`);

programming_language: JavaScript
filename: unix.js
change_type: ModificationType.MODIFY
diff: @@ -1116,7 +1116,7 @@ export const escape = {
     "curly brackets ('{', '}')": [
       {
         input: "a{b",
-        expected: { interpolation: "a{b", noInterpolation: "a{b" },
+        expected: { interpolation: "a\\{b", noInterpolation: "a{b" },
       },
       {
         input: "a}b",
@@ -1124,7 +1124,7 @@ export const escape = {
       },
       {
         input: "a{b{c",
-        expected: { interpolation: "a{b{c", noInterpolation: "a{b{c" },
+        expected: { interpolation: "a\\{b\\{c", noInterpolation: "a{b{c" },
       },
       {
         input: "a}b}c",
@@ -1132,7 +1132,7 @@ export const escape = {
       },
       {
         input: "a{b}c",
-        expected: { interpolation: "a{b}c", noInterpolation: "a{b}c" },
+        expected: { interpolation: "a\\{b}c", noInterpolation: "a{b}c" },
       },
       {
         input: "a{b,c}d",
@@ -3601,3 +3601,13 @@ export const quote = {
     ],
   },
 };
+
+export const redos = () => [
+  // CVE-2022-36064
+  `foo${"{".repeat(150_000)}bar`,
+  `=${":".repeat(150_000)}foobar`,
+  `{${",".repeat(150_000)}`,
+
+  // No identifier (yet)
+  "{,".repeat(150_000),
+];

programming_language: TypeScript
filename: escape.test.js
change_type: ModificationType.MODIFY
diff: @@ -46,4 +46,24 @@ Object.entries(fixtures.escape).forEach(([shellName, scenarios]) => {
   });
 });
 
+fixtures.redos().forEach((s, i) => {
+  test(`bash, ReDoS #${i}`, (t) => {
+    const escape = unix.getEscapeFunction("bash");
+    escape(s, true, false);
+    t.pass();
+  });
+
+  test(`dash, ReDoS #${i}`, (t) => {
+    const escape = unix.getEscapeFunction("dash");
+    escape(s, true, false);
+    t.pass();
+  });
+
+  test(`zsh, ReDoS #${i}`, (t) => {
+    const escape = unix.getEscapeFunction("zsh");
+    escape(s, true, false);
+    t.pass();
+  });
+});
+
 test(macros.unsupportedShell, { fn: unix.getEscapeFunction });

programming_language: TypeScript