CVE-2023-31125 (published 2023-05-08T21:15Z)

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.\n

CVSS v3 base score: 6.5, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-248
cwe_name: Uncaught Exception
description: An exception is thrown from a function, but it is not caught.
extended_description: When an exception is not caught, it may cause the program to crash or expose sensitive information.
         

Fix information:
repo_url: https://github.com/socketio/engine.io
commit_details: repo_url: https://github.com/socketio/engine.io
author: Damien Arrachequesne
author_date: 2023-05-02 01:02:42+02:00
msg: 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
repository: repo_url: https://github.com/socketio/engine.io
repo_name: socketio/engine.io
description: The engine used in the Socket.IO JavaScript server, which manages the low-level transports such as HTTP long-polling and WebSocket. 
owner: socketio
file_changes: filename: server.ts
change_type: ModificationType.MODIFY
diff: @@ -682,7 +682,7 @@ export class Server extends BaseServer {
 
     const res = new WebSocketResponse(req, socket);
     const callback = (errorCode, errorContext) => {
-      if (errorCode) {
+      if (errorCode !== undefined) {
         this.emit("connection_error", {
           req,
           code: errorCode,

programming_language: TypeScript
filename: userver.ts
change_type: ModificationType.MODIFY
diff: @@ -165,7 +165,7 @@ export class uServer extends BaseServer {
     req.res = res;
 
     const callback = async (errorCode, errorContext) => {
-      if (errorCode) {
+      if (errorCode !== undefined) {
         this.emit("connection_error", {
           req,
           code: errorCode,

programming_language: TypeScript
filename: server.js
change_type: ModificationType.MODIFY
diff: @@ -11,6 +11,7 @@ const { ClientSocket, listen, createPartialDone } = require("./common");
 const expect = require("expect.js");
 const request = require("superagent");
 const cookieMod = require("cookie");
+const { WebSocket } = require("ws");
 
 /**
  * Tests.
@@ -197,6 +198,51 @@ describe("server", () => {
           });
       });
     });
+
+    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", () => {

programming_language: TypeScript