@hellman already did a write-up on this challenge. His exploit reads the file name on stderr and hopes to win the race on symlink creation. But actually there is a way to win the race every time! Let's see that.
18 - A small bug - 250 pts Description Category: pwnables Get access to the key using /opt/pctf/z1/exploitme. ssh firstname.lastname@example.org # NLD59WeaNKCgKPfVuaIA5BNtBzRrEBN
AnalysisConnect to the server and see not only the regular setgid program to exploit but also a directory where program gid can write:
z1_16@a5:~$ ls -l /opt/pctf/z1* /opt/pctf/z1: total 16 -rwxr-sr-x 1 root z1key 15116 Apr 20 20:26 exploitme /opt/pctf/z1key: total 28 drwxrwx--- 2 root z1key 20480 Apr 27 11:09 cron.d -rw-r----- 1 root z1key 30 Feb 5 12:09 key -rw-r--r-- 1 root root 105 Apr 22 18:33 README z1_16@a5:~$ cat /opt/pctf/z1key/README All scripts in cron.d will be executed, then deleted once a minute. A script's filename ends with `.sh`.So if we achieve to write a file.sh in this directory using setgid program privileges, we win!
Reversing the main function of the program gives:
The program basically takes a string as first argument and:
- obtains a temporary file
- writes the string in it and unlinks it
- profiles functions enter and exit
Obtain a temporary file
It first uses tempnam libc function to obtain a random file in /tmp. Note that the manual explicitely warns on race conditions using this function ;) Correct way of dealing with temporary file creation is to use mkstemp, which opens the file for you (race-free) and returns the file descriptor.
Then it checks that this file does not exist using stat system call, and finally prints the temporary file on stderr (fd 2) before returning.
Write string and unlink
Simple fopen/fputs/fclose and unlink.
Remember that unlink - by definition - does not follow symlinks.
The enter profiling function prints on standard output (fd 1) that it is entering a function starting at address X. I do not show the exit profiling function because it does nothing at all.
TOCCTOUThere is an obvious Time-of-check-to-time-of-use (TOCCTOU) bug between:
- the moment the program computes a random temporary filename with get_temp, checks that it does not exist
- and the moment the program opens this file in order to write something in it with write_and_unlink
Hopefully, the program gives us (on stderr) the random filename it has computed. To win this race reliably, we would like to be able to stop program execution between these two moments.
ExploitationSome readers might remember my post about exec race condition exploitations. In this particular case, we are going to use technique #2 to make program block by connecting program's stdout or stderr to a filled blocking pipe. The principle is that, when the other end wants to write on it, its write system call will be blocking (and so program frozen) until there is room in the pipe (that is, until the other end reads from it).
If we connect the filled blocking pipe to stderr, there's a problem: we also want to read the random filename from stderr. And as soon as we read the filename the program continues its execution.
Solution: profiling functions! They write on stdout, so we can simply connect program's stdout to an almost-filled pipe that will get program to block at the right moment.
Dividead's blog post Blocking between execution and main() includes a very good piece of code with blocking pipes that we can reuse for this exploit (and so I did). Thanks dividead!
Full exploit here: colored syntax or plain .c
Update: very cool, @hellman translated it in Python!
z1_16@a5:~$ ./exploit Usage: ./exploit <exploitme> <string> <symlink> z1_16@a5:~$ touch /tmp/stalkr; chmod go-rx,a+w /tmp/stalkr; ls -l /tmp/stalkr -rw--w--w- 1 z1_16 z1users 0 Apr 23 16:15 /tmp/stalkr # we don't want our flag to be stolen ;) z1_16@a5:~$ ./exploit /opt/pctf/z1/exploitme 'cat /opt/pctf/z1key/key >/tmp/stalkr' /opt/pctf/z1key/cron.d/stalkr.sh Symlink /tmp/chal_irpdv9 -> /opt/pctf/z1key/cron.d/stalkr.sh created z1_16@a5:~$ date Sat Apr 23 16:16:02 EDT 2011 z1_16@a5:~$ cat /tmp/stalkr This is the key: FUCKALLOFYOU z1_16@a5:~$ rm -f /tmp/stalkrRace won reliably \o/