CVE-2024-37890 (JavaScript, published 2024-06-17T20:15Z)
Description: value: ws is an open source WebSocket client and server for Node.js. A request with a number of headers exceeding theserver.maxHeadersCount threshold could be used to crash a ws server. The vulnerability was fixed in ws@8.17.1 (e55e510) and backported to ws@7.5.10 (22c2876), ws@6.2.3 (eeb76d3), and ws@5.2.4 (4abd8f6). In vulnerable versions of ws, the issue can be mitigated in the following ways: 1. Reduce the maximum allowed length of the request headers using the --max-http-header-size=size and/or the maxHeaderSize options so that no more headers than the server.maxHeadersCount limit can be sent. 2. Set server.maxHeadersCount to 0 so that no limit is applied.
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/websockets/ws
Files changed:
lib/websocket-server.js
lib/websocket.js
test/websocket-server.test.js
test/websocket.test.js
Commit message: [security] Fix crash when the Upgrade header cannot be read (#2231)

It is possible that the Upgrade header is correctly received and handled
(the `'upgrade'` event is emitted) without its value being returned to
the user. This can happen if the number of received headers exceed the
`server.maxHeadersCount` or `request.maxHeadersCount` threshold. In this
case `incomingMessage.headers.upgrade` may not be set.

Handle the case correctly and abort the handshake.

Fixes #2230


Vulnerable code:
    if (req.headers.upgrade.toLowerCase() !== 'websocket') {
    if (!key || !keyRegex.test(key)) {
    if (res.headers.upgrade.toLowerCase() !== 'websocket') {

Fixed code:
    const upgrade = req.headers.upgrade;
    if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') {
    if (key === undefined || !keyRegex.test(key)) {
    const upgrade = res.headers.upgrade;

    if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') {
    it('fails if the Upgrade header field value cannot be read', (done) => {
      const server = http.createServer();
      const wss = new WebSocket.Server({ noServer: true });

      server.maxHeadersCount = 1;

      server.on('upgrade', (req, socket, head) => {
        assert.deepStrictEqual(req.headers, { foo: 'bar' });
        wss.handleUpgrade(req, socket, head, () => {
          done(new Error('Unexpected callback invocation'));
        });
      });

      server.listen(() => {
        const req = http.get({
          port: server.address().port,
          headers: {
            foo: 'bar',
            bar: 'baz',
            Connection: 'Upgrade',
            Upgrade: 'websocket'
          }
        });

        req.on('response', (res) => {
          assert.strictEqual(res.statusCode, 400);

          const chunks = [];

          res.on('data', (chunk) => {
            chunks.push(chunk);
          });

          res.on('end', () => {
            assert.strictEqual(
              Buffer.concat(chunks).toString(),
              'Invalid Upgrade header'
            );
            server.close(done);
          });
        });
      });
    });

    it('fails if the Upgrade header field value cannot be read', (done) => {
      server.once('upgrade', (req, socket) => {
        socket.on('end', socket.end);
        socket.write(
          'HTTP/1.1 101 Switching Protocols\r\n' +
            'Connection: Upgrade\r\n' +
            'Upgrade: websocket\r\n' +
            '\r\n'
        );
      });

      const ws = new WebSocket(`ws://localhost:${server.address().port}`);

      ws._req.maxHeadersCount = 1;

      ws.on('upgrade', (res) => {
        assert.deepStrictEqual(res.headers, { connection: 'Upgrade' });

        ws.on('error', (err) => {
          assert.ok(err instanceof Error);
          assert.strictEqual(err.message, 'Invalid Upgrade header');
          done();
        });
      });
    });