CVE-2022-25858 (published 2022-07-15T20:15Z)

The package terser before 4.8.1, from 5.0.0 and before 5.14.2 are vulnerable to Regular Expression Denial of Service (ReDoS) due to insecure usage of regular expressions.

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/terser/terser
commit_details: repo_url: https://github.com/terser/terser
author: Fábio Santos
author_date: 2022-04-28 15:15:45+01:00
msg: fix potential regexp DDOS
repository: repo_url: https://github.com/terser/terser
repo_name: terser/terser
description: 🗜 JavaScript parser, mangler and compressor toolkit for ES6+
repo_language: JavaScript
owner: terser
file_changes: filename: evaluate.js
change_type: ModificationType.MODIFY
diff: @@ -46,7 +46,8 @@ import {
     makePredicate,
     return_this,
     string_template,
-    regexp_source_fix
+    regexp_source_fix,
+    regexp_is_safe,
 } from "../utils/index.js";
 import {
     AST_Array,
@@ -129,7 +130,7 @@ def_eval(AST_BigInt, return_this);
 
 def_eval(AST_RegExp, function (compressor) {
     let evaluated = compressor.evaluated_regexps.get(this.value);
-    if (evaluated === undefined) {
+    if (evaluated === undefined && regexp_is_safe(this.value.source)) {
         try {
             const { source, flags } = this.value;
             evaluated = new RegExp(source, flags);

programming_language: JavaScript
filename: index.js
change_type: ModificationType.MODIFY
diff: @@ -158,6 +158,7 @@ import {
     return_true,
     regexp_source_fix,
     has_annotation,
+    regexp_is_safe,
 } from "../utils/index.js";
 import { first_in_statement } from "../utils/first_in_statement.js";
 import { equivalent_to } from "../equivalent-to.js";
@@ -2140,6 +2141,7 @@ def_optimize(AST_Call, function(self, compressor) {
                     params.push(value);
                     return arg !== value;
                 })
+                && regexp_is_safe(params[0])
             ) {
                 let [ source, flags ] = params;
                 source = regexp_source_fix(new RegExp(source).source);

programming_language: JavaScript
filename: index.js
change_type: ModificationType.MODIFY
diff: @@ -249,7 +249,15 @@ function regexp_source_fix(source) {
         return (escaped ? "" : "\\") + lineTerminatorEscape[match];
     });
 }
-const all_flags = "gimuy";
+
+// Subset of regexps that is not going to cause regexp based DDOS
+// https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
+const re_safe_regexp = /^[\\/|\0\s\w^$.[\]()]*$/;
+
+/** Check if the regexp is safe for Terser to create without risking a RegExp DOS */
+export const regexp_is_safe = (source) => re_safe_regexp.test(source);
+
+const all_flags = "dgimsuy";
 function sort_regexp_flags(flags) {
     const existing_flags = new Set(flags.split(""));
     let out = "";

programming_language: JavaScript
filename: regexp.js
change_type: ModificationType.MODIFY
diff: @@ -30,10 +30,10 @@ regexp_2: {
         unsafe: true,
     }
     input: {
-        console.log(JSON.stringify("COMPASS? Overpass.".match(new RegExp("([Sap]+)", "ig"))));
+        console.log(JSON.stringify("COMPASS? Overpass.".match(new RegExp("(pass)", "ig"))));
     }
     expect: {
-        console.log(JSON.stringify("COMPASS? Overpass.".match(/([Sap]+)/gi)));
+        console.log(JSON.stringify("COMPASS? Overpass.".match(/(pass)/gi)));
     }
     expect_stdout: '["PASS","pass"]'
 }
@@ -44,10 +44,10 @@ unsafe_slashes: {
         unsafe: true
     }
     input: {
-        console.log(new RegExp("^https://"))
+        console.log(new RegExp("^https//"))
     }
     expect: {
-        console.log(/^https:\/\//)
+        console.log(/^https\/\//)
     }
 }
 unsafe_nul_byte: {
@@ -75,3 +75,16 @@ inline_script: {
     }
     expect_exact: '/* <\\/script> */\n/[<\\/script>]/;'
 }
+
+regexp_no_ddos: {
+    options = { unsafe: true, evaluate: true }
+    input: {
+        console.log(/(b+)b+/.test("bbb"))
+        console.log(RegExp("(b+)b+").test("bbb"))
+    }
+    expect: {
+        console.log(/(b+)b+/.test("bbb"))
+        console.log(RegExp("(b+)b+").test("bbb"))
+    }
+    expect_stdout: ["true", "true"]
+}

programming_language: TypeScript