CaaSio PSE

ångstromCTF 2022 (250 points)

It’s clam’s newest javascript Calculator-as-a-Service: the CaaSio Please Stop Edition! no but actually please stop I hate jsjails js isn’t a good language stop putting one in every ctf I don’t want to look at another jsjail because if I do I might vomit from how much I hate js and js quirks aren’t even cool or funny or quirky they’re just painful because why would you design a language like this ahhhhhhhhhhhhhhhhhhhhh

It’s just a JavaScript eval jail.

There is a meager attempt at sandboxing with node’s vm, but the documentation makes clear that that isn’t meant to be secure:

The vm module is not a security mechanism. Do not use it to run untrusted code.

We don’t need to be very creative and can just Google stuff like “vm.runInNewContext ctf” to find articles like Sandboxing NodeJS is hard, here’s why, which explain that something like the following code can escape the vm easily.

The main intrigue of this jail lies in the spicy regex check: !/[.\[\]{}\s;`'"\\_<>?:]/.test(input). Before we think more about that, let’s look at what kind of code we want to run in the first place. I looked around and found it somewhat annoying to pop a shell with standard node libraries, but we can just read the file directly:

With our vm.runInNewContext jailbreak, it would look like:

Unfortunately, we are not allowed to use… a lot of characters. The main sources of annoyance are the banning of . and all the quotes, including ' and " and the more modern `; secondarily, the lack of square brackets cuts off a Plan B for attribute access. Things we do have access to include all the alphanumerics, parentheses, commas, and the arithmetic operators.

For a bit, I tried looking at other JavaScript jail writeups for inspiration, and found the very impressive /[a-z().]/, which only allows the characters in that regex. Still, the . in that challenge is very powerful because it granted access to attributes like .length and .constructor. In our challenge, we don’t even have, for example, String.fromCharCode.

Well, let’s look at what standard built-in objects and Node global objects we have access to:

  • eval is obviously good.
  • decodeURI seems quite promising; the % used for URI escapes is not banned by the regex.
  • But the most interesting built-in I was reminded of by these lists was RegExp, even though directly calling it is not as useful.

Of course! JavaScript supports regexp literals, which are delimited by /, which is not banned. The only remaining annoyance is that, when you convert a regexp to a string, it still has the /s — so if you eval a regexp, it’s still going to have the /s and will typically just evaluate to itself.

Fortunately, because this is JavaScript, you can add an integer to a regexp, and it converts both to a string:

What’s more, if you have code that’s a legal expression and you include 1+ and +1 on the sides, you’ll get another legal expression after it’s surrounded by 1/ and /1:

The final exploit

Short and sweet:

This prints the flag (and some 1s that are easy to ignore):

actf{omg_js_is_like_so_quirky_haha}

Alternate approaches

Quite a few alternative approaches were discussed in the official Discord and in Huli’s writeup:

  • There are many other ways to get rid of the regex /s, for example by adding URI-escaped characters that turn them into harmless comments.
  • It’s also possible to use a snappier payload, require('repl').start(), which pops a Node REPL instead of a shell (after which you can pop other things from the REPL).
  • The most different approach that was discussed, which was the author’s intended solution, is using the with statement heavily as a substitute for attribute access. Roughly, inside a with(foo) block, a bare name bar is equivalent to foo.bar; so String.fromCharCode is suddenly viable again. Though with is obscure, I actually saw it in JS Safe 2.0 from Google CTF 2018.

(note: the commenting setup here is experimental and I may not check my comments often; if you want to tell me something instead of the world, email me!)