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 the fake_flag.txt and the flag.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.

2023-03-17-182749_1914x325_scrot

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:

2023-03-17-182949_1911x969_scrot

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:

2023-03-17-183348_843x308_scrot

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:

2023-03-17-183510_993x982_scrot

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):

Untitled Diagram drawio(1)

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