Friday, April 29, 2011

pCTF 2011 #18 A small bug

Challenge #18 "A small bug" was a common TOCCTOU bug very interesting to exploit reliably.

@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

Category: pwnables

Get access to the key using /opt/pctf/z1/exploitme.



Connect 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*
total 16
-rwxr-sr-x 1 root z1key 15116 Apr 20 20:26 exploitme

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 in this directory using setgid program privileges, we win!

Reversing the main function of the program gives:

decompilation by IDA Pro with Hex-Rays

The program basically takes a string as first argument and:
  1. obtains a temporary file
  2. writes the string in it and unlinks it
  3. 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.


There 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.


Some 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/
Symlink /tmp/chal_irpdv9 -> /opt/pctf/z1key/cron.d/ 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/stalkr
Race won reliably \o/


  1. blocking stdout/err.. cool :)

  2. Wow, actually I'm totally F.A.N thx :]

  3. Translated the exploit to python :)

  4. Haha very cool dude! I updated the post and mirrored your exploit.