Set-UID Privileged Programs and
Attacks on Them
Contents
The Need for Privileged Programs ...................................................................................... 3
1. The Password Dilemma ......................................................................................... 3
2. Different Types of Privileged Programs ................................................................ 4
The Set-UID Mechanism ..................................................................................................... 5
1. A Superman Story .................................................................................................. 5
2. How It works .......................................................................................................... 5
3. An example of Set-UID Program .......................................................................... 6
4. How to Ensure Its Security .................................................................................... 9
5. The Set-GID Mechanism ....................................................................................... 9
Attack Surfaces of Set-UID Programs .............................................................................. 10
1. User Inputs: Explicit Inputs ................................................................................. 10
2. System Inputs ....................................................................................................... 11
3. Environment Variables: Hidden Inputs ................................................................ 11
4. Capability Leaking ............................................................................................... 11
Invoking Other Programs .................................................................................................. 15
1. Unsafe Approach: Using system() ....................................................................... 15
2. Safe Approach: Using execve() ........................................................................... 16
3. Invoking External Commands in Other Languages ............................................. 18
4. Lessons Learned: Principle of Isolation ............................................................... 18
Principle of Least Privilege ............................................................................................... 20
Summary ............................................................................................................................ 22
The Need for Privileged Programs
1. The Password Dilemma
In Linux, the users’ passwords are stored in /etc/shadow If a user wants to change her/his
password, the shadow file will be eventually modified to store the new passwords.
See the following or use sudo cat /etc/shadow
Another solution is to provide a finer-grained access control mechanism. The operating
System can implement an access control that allows users (non-root) to modify only the
password field of their records. For example:
But sometimes, it can be too restrictive for the users to modify their passwords, so the
operating systems need to “poke a hole” in its protection shell, allowing users to go through
that hole, follow a specific procedure, and make an authorized modification of the shadow
file!
2. Different Types of Privileged Programs
There are two common approaches: daemons(or services in Windows) and Set-UID
programs.
• A daemon is a computer program that runs as a background process (needs a
privileged user ID, such as root).
• Set-UID mechanism, widely adopted in Unix operating Systems, uses a special bit
to mark a program, telling the OS that such a program is special and should be
treated specially when running.
The Set-UID Mechanism
1. A Superman Story
Superman, a strong and protective citizen, wants to retire after millennium years of fighting.
Because of that, he recruits so many people and then gives them suits that he invented, this
suit can give the wearer Superman’s power. To ensure that these superpeople do not use
superpowers to do bad things, Superman conducted comprehensive background checks and
psychological tests on them. Unfortunately, regardless of how thorough they are, once in a
while, some of those went rogue and did bad things. Therefore, he starts to create the 2.0
version that includes an embedded computer chip. When the wearer is said to “go north”,
they will go north, not in other directions.
2. How It works
In a typical computer system, we do not fight evils or save lives, we do need superusers’
power to do routine tasks, such as changing our passwords.
A Set-UID program is just like any other Unix program, except that it has a special marking,
which is a single bit called a Set-UID bit. The purpose of this bit is to tell the OS that when
the program is executed, it should be treated differently than those without such a bit. The
difference is in the user IDs of these processes.
In Unix, a process has three user IDs:
• Real user ID: identifies the real owner of the process.
• Effective user ID: used in access control. This represents what privilege a process
has. For instance, a non-Set-UID program is executed with user ID 5000, its
process’s real and effective user IDs are the same, both being 5000. But it depends
on who owns the program, if it's root, then the program user ID will be 0.
• Saved user ID: it is used to help disable and enable privileges
Now let’s do the copy /bin/id command to print out the user's IDs by doing these following
steps:
We now turn on the Set-UID bit of this program using the “sudo chmod 4755 myid ”. This
code will be later explained, and we can see that the program also prints out the effective
user ID euid; its value is 0, so the process has the root privilege.
If you can’t see the euid, you can try using this command: echo "Effective UID: $EUID”
3. An example of Set-UID Program
We use /bin/cat program to demonstrate how Set-UID programs work. The cat program
basically prints out the content of a specified file! We will make a copy program in our
home directory (userID is seed ), and rename it to mycat . We will run this program to view
the shadow file!
At this step you might need to know how to create a user-seed, so try this:
sudo useradd -m seed
sudo passwd seed
or maybe you want to login as seed, the first initialization will look like this:
sudo -i -u seed
If you don’t know the exact location (where the file exits), it is at /home/seed
In term of password changing (including root or user) try sudo passwd seed
From now on, we will mainly focus on seed so we will continue on:
This happens right? So now we will need a little steps to solve this. First type exit
Fix: Add seed to the sudo group
• Switch to the root user:
su -
• Add seed to the sudo group:
usermod -aG sudo seed
• Log out and log back in as seed for the changes to take effect. The result should
be:
Now let’s continue to copy the cat file by doing these steps (remember to log in as seed by
typing: sudo -i -u seed )
We want to see if we can see the /etc/shadow file from seed user, let’s do ./mycat
/etc/shadow
Let us make one small change before running the program mycat again: we turn on the
Set-UID bit of this program, and run mycat to view the shadow file again! sudo chmod
4755 mycat
If we change the owner back to seed, while keeping the Set-UID bit enabled, the program
will fail. Because it is based on who executes the program, not the one who owns it!
4. How to Ensure Its Security
In principle, the Set-UID mechanism is secure. Although a Set-UID program allows normal
users to escalate their privileges, this is different from directly giving the privileges to users.
In the latter case, normal users can do whatever they want after getting the privileges, while
in the Set-UID case, normal users can only do whatever is included in the program.
Basically, users’ behaviors are restricted!
However, it is not safe to turn all programs into Set-UID programs. For example, turn the
/bin/sh program into a Set-UID program because this program can execute other programs
specified by users!
5. The Set-GID Mechanism
Apply to groups, instead of users. Namely, a process has an effective group ID and a real
group ID, and the effective group ID is used for access control. Because they are very
similar, we will not discuss that in detail!
Attack Surfaces of Set-UID Programs
The attack surface is a sum of the places where the program gets its inputs. These inputs,
if not properly sanitized, may affect the behaviors of the program!
1. User Inputs: Explicit Inputs
A program may explicitly ask users to provide inputs. If the program does not do a good
job sanitizing its inputs, it may become vulnerable! For example, if the input data are
copied into a buffer, it may overflow the buffer, and cause the program to run malicious
code!
Another interesting example is the vulnerability in the earlier version of chsh, which is a
Set-UID program that allows users to change their default shell programs. The default shell
information is stored in the /etc/passwd file! To change it, the password file needs to be
modified; that is why chsh needs to be a Set-UID program, because the password file is
only writable by root!
Sadly, the chsh program did not sanitize the input correctly, and failed to realize that the
input may contain two lines of text. When the program writes the input into the password
file, the first line replaces the shell-name field in the user’s entry, and the second line
replaces the next entry. Since each line in the password file contains the account
information of one user, by creating a new line of text in the new password file, attackers
can essentially create a new account on the system. If attackers put 0s in the third and fourth
(the user ID and group ID fields), they can create a root account!
2. System Inputs
Programs may get inputs from the underlying system. One could think those are safe, but
in reality, it depends on whether they are controllable by untrusted users or not! For
example, a privileged program program may need to write to a file xyz int the /tmp folder,
and the filename is already fixed by the program. Given the name, the target is provided
by the system, so it can’t be the users’ input right? However, the file is inside the world-
writable /tmp folder, so the actual target of the file may be controllable by users!
3. Environment Variables: Hidden Inputs
“The enemy is never more unnerving than when he’s invisible” – K. K. Parker, Devices
and Desires
High potential risks can derived from these hidden inputs from unseeable inputs. One
type of hidden input is environment variable. Environment variables are a set of named
values that can affect the way a process behaves. These variables can be set by users
before running a program, and they are part of the environment in which a program runs.
A closer look at how system() is implemented, we will find out that it does not directly
run the ls command; instead, it first runs the /bin/sh program, and then uses this
program to run ls . Because the full path to ls is not provided, /bin/sh uses the PATH
environment variable to find where the ls command is. Users can change the value of the
PATH environment variable before running the Set-UID program. More specifically, users
can provide a malicious program called ls , and by manipulating the PATH environment
first, and gets executed, instead of the intended /bin/ls program
4. Capability Leaking
A privileged program downgrades itself during its execution, so the process continues as a
non-privileged one! For example, the su program is a privileged Set-UID program,
allowing one user to switch to another user, if the first user knows the second user’s
password, we can already know what will happen!
In other words, although the effective user ID of the process becomes non-privileged, the
process is still privileged because it possesses privileged capabilities.
We use a program to illustrate how capability can be leaked. First, it opens a file /etc/zzz
that is only writable by root. After the file is opened, a file descriptor is created, and the
subsequent operations in the file can be done using a file descriptor. That is a form of
capability because whoever carries it is capable of accessing the corresponding file. In the
second step, the program downgrades its privileges by making its effective user ID (root)
the same as the real user ID, essentially removing the root privilege from the process. In
the third step, the program invokes a shell program.!
By creating /etc/zzz please do: sudo touch /etc/zzz and verify it by typing ls -l /etc/zzz
in seed console!
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
void main()
{
int fd;
char *v[2];
/*
* Assume that /etc/zzz is an important system file, and it is owned by root
with permission 0644.
* Before running this program, you should create the file /etc/zzz.
*/
fd = open ("/etc/zzz", O_RDWR | O_APPEND);
if (fd == -1) {
printf("Cannot open /etc/zzz\n");
exit(0);
}
// Print out the file descriptor value
printf("fd is %d\n", fd);
// Permanently disable the privilege by making the effective uid the same as
the real uid
setuid(getuid());
// Execute /bin/sh
v[0] = "/bin/sh"; v[1] = 0;
execve(v[0], v, 0);
}
Unfortunately, the above program forgets to close the file, so the file descriptor is still valid,
and the process, which does not have privileges, is still capable of writing to /etc/zzz using
the command echo … >&3 where “&3 ” means file descriptor 3.
Now we are going to execute Set-UID program!
Firstly, let’s create a new file in your seed by using:
• touch <newfile> The touch command is used to create an empty file or update the
timestamp of an existing file.
• nano <newfile> The nano command opens the specified file in the Nano text editor,
which is simple and beginner-friendly.
• rm <newfile> or sudo rm <newfile>(in case if the file is protected)
E.g:
Press ctrl + x and press enter key to finish!
We first execute the caplea
Now we compile it to assembly code: gcc -o cap_leak cap_leak.c
We first execute the cap_leak by using ./cap_leak
To fix this problem, just add close(fd) and everything will be solved!
Invoking Other Programs
This action needs to be extremely careful in Set-UID programs, because the privileged
program may end up executing unintended programs provided by users. There is no way
we can restrict the behavior of the Set-UID program. However, users are often required to
provide inputs for the command.
1. Unsafe Approach: Using system()
There are many ways to execute an external command. The easiest way is to use a function
called system(). For example, Mallory works for an auditing agency, and she needs to
investigate a suspected fraud and be able to read all the files in the company’s Unix system.
However, to protect the integrity of the system, Mallory is not allowed to modify any file.
Vince writes a C program to type a file name at the command line, and then it displays any
file Mallory specifies
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
char *cat="/bin/cat";
if (argc<2) {
printf("Please type a file name.\n");
return 1;
}
char *command = malloc(strlen(cat) + strlen(argv[1] + 2));
sprintf(command, "%s %s", cat, argv[1]);
system(command);
return 0;
}
After compiling the above program (let us call catall ), changing its owner to root, and
enabling the Set-UID bit, Vince gives Mallory the executable permission, so she can run
the program to view any file, including those that are only readable to root, such as
/etc/shadow
If we understand how “man system ” command works, here, the system (command)
executes a command by calling “/bin/sh – c command ”. In other words, the command is
not directly executed by the above program; instead, the shell program is executed first,
and then the shell will take command as its input, parse it, and execute whatever command
is specified in it. Unfortunately, Shell is too powerful; it can do many things beyond
executing one single command. For example, in a shell prompt, if we want to type two
commands in one line, we can use a semicolon (;) to separate two commands.
With the above knowledge, she just needs to feed a string “aa; /bin/sh ”. Since “aa ” is just
a random file name, cat complains that the file does not exist, which is not something that
we care about. If the sign changes from $ to # we successfully get the root privilege.
We will go further on this by doing the same steps as shown in capability leaking
2. Safe Approach: Using execve()
Running a shell inside Set-UID programs is extremely dangerous because shell is simply
too powerful. The security of Set-UID programs depends on the proper restriction of its
behaviors; running a powerful shell program inside makes such a restriction very difficult.
A much safer approach is to cut out the “middle man”, and run the command directly
See the following revised program:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
char *v[3];
if (argc < 2) {
printf("Please type a file name.\n");
return 1;
}
v[0] = "/bin/cat"; v[1] = argv[1]; v[2] = 0;
execve(v[0], v, 0);
return 0;
}
Notes on the exec() family of functions. Several other functions, such as execl , exclp ,
execle , execv , execvp , and execvpe , behave similarly to execve . They all belong to
exec() family of functions.
3. Invoking External Commands in Other Languages
The risk of invoking external commands is not limited to C programs, other programming
languages have the same issue. For example, php, java,…
4. Lessons Learned: Principle of Isolation
The difference between system() and execve() reflects an important principle in computer
security:
Principle of data/code Isolation: Data should be clearly isolated from code.
What this implies is that if an input is meant to be used as data, it should be strictly be used
as data, and none of its contents should be used as code (e.g. as the name of a command).
If there is a mixture of data and code in the input, they should be clearly marked, so the
computer systems will not mistakenly treat data as code. In the system () case, users are
supposed to provide a file name, which should be strictly treated as data. However, the
system() function does not support code/data isolation, so attackers can embed a new
command or special characters (another form of code) in the input, leading to unintended
code being executed. The execve () function clearly forces developers to break down their
inputs into code (the first argument) and data (the second and third arguments), so there is
no ambiguity.
There is a cost when applying this principle: the loss of convenience. The system() function
is more convenient to use than execve(), because you just need to put everything in a single
string, as opposed to breaking them up manually into code and data. This kind of cost is
quite normal, as we often say "there is no free lunch for security", which means, to be more
secure usually requires a sacrifice of some degree of convenience. In this case, the sacrifice
is not much, but in many other cases, it may be significant. A real security expert knows
how to balance security and convenience.
Principle of Least Privilege
Every program and every privileged user of the system should operate using the least
amount of privileges necessary to complete the job [Saltzer and Schoeder, 1975].
Most of the tasks performed by a Set-UID program only need a portion of the power from
root, not all, but they are given the full power of root. That is why when they are
compromised, the damage is quite severe. This definitely violates the Principle of Least
Privilege. According to this principle, a privileged program should only be given whatever
power is necessary for it to perform its tasks. Unfortunately, most operating systems do not
provide a sufficient granularity for privileges. For example, earlier Unix operating systems
had only two levels of privileges, root and non-root. To provide a finer granularity, POSIX
capabilities was introduced [Linux Programmer's Manual, 2017a). They partition the
powerful root privilege into a set of less powerful privileges. This way, a privileged
program can be assigned the corresponding POSIX capabilities based on its tasks. Modern
operating systems, such as Android, also provide fine- grained privileges. For example,
Android has more than 100 permissions, each representing a privilege. An Android app
needing to access GPS is only given the location permission, while apps requiring access
to cameras are only given the camera permission.
There is another implication from this principle: if a privileged program does not need
some privileges for part of its execution, it should disable the privileges either temporarily
or permanently, depending on whether the privileges are still needed later on. By doing so,
we can minimize the risk even if there are mistakes in the code.
Set-UID programs can use seteuid() and setuid() to enable/disable their privileges. The
seteuid () call sets the effective user ID of the calling process. When a Set-UID program
uses this call to set the effective user ID to its real user ID, it temporarily disables the
privilege. The program can regain the privilege by calling it again to set its effective user
ID to the privileged user.
It should be noted that disabling privileges does not make a program immune to all the
attacks. Some attacks, such as buffer overflow, involve code injection, i.e., the Set-UID
program is fooled to execute the code injected by attackers. For these attacks, even if the
privileges are disabled temporarily, it does not prevent damages, because the malicious
code can enable the privileges.
To permanently disable a privilege, Set-UID programs need to use setuid(), which also sets
the effective user ID of the calling process, but if the effective user ID of the caller is root,
the real and saved user ID are also set, making it impossible for the process to regain the
privilege. Privileged processes usually use setuid () to downgrade their privilege before
handling the control to a normal user. We have seen an example in Listing 1.1.
Summary
Set-UID is a security mechanism that allows normal users to gain temporary privileges
when executing certain programs, allowing them to do what they cannot do with their own
privileges, such as changing the /etc/shadow file to update their passwords. Because of
the involved privilege escalation, one needs to be very careful when writing Set-UID
programs; if a developer makes a mistake, normal users may be able to conduct
unauthorized actions using the privileges obtained via a Set-UID program. In this chapter,
we have systematically analyzed the risks faced by Set-UID programs, and showed a
variety of vulnerabilities in Set-UID programs and how attackers can exploit them to gain
privileges.