A very tough Pyjail + Golf challenge.
Builtins are removed, meaning no top level functions, we're run inside eval, which means no assignments, all letters are blacklisted, as well as quotes and spaces.
The limit for the challenge was set at 102 chars. First, the letter blacklist. This was definitely the easiest, and we just had to use a 'fancy text generator', as Python appears to 'collapse' this text to regular ASCII before the length check but after the blacklist. Our initial method involved traversing the namespace twice, to reach open() and the string 'flag.txt'.
This was over 200 chars, and didn't work due to Python not loading in the function correctly.
We realised we would have to pop a shell.
Traversing to system took us 120 chars, and that was without a string such as sh to run it with.
After a while, I spotted an os function earlier in the tree, and could use that to traverse to the os namespace, calling system from there. After that, it was a simple matter of getting any class, and substringing its hash method to get 'sh'.
Sandboxed NodeJS environment
We figured out we could use this.constructor.constructor("return this.process")() to run more arbritrary JS, but the require function was disabled.
However, we could use this.process.bindings to import the original C++ functions. With some reference to https://gist.github.com/phra/51f73898df729789aff741c6ea91d294 and the node JS source code, I came up with:
this.constructor.constructor("b = Buffer.allocUnsafe(8192);this.process.binding('fs').read(this.process.binding('fs').open('/ctf/flag.txt', 0, 0600, 0, 0), b, 0, 4096, 0, 0, 0); return b")().toString()
Define a buffer of size 8192
Call read() on a file descriptor provided by open('/ctf/flag.txt') in readonly, and read that into the buffer, then return
The zeros are where the docs specified 'undefined' and 'ctf' and luckily this didn't matter. We get the flag(and a bunch of junk)