Tags: ipython command-injection misc bash 


> We launched a new service today. It's called ENOJAIL™ and gives the user access to an unlimited IPython shell. Sadly, management decided we need to leave our valuable `flag.txt` on the server, so naturally, engineering had to lock it back down a bit. But we are sure you will still enjoy all of the possibilities, such as.. Calculating 5/5 fully interactively, evaluating '1', and so much more!

### What's going on?
It's all about a broken iPython Shell. We observed that some characters were not allowed.
So we tried to print'em all, and got the whitelisted characters:

We also tried some bash commands, and we saw some of them worked (`cd`, `ll`).
`ll` showed us 2 files, `flag.txt` and `enojail.py`.
However, commands with blacklisted characters couldn't be valid.

### The solution
For this reason, we thought about commands with only allowed characters and we found out `head`, `diff` were perfect for printing the flag. Nonetheless, commands like `head` and `diff` didn't work alone. After a few attempts, we observed `ll;` was perfect for command injection:

`ll; head `diff ./ ../` -c 100000000000`
- `ll` is important, we can use it for command injection e.g. `head`
- `head -c 100000000000` shows the first 100000000000 bytes
- `diff` shows the differences between the current directory and the parent dir.
- ```head `diff ./ ../` -c 100000000000``` shows the first 100000000000 byte difference between the parent dir and the current dir, exposing `flag.txt` from the current dir.

Voilà, the flag.

### An interesting script

*We love this pyfuck variant*, so here's the `enojail.py` source code leaked from the challenge.

#!/usr/bin/env python3
import re, sys, signal, datetime
from subprocess import Popen, PIPE
from functools import partial
from socketserver import ForkingTCPServer, BaseRequestHandler

PORT = 5656
REGEX = r"p*[^ &\--=@-P'_-m]*,*"

class RequestHandler(BaseRequestHandler):
def handle(self):
"{}: session for {} started".format(
datetime.datetime.now(), self.client_address[0]
fd = self.request.makefile("rwb", buffering=0)
main(fd, fd, bytes=True)

def main(f_in=sys.stdin, f_out=sys.stdout, bytes=False):
def enc(str):
if bytes:
return str.encode()
return str

def decode(b):
if bytes:
return b.decode()
return b

def alarm_handler(signum, frame):
f_out.write(enc("\nThank you for your visit.\nPlease come back soon. :)\n"))
print("{}: Another timeout reached.".format(datetime.datetime.now()))

if "debug" not in sys.argv:
signal.signal(signal.SIGALRM, alarm_handler)

f_out_no = f_out.fileno()

cat_food = partial(re.compile(r).sub, "")
proc = Popen(
["python3", "-u", "-m", "IPython", "--HistoryManager.enabled=False"],
si = proc.stdin

f"""Welcome to the ENOJAIL™ interactive IPython experience!
You can IPython all you want.
Just please don't look at ./flag.txt, thanks!

Oh, actually, we will filter out some characters before we eval them, just to be on the safe side.
Get started by tying '1' or 1 / 1.

Enjoy your stay! :)\n\n""".encode()

while True:
userinput = cat_food(decode(f_in.readline())).strip()
# forward "sanitized" input to IPython

if __name__ == "__main__":
print("Listening on port {}".format(PORT))
ForkingTCPServer(("", PORT), RequestHandler).serve_forever()


Original writeup (https://wiki-ulisse.fuo.fi/en/CTFs/nullcon-2022/ENOJAIL).