UofTCTF 2025

Racing 2

Misc

View on GitHub

Analysis

This problems builds on the problem Racing. Once again, we are given the source code for a C program and a machine to connect to via SSH. The machine contained an executable of the source code provided with the setuid flag enabled inside the /challenge folder. It also contained a file flag.txt on the system's root.

Solution

Instead of having a program that reads a file - like in the previous problem - this time the program can write to a file on the path /home/user/permitted. We've seen on the previous problem that we can create a symlink on that location and, essentially, make the program access another file on the system. Since the program has the setuid bit enabled, the program will run as the root user and, therefore, is able to override any file on the system that root would normally have permission to.

However, the program uses the access function to check if the file is accessible by the current user before attempting to write. While the write operation would be successful because the program runs as the super user, the access function will provide us access levels for the current session's user, aptly named user, in this case. If it is a protected file, the program will fail the access check and end.

So there are two things at play here: the moment it checks if the user has proper access to the file, and another where it writes over the contents of that file. This is a case of TOCTOU, that is, time of check, time of update. In a nutshell, this means that there is a moment between the check (our access function call) and the time of update (writing to the file) where the state of the system can change.

In our case here, the moment the program asks for the content to be written on the file, it has already performed the check, but not the update. That means that, on this moment, we can perform another action that changes the /home/user/permitted file into something different, and then resume the execution of the program, so that it operates on the new file. To leave the context of the program and return to our shell session, we can press [Ctrl-Z] .

Therefore, in order to write to a different file we would do something like:

$ touch /home/user/permitted
$ /challenge/chal

Enter text to write: 
[Ctrl-Z] # This takes us back to our shell session

$ rm /home/user/permitted
$ ln -s <FILE TO WRITE> /home/user/permitted
$ fg # This takes us back to the program
<CONTENT TO WRITE>

This will write <CONTENT TO WRITE> to the file <FILE TO WRITE> using our super user permissions.

We still do not know exactly which file we can change that will make us able to access the /flag.txt file. While there are many files we could change on the system, the file /etc/passwd controls the user ids (UID) and group ids (GID) of the users on the system. If we write to this file, we can specify the our current user, "user", is, in fact, the one with UID = 0, which is the one that owns the /flag.txt file.

Therefore, we can run the following:

$ touch /home/user/permitted
$ /challenge/chal

Enter text to write: 
[Ctrl-Z]

$ rm /home/user/permitted
$ ln -s /etc/passwd /home/user/permitted
$ fg
user::0:0:admin:/root/root:/bin/bash

If you look at the contents of /etc/passwd, you will see that it has in fact changed, but if you try to read /flag.txt, you will still get a message cat: /flag.txt: Permission denied. This is because the current UID of the terminal session is still set to the old value, not the one we updated on /etc/passwd. If we try to run bash, we will get a new session, but it will retain the old UID. In order to make it reload the UID value, we need to use:

$ su user

This will give us a new session where our UID is reloaded from /etc/passwd, that is, it is now UID = 0. Finally, we can read /flag.txt :

$ cat /flag.txt
uoftctf{f1nn_mcm155113_15_my_f4v0r173_ch4r4c73r}

Last Updated in 2025
Halifax, NS
Canada