5 minutes
🇬🇧 What is: “A Race Condition Attack”
Table of contents
Prerequisites
none
Introduction
With our current technology, computers are often faster than humans at doing things in general. Today, we’re going to show that with the right timing, this speed becomes pretty useless…
Of course, we are not actually going to be faster than the computer, but let’s dive into Race Condition attacks to better understand what they are and how they work !
First of all, we’ll go through what is known as Time of check and Time of use, then, we’ll illustrate this attack with an example taken from a CTF. In the last part, we’ll briefly see how they can be spotted, and thus avoided.
Time of check, Time of use
Time Of Check To Time Of Use (also known as TOCTOU or TOCTTOU) is a class of software bugs caused by a race condition.
This kind of bugs occur when a resource (such as a file) is checked then modified before it is used thus making the check useless or invalid. They usually happen when the status changes, either maliciously or unintentionally and they often lead to Privilege Escalation, allowing the user to read or modify files they shouldn’t be able to.
They are also very difficult to detect, but we’ll go over that in the last part.
Example
To illustrate my topic, we are going to take a challenge from PicoCTF 2023 called “tic-tac”.
We are given this .cpp
file:
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <sys/stat.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <filename>" << std::endl;
return 1;
}
std::string filename = argv[1];
std::ifstream file(filename);
struct stat statbuf;
// Check the file's status information.
if (stat(filename.c_str(), &statbuf) == -1) {
std::cerr << "Error: Could not retrieve file information" << std::endl;
return 1;
}
// Check the file's owner.
if (statbuf.st_uid != getuid()) {
std::cerr << "Error: you don't own this file" << std::endl;
return 1;
}
// Read the contents of the file.
if (file.is_open()) {
std::string line;
while (getline(file, line)) {
std::cout << line << std::endl;
}
} else {
std::cerr << "Error: Could not open file" << std::endl;
return 1;
}
return 0;
}
If you want to try this code locally, you can copy it and compile it using
g++
This program is going to read the file passed as the first argument. First, it checks the file’s status information, then, it checks if the user is the file’s owner. If both these checks pass, it reads the content of the specified file.
This is a very classic case of Race Condition. Our goal is to read flag.txt
.
Here’s what we are going to do:
- First, create a
fake_flag.txt
file. - Then, make a symbolic link to this file.
- Finally, make a
while
loop that keeps switching the symbolic link between thefake_flag.txt
and theflag.txt
files.
With a little luck, the loop will change the symlink right after the check is passed, and thus the program will read flag.txt
.
Alright, first, let’s create the fake_flag.txt
file and make our symlink.
By doing ls -la
, we can see that our exploit
is pointing to the fake_flag.txt
file.
Then, let’s make our loop and put it in the background:
We can check it is working by running ls -la
again and seeing if the symlink changes.
Now, we could try to launch the process manually but that would be very annoying, so we can also make a loop for that:
This is not mandatory, but it just saves you time because you can’t guess the timing for the check.
Let’s run this, and see if it works:
As you can see, it failed a lot of times but it worked once and that’s all we need !
Here’s a scheme that recaps what happenned (if you can’t see it, right click & open image):
How to avoid these attacks
As I said it in the first part, this kind of attack is hard to detect and to mitigate. The root cause of many TOCTTOU vulnerabilities lies in the lack of concurrency control in an operating system’s file-system API and so it’s not a problem that’s easy to resolve. The challenge therefore is ensuring the file system state, managed by the operating system, cannot change between two system calls.
There is no global patch for these attacks, the first challenge is obviously to identify them, but patching them is rather hard. A technique could be locking the file before it is checked to ensure it’s not getting modified during the process.
Another technique could be to use error handling: we could use a try/catch that would try to read the specified file, and catch the error if the user can’t read it.
Conclusion
Race Condition attacks are theorically simple and easy to understand, but they can be very dangerous because of their difficulty in being identified and the risks to which they expose the system. Hopefully, there are some ways to avoid them, so make sure to secure your program.
Thank you for reading this post, if you have any questions feel free to contact me on discord.
Further documentation
-> https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use
-> https://www.techtarget.com/searchsecurity/answer/How-to-mitigate-the-risk-of-a-TOCTTOU-attack