[CVE-2023-32695] Socket.IO DoS Trought Javascript Property Manipulation on WebSockets

[CVE-2023-32695] Socket.IO DoS Trought Javascript Property Manipulation on WebSockets

In this blog post, I will show a recent CVE that I discovered that exploits a Denial-of-Service (DoS) vulnerability in the widely used Socket.IO library.

What is Socket.IO?

Socket.IO is one of the most used libraries for JavaScript, it's widely used with Node.js for WebSockets communication:

You can find the source code at https://github.com/socketio/socket.io.

Understanding the Socket.IO's Packet Structure

This is a simple example of a Socket.IO web socket message:

42["eventName", "String Value", {"Object":"value"}]
  1. 42 - The first part of the message is the Socket.IO packet type.

  2. ["eventName", "String Value", {"Object":"value"}] - The second part of the message is the payload data being sent within the custom event. It is an array containing three elements:

    a. "eventName" - The first element of the array will always be the event name defined in the back and front end for the server and client to know what to do with this packet.

    b. "String Value" - This is a string example associated with the event. It can be any data.

    c. {"Object":"value"} - This is an example of object data being sent as part of the event payload.

  3. The second part of the packet (the array) can have many elements as you want, but the back end must be developed to process it, otherwise, it will be useless.

Now that we understand the basics of Socket.IO, we can understand the DoS vulnerability.

The Vulnerability

Socket.IO uses the EventEmitter node library for asynchronous communication between different parts of the code, but a DoS vulnerability occurs because the emit() function is called with unsanitized user input, which allows sending objects instead of strings. This behavior allows attackers to override the toString() function that is automatically called during the execution of the EventEmitter library, causing an unrecoverable crash in the server.

When a WebSocket message is sent to the server, the emitUntyped() function is called, which calls the emit() function from EventEmitter:

The ev argument carries the event name, and the event name can be sent in the first position of the list in the WebSocket message.

The DoS vulnerability occurs because attackers can send JavaScript objects instead of strings in the event name parameter, and when the emit() function from EventEmitter is called, it will crash if the toString() property is overridden in the sent object.

The crash occurs at the following code of EventEmitter (https://github.com/nodejs/node/blob/main/lib/events.js):

When the malicious object from the attacker arrives at const handler = events[type]; (line 505), JavaScript will try to convert the object into a string to get a reference to the events object.

When JavaScript tries to convert an object into a string, the toString() property is automatically called, but if toString is an attribute of the sent object, JavaScript will throw an exception that crashes the server because toString is not a function anymore.

Proof of Concept

In this example, I'm going to use a simple NodeJs server that uses WebSocket with the Socket.IO library. Below is the source code:

const express = require('express');
const http = require('http');
const { Server } = require("socket.io");

const app = express();
const server = http.createServer(app);
const io = new Server(server);

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', (socket) => {
  console.log(`connect ${socket.id}`);

  socket.on('msg', (msg) => {
    console.log(msg)
    io.emit('resp', msg);
  });

  socket.on("disconnect", (reason) => {
    console.log(`disconnect ${socket.id} due to ${reason}`);
  });
});

server.listen(3000, () => {
  console.log('listening on *:3000');
});

To exploit the vulnerability, we need to create a valid WebSocket connection with the server, and then, send the following payload that will exploit the DoS issue:

42[{"toString":"1337"},"1337"]

Conclusion

It was considered a high-severity vulnerability because it can crash a server just by using Socket.IO, since this library is the most used for WebSockets in Node servers, millions of websites can be shot down with a single request.

A fix was released in the latest version of Socket.IO.

References

https://nvd.nist.gov/vuln/detail/CVE-2023-32695

https://security.snyk.io/vuln/SNYK-JS-SOCKETIOPARSER-5596892

https://github.com/advisories/GHSA-cqmj-92xf-r6r9

Did you find this article valuable?

Support Rafael da Costa Santos by becoming a sponsor. Any amount is appreciated!