Earlier this year, we did some research on Socket.IO and evaluate the overall security of the framework. David Johannson of Synopsys took a deep dive into the code and discovered an interesting flaw in Socket.IO that resulted in the ability for an attacker to get in the middle of a secure connection between a Socket.IO server and client allowing them to view intercept the encrypted transactions in the clear or even alter them in transit by using a certificate and masquerading as the Socket.IO server. This attack, commonly known as a “Man-in-the-Middle” or “Meet in the Middle” attack (MitM for short) is not new—as a matter of fact, it’s been around for quite some time.
The premise of encrypted communications such as TLS relies on the ability to authenticate that an endpoint is who is says it is using a certificate signed by a Certificate Authority (CA). In most clients, the default behavior for certificate validation is when a certificate cannot be validated because it’s either not signed by a trusted CA or maybe even not signed at all is to reject the connection and notify the user. You may have noticed occasionally when you visit a site in your browser and get a popup stating that you may not be visiting the site you believe you are visiting; this is a function of certificate validation.
MitM attacks take advantage of unencrypted connections by pretending to be an endpoint (since no one’s verifying their identity anyway). Let’s say Alice is running a Socket.IO client, and she’s trying to connect to Bob’s server. In a MitM attack, Eve will intercept this connection, but Alice and Bob won’t know that. Even though the connection is encrypted, what Alice doesn’t know is that Eve pretended to be Bob, so she is the endpoint for the encrypted communications and simply decrypts, the re-encrypts and passes along to Bob. Alice has no way of knowing that she’s being Evesdropped because no one checked to make sure that Bob was actually Bob.
When David Johansson dug into the Socket.IO client code, he found a mistake in the configuration handling code of the
engine.io-client module. The mistake stemmed from an assumption in the way Node’s TLS module functions when handling the
rejectUnauthorized setting. According to the “Principal of Secure Defaults”, the configuration handling code within the TLS module should be written in such a way that an error results in a secure configuration, however that wasn’t the case. The TLS module in Node.js essentially defaults to an insecure configuration by default, which from a design perspective is exactly the opposite of what you’d want to do when you get a bad certificate. The worst part is that due to the nature of the configuration option, you would never know that your configuration was insecure since when Node is configured to accept invalid certificates it won’t log that certificates are invalid. You can check out the technical details of the flaw in David’s post, Node.js and Socket.IO: How security fails when ‘null’ is ‘false’.
This chain of vulnerable design and improper implementation is difficult to detect in code, unless you have all the dependency code you’re using within the scope of a review and so these types of flaws can often “slip through the cracks” and go unnoticed for days, months, or — even in some cases — years. In a world where bad actors are constantly on the lookout for ways to intercept encrypted communications, these once-overlooked classification of flaw are now at the center of one of the most important and impactful debates in the history of the “Crypto Wars.”
A quick Google search shows around 2,000
package.json files that import the
socket.io-clientlibrary, indicating that there are potentially on the order of hundreds of frameworks, libraries, and open-source applications today that are could be vulnerable to this attack. Given the nature of the design flaw in Node.js that leads to this problem (Principal of Secure Defaults) and the probability that other frameworks using the TLS module are making the same mistake, I would generally rate this a higher likelihood than I may otherwise do. A search on Github for instances where
socket.io-client is imported and no calls (in the same file) are made to set the
Overall, this can serve as a lesson in Secure Software Design and the importance that frameworks play in modern software and especially the impact that a framework vulnerability and a responsive development community can have on the global state of application security.