One of the most depended-upon packages is Socket.IO, which is a framework for event-driven communication in real-time. It can be used for things such as pushing data to clients for real-time updates (e.g., charts and graphs), streaming binary data, and providing instant messaging and collaboration features.
The Socket.IO server runs on top of Node.js, but the Socket.IO client can run on Node.js as well as in browsers. Socket.IO uses XHR (XmlHttpRequest) polling as the initial transport protocol, but upgrades to WebSockets when they are supported. Using Socket.IO client in a server-side application is typical for microservices infrastructure, where one server-side component is talking to another server-side component. In this case, one of the components will be running a Socket.IO server, while others will be Socket.IO clients.
What I found is that when running Socket.IO as a client in Node.js with default configuration, it doesn’t reject connections to TLS-enabled Socket.IO servers with untrusted or invalid certificates. This means that by default, Socket.IO clients in Node.js will not authenticate the server they connect to when TLS is used. The connection can therefore be intercepted by a malicious entity performing a man-in-the-middle attack, resulting in loss of confidentiality and data integrity.
As I ran on the latest version of Node.js and Socket.IO, I found this behavior surprising since Node.js has been validating the server certificate by default since version 0.9.2 released in 2012. Looking closer into the source code, I found that this insecure default behavior is not intentional; rather, it is due to some unfortunate design decisions in the Node.js TLS module and Socket.IO client code. The reason can be found in the subcomponent engine.io-client where the TLS connect option rejectUnauthorized is set to null when it’s not defined in the options passed to the socket:
this.rejectUnauthorized = opts.rejectUnauthorized === undefined ? null : opts.rejectUnauthorized;
Unfortunately, when this option reaches the TLS module in Node.js, it overwrites the default value of rejectUnauthorized from true to null, which evaluates to false when checked in the if-statement handling certificate validation errors. This leads to the client establishing connections to any TLS server, regardless of whether a valid certificate was provided or not.