Race Conditions
Knock knock! Race condition. Who's there? - by vealending
Last updated
Knock knock! Race condition. Who's there? - by vealending
Last updated
This room is about race conditions, as the title states. In the home directories of Walk, Run and Sprint are in each of one a vulnerable SUID binary with its corresponding C source code. The task is to exploit the binary to read the contents of the flags.
Using the provided ssh credentials, we directly log in and check out the home directory of Walk. In this, we'll find the binary anti_flag_reader
with its source code and a flag, owned by Walk.
Running the binary without any parameters, it's describing its usage.
Running the binary with the flag as a parameter reveals that it is checking the provided file path and if the file is a symbolic link. To progress, the user has to hit enter.
After hitting enter, it refuses to give us the flag. Obviously, the file path contained the flag.
Just to check with a file that is neither a symlink nor has the word flag
in its file path, it is printing out the content for us.
So, next, we take a look at the source code. We see it is checking for the word flag
in the provided file path (line 21) and if the file is a symbolic link (line 24). The results of the checks are stored in variables, which are then used after the user hit enter (line 29). The explicit check for symbolic links is a big hint for us.
A symbolic link is a file that points to a file or directory
Now, as long as the user doesn't hit enter, it is possible to replace the file and bypass the checks done before. Those are still valid and stored in the variables. This flaw is the so call Time-of-check to time-of-use vulnerability, where a program or system checks the status of something at one time but uses that information at a later time.
So, the vulnerability arises because the program assumes that the information obtained during the initial check will remain valid when it is actually used. The time gap between the check and use creates a window of opportunity for the file's state to change, potentially leading to incorrect or insecure behavior.
To abuse this vulnerability, we run the binary with a valid file, and replace it with a symbolic link pointing to the flag of user Walk.
We used the already created file from our initial test. And don't hit enter.
Next, the symbolic link is created in another ssh
session with ln -sf ../walk/flag file
in the home directory of the user race.
ln
make links between files
-s, --symbolic
make symbolic links instead of hard links
-f, --force
remove existing destination files
Hitting enter to continue gives us the flag of user Walk.
While trying to abuse the TOCTOU of the user Walk, the first attempts were done in the /tmp
directory, creating files and symbolic links there for /home/walk/flag
, which were without success. For more information see:
In the case of protected_symlinks
for the /tmp
directory, the feature ensures that only symlinks are followed when the uid of the symlink and follower matches, or when the directory owner matches the symlink’s owner, or outside a sticky world-writable directory such as /tmp
.
This prevents other users or processes from creating malicious symlinks that could be exploited to access or modify sensitive files or directories.
In the home directory of user Run, we'll find the binary cat2
with its source code and a flag, owned by Run.
Running cat2
with an example file, it advertises itself as a more secure version of the popular cat
command by performing additional checks on the user's security context.
Looking at the source code of cat2
, the checks are performed at line 24 by calling the function check_security_context. The function checks if the current user has read permission on that file with access(file_name, R_OK);
after the check usleep
is called, used to suspend the execution of the program for 500 microseconds.
It is almost the same case of a TOCTOU as with the binary of Walk. But this time we have to time the symbolic link creation more correctly. In the time frame after executing the check, and being in the sleep state of the program. The symbolic link has to point to /home/run/flag
.
Before we continue, first we remove our test file file
and write a simple bash script, which creates a valid file owned by race. Running cat2 in the background and after 200 microseconds creating the symbolic link to /home/run/flag
while cat2
is running.
Running the script, we are able to read the contents of /home/run/flag
.
As with the other users, we have the SUID binary bankingsystem
, its corresponding c source code and the flag.
Checking out the source code, it is different to the other two.
It is a simple server application that listens for incoming connections on port 1337. It creates a new thread for each connection received to handle client requests concurrently. The server supports three commands: deposit
, withdraw
, and purchase flag
.
deposit
: the server increments the global variable money
by 10,000.
withdraw
: the server decrements the money
variable by 10,000.
purchase flag
: the server checks if the money
variable is at least 15,000. If so, it sends the contents of the file located at /home/sprint/flag
to the client using the sendfile
function and deducts 15,000 from money
. If the client doesn't have enough money, it sends a message indicating insufficient funds.
Looking at the variable money
we see that is a shared variable. Multiple threads can access and modify this variable concurrently without any synchronization mechanism, which can lead to race conditions.
With each session created, we are only able to add 10,000. But if multiple threads execute the deposit operation simultaneously, they may read the value of money
, modify it, and then update the value independently. This can result in incorrect calculations and an inconsistent final balance.
Similarly, if multiple threads execute the purchase flag operation concurrently and have enough money to purchase the flag, they may all read the value of money
, check the balance, and proceed with the purchase. This can lead to the flag being purchased.
Next, we check out the binary and run it.
Using Nmap to check if the port is open.
Connecting to the machine on port 1337 we are able to deposit and the connection closes.
Next, a script is needed to connect to the application multiple times concurrently to deposit and have them read the value of money
, modify it, and then update the value independently with the chance of reading a value above 0 produced by other threads and getting a deposite above 10000.
Running the script and observing the output we can see that sometimes the balance reached 20000 or 30000. Here we have proof of a race condition as described before and should be able to purchase the flag.
Next, we just add a thread to execute the purchase flag
command to the script.
After running the new script and scrolling trough the output eventually reveals to us the flag of user Sprint.