Several days ago I noticed a blog post on the opsecx blog talking about exploiting a RCE (Remote Code Execution) bug in a nodejs module called node-serialize. The blog post explains pretty clearly what's wrong with the module in question but one thing that strikes me is how complex the exploitation process was with Burp. No offence to Burp - it is a great tool - but I think we can do better.
- Php Serialize Array
- Php Serialize Data
- Php Serialize Class
- Js Form Serialize
- Php Serialize Online
- Php Serialize Json
DO NOT serialize data and place it into your database. Serialize can be used that way, but that's missing the point of a relational database. Speed: Despite its complexity, the Plunker editor is designed to load in under 2 seconds. Ease of use: Plunker's features should just work and not. Node mysql: This is a node.js driver for mysql. Serialize Function: The serialize is an inbuilt function PHP that is used to serialize the given array. The serialize function accepts a single parameter which is the data we want to serialize and returns a serialized string. Description Example of php.js unserialize function: In the following web document, unserialize function converts a serialize data to actual format.
Php Serialize Array
In this post I would like to show my take on this particular RCE and also perhaps share additional insight that may prove to be helpful in the future - perhaps in your own research.
Before we begin it will be useful to evaluate the attack surface first. The node-serialize module is modestly used. At the time of writing it had about 2000 downloads per month and 9 dependants without any sub-dependants.
Here is a list of all dependant modules: cdlib, cdlibjs, intelligence, malice, mizukiri, modelproxy-engine-mockjs, node-help, sa-sdk-node, scriby, sdk-sa-node, shelldoc, shoots. Without analysing the code there is no way for identifying if these implementations are also vulnerable, but due to the nature of the vulnerability I will assume they are.
Though, more importantly, we haven't answered the question on how wide-spread this module actually is. 2000 downloads per month could mean many things and it is hard to gauge the number of applications behind this number. A quick look at github and google is the only way to get some answers and this is where things start to get interesting.
A GitHub search echoes 97 potentially vulnerable public modules/applications which are most likely used privately due to not being enlisted on npmjs.com. Skimming through the code provides a sense of understanding how wide-spread (or not) the problem is. I was surprised to find out that it has something to do with Pokémon. Go figure!
I will throw in support for https://nodesecurity.io here because it is the only way to keep on top of situations like this one especially when the NodeJS module system is concerned. It is free for open-source projects.
So far we figured that we are dealing with a bug with somewhat limited potential for abuse, which is good from public safety point of view. Let's move into the more academic side of things and exploit it. In order to test the bug we need a vulnerable application. The opsecx have one so we will use it in this exercise as well. The code is fairly straightforward.
You will need the following pacakge.json file to get it going (do npm install).
So let's skip to the actual thing. As you can see from the code, this example web app is setting a cookie with the user profile, which is a serialised object using the vulnerable node module. This is all encoded in base64. To get an idea what the base64 string looks when unpacked we can get to utilise the ENcoder.
This looks like standard JSON. Looks can be deceiving sometimes. We will get to this later. First, let's setup Rest so that we can test it out. Notice that we are using the Cookie builder to get the correct encoding and that we are utilising the Encode gadget to convert the JSON string into Base64 format.
Now we have a working request which we will convert into an exploit. The first thing to do is to understand how exactly the vulnerability in node-serialize works. Looking at the source code it is pretty evident that the module will serialise functions as shown over here.
The issue will manifestate as soon as we call the unserialize method. The exact line is over here.
This means that if we create a JSON object with an arbitrary parameter which contains a value that begins with
_$$ND_FUNC$$_ we get remote code execution because it will eval. To test this we can use the following setup.
If successful, and it should be successful, you will get an error back because the server will exit before the request is completed. Now we have remote code execution but we can do better.
I find the exploitation technique presented in the opsecx blog a bit crud for my likings. It is perfectly fine for demonstration purposes but given that we have already achieved eval inside a node process there are many things that we can do in order to pull a more elegant hack without the need to involve python and stage the attack. Since we are going to write several large code blocks we may as well modify our working exploit so that it is easier to work with. For that we will use variables. Go into the Variables tab and setup a new variable called code.
This is going to store our code so that we don't have to worry about encodings. Now all we have to do is modify the profile cookie so that the code variable is embedded following the correct encoding for both JSON and the special way node-serialize does functions.
This is beautiful! Now every time we change the code variable the profile cookie payload will dynamically change by keeping the chain of encodings and the node-serialize magic to make it all perfect.
We need to work on our code payload. Assuming that we don't know how the app works, we need a generic way of exploiting it, or for that matter any other application, without prior knowledge of the environment or the setup. This means that we cannot rely on global scope variables that may or may not exist. We cannot rely that the express app is exported therefore it can be accessed for additional routes to be installed. We don't want to spawn new ports or reverse shell in order to keep minimal profile, etc.
This is a big list of requirements to satisfy but after some research it is easy to find a way this could work.
Our journey starts by taking a reference to ServerResponse function from the http module. The prototype of ServerResponse is used as the
__proto__ of response object in expressjs.
Php Serialize Data
This means that if we change the prototype of ServerResponse that will reflect into the
__proto__ of the response. The send method from the response object calls into the ServerResponse prototype.
This means that once the send method is invoked, a call to the end method will be made which happens to be coming from the prototype of ServerResponse. Since the send method is used sufficiently for pretty much anything expressjs related, this also means that we now have a direct way to quickly gain access to more interesting structures such as the current open socket. If we override the end method of the prototype this means that we can get a reference to the socket object from the
The code to achieve this effect will look like this.
Since we are overriding the prototype of end we also need to somehow differentiate our startup request from any other request as this may result in some unexpected behaviour. We will check the query parameter for a special string (abc123) that will tell us that this is our own evil request. This information can be retrieved accessing the httpMessage object from the socket like this.
Now we have everything lined up. What is left is to start the shell. In node this is relatively straightforward.
After we merge both segments, the final code looks like this. Notice how we redirect the end function to spawn a shell within node by reusing the already established socket. This is pure fun!
Now open netcat to localhost 3000 and type the following request
What? That get's you nowhere. This is a little gotcha that I wanted to cover separately. You see, we are hijacking an existing socket and as such we are not the only custodians of the beast. There are other things probably responding to that socket as well so we need to make sure we take care of them. Luckily this is easy to achieve with a bit of knowledge how node sockets work. The final code will look like this.
And finally we are here. Now we can take advantage of this vulnerability whenever we like. A remote shell can be obtained by opening a request with our special string utilising the same server process and established socket.
We started with a simple RCE vulnerability and ended up creating a generic way of spawning a shell across an already established HTTP channel, which should work independently in many types of situations, with a few caveats which I will leave for you to figure out. The best part of the whole thing is that the exploitation process was made simple with the help of Rest, which undoubtedly has been the star of the show in the last several posts: 1, 2, 3.
Php Serialize Class
According to the documentation:'It is critical to select a serialization scheme which is deterministic across executions of the transaction, across platforms, and across versions of the serialization framework. Data structures which don’t enforce ordered serialization (e.g. sets, maps, dicts) should be avoided.'https://sawtooth.hyperledger.org/docs/core/releases/1.2.6/architecture/global_state.html?highlight=deterministic
Thus, storing general JSON data would seem to be a bad idea, as in:
Although JSON.stringify is to a certain extent deterministic (Is JSON.stringify() deterministic in V8?)
Also cbor and protobuff might not be recommended because they do not enforce an ordering:
'The CBOR data model for maps does not allow ascribing semantics tothe order of the key/value pairs in the map representation.'https://tools.ietf.org/html/rfc7049
'By default, repeated invocations of serialization methods on the same protocol buffer message instance may not return the same byte output; i.e. the default serialization is not deterministic.'https://developers.google.com/protocol-buffers/docs/encoding
'Wire format ordering and map iteration ordering of map values is undefined, so you cannot rely on your map items being in a particular order.'https://developers.google.com/protocol-buffers/docs/proto3#maps
Js Form Serialize
Php Serialize Online
Php Serialize Json
However, both approaches seem very limited.