CVE-2023-24807: Inefficient Regular Expression Complexity (CWE-1333)
value: Undici is an HTTP/1.1 client for Node.js. Prior to version 5.19.1, the `Headers.set()` and `Headers.append()` methods are vulnerable to Regular Expression Denial of Service (ReDoS) attacks when untrusted values are passed into the functions. This is due to the inefficient regular expression used to normalize the values in the `headerValueNormalize()` utility function. This vulnerability was patched in v5.19.1. No known workarounds are available.
CWE description: The product uses a regular expression with an inefficient, possibly exponential worst-case computational complexity that consumes excessive CPU cycles.
CVSS v3 base score: 7.5
Published: 2023-02-16T18:15Z
Repository: https://github.com/nodejs/undici
Language: JavaScript
Files changed: lib/fetch/headers.js, test/fetch/headers.js
Commit date: 2023-02-13 12:23:21 +0100
Commit message: Merge pull request from GHSA-r6ch-mqf9-qc9w

Refs: https://hackerone.com/bugs?report_id=1784449

Co-authored-by: Matteo Collina <hello@matteocollina.com>
Diff: diff --git a/lib/fetch/headers.js b/lib/fetch/headers.js
index 634b81fc..ea3b9a14 100644
--- a/lib/fetch/headers.js
+++ b/lib/fetch/headers.js
@@ -22,14 +22,16 @@ const kHeadersSortedMap = Symbol('headers map sorted')
  */
 function headerValueNormalize (potentialValue) {
   //  To normalize a byte sequence potentialValue, remove
   //  any leading and trailing HTTP whitespace bytes from
   //  potentialValue.
-  return potentialValue.replace(
-    /^[\r\n\t ]+|[\r\n\t ]+$/g,
-    ''
-  )
+
+  // Trimming the end with `.replace()` and a RegExp is typically subject to
+  // ReDoS. This is safer and faster.
+  let i = potentialValue.length
+  while (/[\r\n\t ]/.test(potentialValue.charAt(--i)));
+  return potentialValue.slice(0, i + 1).replace(/^[\r\n\t ]+/, '')
 }
 
 function fill (headers, object) {
   // To fill a Headers object headers with a given object object, run these steps:
 
diff --git a/test/fetch/headers.js b/test/fetch/headers.js
index 7ae85b81..1f9196c4 100644
--- a/test/fetch/headers.js
+++ b/test/fetch/headers.js
@@ -664,10 +664,22 @@ tap.test('invalid headers', (t) => {
   }, TypeError, 'symbols should throw')
 
   t.end()
 })
 
+tap.test('headers that might cause a ReDoS', (t) => {
+  t.doesNotThrow(() => {
+    // This test will time out if the ReDoS attack is successful.
+    const headers = new Headers()
+    const attack = 'a' + '\t'.repeat(500_000) + '\ta'
+    headers.append('fhqwhgads', attack)
+  })
+
+  t.end()
+})
+
+
 tap.test('Headers.prototype.getSetCookie', (t) => {
   t.test('Mutating the returned list does not affect the set-cookie list', (t) => {
     const h = new Headers([
       ['set-cookie', 'a=b'],
       ['set-cookie', 'c=d']
@@ -680,6 +692,6 @@ tap.test('Headers.prototype.getSetCookie', (t) => {
     t.same(old, now)
     t.end()
   })
 
   t.end()
-})
+})
\ No newline at end of file

Vulnerable code:   return potentialValue.replace(
    /^[\r\n\t ]+|[\r\n\t ]+$/g,
    ''
  )
})
Fixed code: 
  // Trimming the end with `.replace()` and a RegExp is typically subject to
  // ReDoS. This is safer and faster.
  let i = potentialValue.length
  while (/[\r\n\t ]/.test(potentialValue.charAt(--i)));
  return potentialValue.slice(0, i + 1).replace(/^[\r\n\t ]+/, '')
tap.test('headers that might cause a ReDoS', (t) => {
  t.doesNotThrow(() => {
    // This test will time out if the ReDoS attack is successful.
    const headers = new Headers()
    const attack = 'a' + '\t'.repeat(500_000) + '\ta'
    headers.append('fhqwhgads', attack)
  })

  t.end()
})


})