CVE-2023-31125: Uncaught Exception (CWE-248)
value: Engine.IO is the implementation of transport-based cross-browser/cross-device bi-directional communication layer for Socket.IO. An uncaught exception vulnerability was introduced in version 5.1.0 and included in version 4.1.0 of the `socket.io` parent package. Older versions are not impacted. A specially crafted HTTP request can trigger an uncaught exception on the Engine.IO server, thus killing the Node.js process. This impacts all the users of the `engine.io` package, including those who use depending packages like `socket.io`. This issue was fixed in version 6.4.2 of Engine.IO. There is no known workaround except upgrading to a safe version.

CWE description: An exception is thrown from a function, but it is not caught.
CVSS v3 base score: 6.5
Published: 2023-05-08T21:15Z
Repository: https://github.com/socketio/engine.io
Language: TypeScript
Files changed: lib/server.ts, lib/userver.ts, test/server.js
Commit date: 2023-05-02 01:07:40 +0200
Commit message: fix: prevent crash when provided with an invalid query param

A specially crafted request could lead to the following exception:

> TypeError: Cannot read properties of undefined (reading 'handlesUpgrades')
>    at Server.onWebSocket (build/server.js:515:67)

This bug was introduced in [1], released in version 5.1.0 and included
in version 4.1.0 of the `socket.io` parent package. Older versions are
not impacted.

[1]: https://github.com/socketio/engine.io/commit/7096e98a02295a62c8ea2aa56461d4875887092d

Diff: diff --git a/lib/server.ts b/lib/server.ts
index 160b2d88..a4a4dcf8 100644
--- a/lib/server.ts
+++ b/lib/server.ts
@@ -680,11 +680,11 @@ export class Server extends BaseServer {
   ) {
     this.prepare(req);
 
     const res = new WebSocketResponse(req, socket);
     const callback = (errorCode, errorContext) => {
-      if (errorCode) {
+      if (errorCode !== undefined) {
         this.emit("connection_error", {
           req,
           code: errorCode,
           message: Server.errorMessages[errorCode],
           context: errorContext,
diff --git a/lib/userver.ts b/lib/userver.ts
index 5616f78f..951d3b96 100644
--- a/lib/userver.ts
+++ b/lib/userver.ts
@@ -163,11 +163,11 @@ export class uServer extends BaseServer {
     this.prepare(req, res);
 
     req.res = res;
 
     const callback = async (errorCode, errorContext) => {
-      if (errorCode) {
+      if (errorCode !== undefined) {
         this.emit("connection_error", {
           req,
           code: errorCode,
           message: Server.errorMessages[errorCode],
           context: errorContext,
diff --git a/test/server.js b/test/server.js
index 19dd3fbe..bfda7cd9 100644
--- a/test/server.js
+++ b/test/server.js
@@ -9,10 +9,11 @@ const zlib = require("zlib");
 const { Server, Socket, attach } = require("..");
 const { ClientSocket, listen, createPartialDone } = require("./common");
 const expect = require("expect.js");
 const request = require("superagent");
 const cookieMod = require("cookie");
+const { WebSocket } = require("ws");
 
 /**
  * Tests.
  */
 
@@ -195,10 +196,55 @@ describe("server", () => {
 
             setTimeout(done, 50);
           });
       });
     });
+
+    it("should disallow `__proto__` as transport (polling)", (done) => {
+      const partialDone = createPartialDone(done, 2);
+
+      engine = listen((port) => {
+        engine.on("connection_error", (err) => {
+          expect(err.req).to.be.ok();
+          expect(err.code).to.be(0);
+          expect(err.message).to.be("Transport unknown");
+          expect(err.context.transport).to.be("__proto__");
+          partialDone();
+        });
+
+        request
+          .get(`http://localhost:${port}/engine.io/`)
+          .query({ transport: "__proto__", EIO: 4 })
+          .end((err, res) => {
+            expect(err).to.be.an(Error);
+            expect(res.status).to.be(400);
+            expect(res.body.code).to.be(0);
+            expect(res.body.message).to.be("Transport unknown");
+            partialDone();
+          });
+      });
+    });
+
+    it("should disallow `__proto__` as transport (websocket)", (done) => {
+      const partialDone = createPartialDone(done, 2);
+
+      engine = listen((port) => {
+        engine.on("connection_error", (err) => {
+          expect(err.req).to.be.ok();
+          expect(err.code).to.be(0);
+          expect(err.message).to.be("Transport unknown");
+          expect(err.context.transport).to.be("__proto__");
+          partialDone();
+        });
+
+        const socket = new WebSocket(
+          `ws://localhost:${port}/engine.io/?EIO=4&transport=__proto__`
+        );
+
+        socket.onerror = partialDone;
+      });
+    });
   });
 
   describe("handshake", () => {
     it("should send the io cookie", (done) => {
       listen({ cookie: true }, (port) => {

Vulnerable code:       if (errorCode) {
      if (errorCode) {
Fixed code:       if (errorCode !== undefined) {
      if (errorCode !== undefined) {
const { WebSocket } = require("ws");

    it("should disallow `__proto__` as transport (polling)", (done) => {
      const partialDone = createPartialDone(done, 2);

      engine = listen((port) => {
        engine.on("connection_error", (err) => {
          expect(err.req).to.be.ok();
          expect(err.code).to.be(0);
          expect(err.message).to.be("Transport unknown");
          expect(err.context.transport).to.be("__proto__");
          partialDone();
        });

        request
          .get(`http://localhost:${port}/engine.io/`)
          .query({ transport: "__proto__", EIO: 4 })
          .end((err, res) => {
            expect(err).to.be.an(Error);
            expect(res.status).to.be(400);
            expect(res.body.code).to.be(0);
            expect(res.body.message).to.be("Transport unknown");
            partialDone();
          });
      });
    });

    it("should disallow `__proto__` as transport (websocket)", (done) => {
      const partialDone = createPartialDone(done, 2);

      engine = listen((port) => {
        engine.on("connection_error", (err) => {
          expect(err.req).to.be.ok();
          expect(err.code).to.be(0);
          expect(err.message).to.be("Transport unknown");
          expect(err.context.transport).to.be("__proto__");
          partialDone();
        });

        const socket = new WebSocket(
          `ws://localhost:${port}/engine.io/?EIO=4&transport=__proto__`
        );

        socket.onerror = partialDone;
      });
    });