4.9 KiB
NodeJS deserialization __proto__ abuse
This information was copied from this research: https://www.acunetix.com/blog/web-security-zone/deserialization-vulnerabilities-attacking-deserialization-in-js/
While I was researching packages, I stumbled upon the idea to look at other approaches of attacks on deserialization, which are used in other languages. To achieve code execution we leverage functions with attacker’s controlled data which are called automatically during the deserialization process or after when an application interacts with a newly created object. Something similar to “magic methods” in other languages.
Actually, there are a lot of packages which work completely differently, still after some experiments I came to an interesting semi-universal attack. It is based on two facts.
Firstly, many packages use the next approach in the deserialization process. They create an empty object and then set its properties using square brackets notations:
obj[key]=value
where key and value are taken from JSON
Therefore we as attackers are able to control practically any property of a new object. If we look through the list of properties, our attention comes to the cool __proto__ property . The property is used to access and change a prototype of an object. This means that we can change the object’s behavior and add/change its methods.
Secondly, a call of some function leads to the invoking of the function arguments’ methods. For example, when an object is converted to a string, then methods valueOf, toString of the object are called automatically more details [here](http://2ality.com/2012/03/converting-to-string.html)
. So, console.log(obj)
leads to invocation of obj.toString()
. Another example, JSON.stringify(obj)
internally invokes obj.toJSON().
Using both of these features, we can get remote code execution in process of interaction between an application (node.js)
and an object.
I’ve found a nice example – package Cryo, which supports both function serialization and square bracket notation for object reconstruction, but which isn’t vulnerable to IIFE, because it properly manages object without using `eval&co`
.
Here a code for serialization and deserialization of an object:
cvar Cryo = require('cryo');
var obj = {
testFunc : function() {return 1111;}
};
var frozen = Cryo.stringify(obj);
console.log(frozen)
var hydrated = Cryo.parse(frozen);
console.log(hydrated);
Serialized JSON looks like that. Pretty tangled:
{"root":"_CRYO_REF_1","references":[{"contents":{},"value":"_CRYO_FUNCTION_function () {return 1111;}"},{"contents":{"testFunc":"_CRYO_REF_0"},"value":"_CRYO_OBJECT_"}]}
For our attack we can create a serialized JSON object with a custom __proto__
. We can create our object with our own methods for the object’s prototype, but as a small trick, we can set an incorrect name for __proto__
because we don’t want to rewrite a prototype of the object in our application
and serialize it.
var obj = {
__proto: {
toString: function() {console.log("defconrussia"); return 1111;},
valueOf: function() {console.log("defconrussia"); return 2222;}
}
};
So we get serialized object and rename from __proto
to __proto__
in it:
{"root":"CRYO_REF_3","references":[{"contents":{},"value":"_CRYO_FUNCTION_function () {console.log(\"defconrussia\"); return 1111;}"},{"contents":{},"value":"_CRYO_FUNCTION_function () {return 2222;}"},{"contents":{"toString":"_CRYO_REF_0","valueOf":"_CRYO_REF_1"},"value":"_CRYO_OBJECT"},{"contents":{"proto":"CRYO_REF_2"},"value":"_CRYO_OBJECT"}]}
When we send that JSON payload to an application, the package Cryo deserializes the payload in an object, but also changes the object’s prototype to our value. Therefore, if the application interacts with the object somehow, converts it to a sting, for example, then the prototype’s method will be called and our code will be executed. So, it’s RCE.
I tried to find packages with similar issues, but most of them didn’t support serialization of function. I didn’t find any other way to reconstruct functions in __proto__
. Nevertheless, as many packages use square bracket notation, we can rewrite __proto__
for them too and spoil prototypes of newly created objects. What happens when an application calls any prototype method of such objects? It may crash due to an unhandled TypeError exception.
In addition, I should mention that the whole idea potentially works for deserialization from any format not only JSON
. Once both features are in place, a package is potentially vulnerable. Another thing is that JSON.parse
is not “vulnerable” to __proto__ rewriting
.