CVE-2023-23936: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection') (CWE-74)
value: Undici is an HTTP/1.1 client for Node.js. Starting with version 2.0.0 and prior to version 5.19.1, the undici library does not protect `host` HTTP header from CRLF injection vulnerabilities. This issue is patched in Undici v5.19.1. As a workaround, sanitize the `headers.host` string before passing to undici.
CWE description: The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
CVSS v3 base score: 5.4
Published: 2023-02-16T18:15Z
Repository: https://github.com/nodejs/undici
Language: JavaScript
Files changed: lib/core/request.js, test/headers-crlf.js
Commit date: 2023-02-13 12:17:57 +0100
Commit message: Merge pull request from GHSA-5r9g-qh6m-jxff


Diff: diff --git a/lib/core/request.js b/lib/core/request.js
index 7271bc64..adb5bfcb 100644
--- a/lib/core/request.js
+++ b/lib/core/request.js
@@ -302,10 +302,13 @@ function processHeader (request, key, val) {
   if (
     request.host === null &&
     key.length === 4 &&
     key.toLowerCase() === 'host'
   ) {
+    if (headerCharRegex.exec(val) !== null) {
+      throw new InvalidArgumentError(`invalid ${key} header`)
+    }
     // Consumed by Client
     request.host = val
   } else if (
     request.contentLength === null &&
     key.length === 14 &&
diff --git a/test/headers-crlf.js b/test/headers-crlf.js
new file mode 100644
index 00000000..fa90eaad
--- /dev/null
+++ b/test/headers-crlf.js
@@ -0,0 +1,37 @@
+'use strict'
+
+const { test } = require('tap')
+const { Client } = require('..')
+const { createServer } = require('http')
+const EE = require('events')
+
+test('CRLF Injection in Nodejs ‘undici’ via host', (t) => {
+  t.plan(1)
+
+  const server = createServer(async (req, res) => {
+    res.end()
+  })
+  t.teardown(server.close.bind(server))
+
+  server.listen(0, async () => {
+    const client = new Client(`http://localhost:${server.address().port}`)
+    t.teardown(client.close.bind(client))
+
+    const unsanitizedContentTypeInput =  '12 \r\n\r\naaa:aaa'
+
+    try {
+      const { body } = await client.request({
+        path: '/',
+        method: 'POST',
+        headers: {
+          'content-type': 'application/json',
+          'host': unsanitizedContentTypeInput
+        },
+        body: 'asd'
+      })
+      await body.dump()
+    } catch (err) {
+      t.same(err.code, 'UND_ERR_INVALID_ARG')
+    }
+  })
+})

Fixed code:     if (headerCharRegex.exec(val) !== null) {
      throw new InvalidArgumentError(`invalid ${key} header`)
    }
'use strict'

const { test } = require('tap')
const { Client } = require('..')
const { createServer } = require('http')
const EE = require('events')

test('CRLF Injection in Nodejs ‘undici’ via host', (t) => {
  t.plan(1)

  const server = createServer(async (req, res) => {
    res.end()
  })
  t.teardown(server.close.bind(server))

  server.listen(0, async () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    t.teardown(client.close.bind(client))

    const unsanitizedContentTypeInput =  '12 \r\n\r\naaa:aaa'

    try {
      const { body } = await client.request({
        path: '/',
        method: 'POST',
        headers: {
          'content-type': 'application/json',
          'host': unsanitizedContentTypeInput
        },
        body: 'asd'
      })
      await body.dump()
    } catch (err) {
      t.same(err.code, 'UND_ERR_INVALID_ARG')
    }
  })
})