Our vulnerable program is 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.
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.
However, 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)!
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 hash value:
$6$jPSpZ3iS84semtGU$DLwyTleAM2Of8NzDrwwNTnuSamJlnTx6NlMgbhPT5L8POT/J1MSCPucOAp1Qt3zRClS2NWT.RksROF9R1XLrn0
ifaccess()
system call grants permission.
Symbolic Link
We will soon exploit this bug with 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.
Task 9
TASK 9:
Creating symbolic link.
For instance, we can create a text file with:
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
:
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 program to create the symbolic link for you. It is inside /User/symlink.c
.
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 literally RACE with the vulnerable_root_prog
to quickly change the userfile.txt
into a symbolic link pointing to /etc/shadow
ONLY on this very specific time window of AFTER the access()
check and BEFORE the fopen()
.
The Attack
/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 & ./symlink userfile.txt /etc/shadow & 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../symlink userfile.txt /etc/shadow
: immediately, executes thesymlink
program to changeuserfile.txt
to point to/etc/shadow
.NEWFILE=
ls -l /etc/shadow: check the file info of
/etc/shadowand store it into variable
NEWFILE`; to be used in the next loop check
Step (3.1) and (3.2) above are racing, and the script terminates when /etc/shadow
has been successfully changed.