A couple of weeks ago, I participated in Stripe’s Capture the Flag, a security wargame designed to have you think like
an attacker and understand what it takes to produce a secure program. Participants are given an initial login/password to a server and the objectives are to exploit a set of setuid programs to access the setuid user’s password in their home directory. Starting off as a user called level01, we were given a setuid program source file and the executable that runs on user level02’s permissions. By uncovering exploits in the program, you gain level02’s permissions and accessibility to its password file. This continues until the last user, called capture-the-flag, is reached.
Warning: If most of this looks like greek to you, please skip to the last paragraph.
This was a simple setuid exploit. A line in the code had a call to the system’s date program without an absolute path. I created a simple program called ‘date’ which basically echoed the /home/level02/.password file and I set the environment PATH variable to where my malicious date program resided. I ran the program, and voila!, the password appeared on my screen.
Another simple exploit. This time it was a PHP web app that basically captures your information from a web form and saves your profile in a flat file on the server. It creates cookie on your web browser that stores the file path. The basic attack was to modify the cookie such that it read from /home/level03/.password.
I was feeling rather confident in my skills until I hit this round, however, this was probably the second hardest one of the contest and it took me a good portion of my evening to figure out. The target was a command line program that took in two arguments; an index and a string. Based on the index value, it did a transformation on the string, such as all upper case or all lower case. At first glance, the code looked pretty secure. There were no buffer overflows or unprotected calls to system programs, except in a ‘run(const char* str)’ function that was not called during the runtime. However, I did notice the that index only had an upper bound but no lower bound so I knew I could pass in a negative index and hike through the stack. To summarize what I did, without giving a long winded explanation, I used GDB and memory address arithmetic to figure out the negative index to use to find a buffer to stick in the address of ‘run(const char* str)’. By passing in a soft link to /bin/sh as the string argument, I was able to shell in as level04.
This one was not as elegant than the last one. It was another command line program that basically copied a string from the command line argument into a unprotected buffer. Clearly, this was a buffer overflow exploit. I basically brute force attacked it by pre-padding some shellcode into the string at each iteration until it gave me shell access as level05.
This time, we were given a web service written in Python. It was a simple web service that basically echoed back a string in all upper cases with its response time. From the looks of the source code, my intuition told me the exploit had something to do with the internal serialization and bad regular expressions processing it contained. My intuition was right and after some research into Python’s object serialization, I was able to have any system command de-serialized and executed on the webservice’s internal job queue.
I caught a stomach virus a few days before the contest ended, so I wasn’t able to complete this in time. After I healed, I decided to try it out on my own system. As expected this was the hardest one and I had to actually ask someone on proggit for advice. The command line program took in a path to a file and a guess. Then it would tell you if you've correctly guessed the contents of the file or not. The obvious solution would be to brute force guess the password in capture-the-flag's password file, but I didn't have the CPU horse power to crack the password in a reasonable time. I could have applied some heuristics such as using only letters and numbers, but I didn't know the password length, so I knew this wouldn't fly. The program itself printed a "." to stderr using fprintf() for each character in the guess and I was given a hint that fprintf() uses write(), which blocks if the pipe is full. If the program came across the wrong character, it would immediately fork an echo to inform you were wrong. By researching what the max pipe size is on linux, I was able to block the fprintf's to stderr at the right moments such that I could correctly guess the first character based on the forked echo. I would do this for the next character and then continue until my guess was right. It was definitely a smarter and much quicker brute force attack than the obvious solution.
"Capture the Flag" was a great learning experience in the area of security. I encourage anyone interested in computer security to participate in similar contests. There is an on-going one called "Smash the Stack" that "Capture the Flag" was based upon. In addition, professors from Stanford University will be releasing an online course specific to the topic. If you keep up with the news, it’s clear that the next war will take place on the digital battlefield, if it has not begun already.