SEED Labs – Environment Variable and Set-UID Program
???note Copyright © 2006 - 2016 by Wenliang Du. This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. If you remix, transform, or build upon the material, this copyright notice must be left intact, or reproduced in a way that is reasonable to the medium in which the work is being re-published.
## 1. Overview
The learning objective of this lab is for students to understand how environment
variables affect program and system behaviors. Environment variables are a set of
dynamic named values that can affect the way running processes will behave on a
computer. They are used by most operating systems, since they were introduced to
Unix in 1979. Although environment variables affect program behaviors, how they
achieve that is not well understood by many programmers. As a result, if a program
uses environment variables, but the programmer does not know that they are used,
the program may have vulnerabilities.
In this lab, students will understand how environment variables work, how they
are propagated from parent process to child, and how they affect system/program
behaviors. We are particularly interested in how environment variables affect
the behavior of Set-UID programs, which are usually privileged programs.
This lab covers the following topics:
- Environment variables
- Set-UID programs
- Securely invoke external programs
- Dynamic loader/linker
## 2. Lab Tasks
Files needed for this lab are included in [Labsetup.zip](https://seedsecuritylabs.org/Labs_20.04/Files/Environment_Variable_and_SetUID/Labsetup.zip). To download the lab files
to your CloudLab machine, run the followings:
~~~bash
mkdir setuid_lab
cd setuid_lab
wget https://seedsecuritylabs.org/Labs_20.04/Files/Environment_Variable_and_SetUID/Labsetup.zip
unzip Labsetup.zip
cd Labsetup
~~~
???note Task 1: Manipulating Environment Variables
In this task, we study the commands that can be used to set and unset environment
variables. We are using Bash in the seed account. The default shell that a user uses
is set in the `/etc/passwd` file (the last field of each entry). You can change this
to another shell program using the command chsh (please do not do it for this lab). Please do the following tasks:
- Use printenv or env command to print out the environment variables. If you are
interested in some particular environment variables, such as `PWD`, you can use `printenv PWD` or `env | grep PWD`.
- Use export and unset to set or unset environment variables. It should be noted that these two commands are not separate programs; they are two of the Bash’s internal commands
(you will not be able to find them outside of Bash).
???note Task 2: Passing Environment Variables from Parent Process to Child Process
In this task, we study how a child process gets its environment variables from its
parent. In Unix, fork()
creates a new process by duplicating the calling process.
The new process, referred to as the child, is an exact duplicate of the calling
process, referred to as the parent; however, several things are not inherited by
the child (please see the manual of fork()
by typing the following command: man fork
).
In this task, we would like to know whether the parent’s environment variables are
inherited by the child process or not.
- Step 1. Please compile and run the following program, and describe your observation.
The program can be found in the Labsetup folder; it can be compiled using
gcc myprintenv.c
, which will generate a binary called a.out. Let's run it and save the output into a file usinga.out > file
.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
void printenv() {
int i = 0;
while (environ[i] != NULL) {
printf("%s\n", environ[i]);
i++;
}
}
void main() {
pid_t childPid;
switch(childPid = fork()) {
case 0: /* child process */
printenv(); // Line 1
exit(0);
default: /* parent process */
//printenv(); // Line 2
exit(0);
}
}
- Step 2. Now comment out the
printenv()
statement in the child process case (Line 1), and uncomment the printenv() statement in the parent process case (Line 2). Compile and run the code again, and describe your observation. Save the output in another file. - Step 3. Compare the difference of these two files using the diff command. Please draw your conclusion.
???note Task 3: Environment Variables and execve()
In this task, we study how environment variables are affected when a new program is
executed via `execve()`. The function `execve()` calls a system call to load a new command
and execute it; this function never returns. No new process is created; instead, the
calling process’s text, data, bss, and stack are overwritten by that of the program
loaded. Essentially, `execve()` runs the new program inside the calling process. We
are interested in what happens to the environment variables; are they automatically inherited by the new program?
- Step 1. Please compile and run the `myenv.c` program, and describe your observation.
This program simply executes a program called `/usr/bin/env`, which prints out the environment variables of the current process.
~~~c
#include <unistd.h>
extern char **environ;
int main() {
char *argv[2];
argv[0] = "/usr/bin/env";
argv[1] = NULL;
execve("/usr/bin/env", argv, NULL); // Line 1
return 0 ;
}
~~~
- Step 2. Change the invocation of `execve()` in Line 1 to the following; describe
your observation: `execve("/usr/bin/env", argv, environ);`
- Step 3. Please draw your conclusion regarding how the new program gets its
environment variables.
???note Task 4: Environment Variables and system()
In this task, we study how environment variables are affected when a new program
is executed via the system()
function. This function is used to execute a command,
but unlike execve()
, which directly executes a command, system()
actually executes
/bin/sh -c command
, i.e., it executes /bin/sh
, and asks the shell to execute the
command. If you look at the implementation of the system()
function, you will see that
it uses execl()
to execute /bin/sh
; execl()
calls execve()
, passing to it the
environment variables array. Therefore, using system()
, the environment variables of
the calling process is passed to the new program /bin/sh
. Please compile and run the
following program to verify this.
???note Task 5: Environment Variable and Set-UID Programs
Set-UID is an important security mechanism in Unix operating systems. When a Set-UID
program runs, it assumes the owner’s privileges. For example, if the program’s owner
is root, when anyone runs this program, the program gains the root’s privileges during
its execution. Set-UID allows us to do many interesting things, but since it escalates
the user’s privilege, it is quite risky. Although the behaviors of Set-UID programs
are decided by their program logic, not by users, users can indeed affect the behaviors
via environment variables. To understand how Set-UID programs are affected, let us
first figure out whether environment variables are inherited by the Set-UID program’s
process from the user’s process.
- Step 1. Write the following program that can print out all the environment variables
in the current process.
~~~c
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
int main() {
int i = 0;
while (environ[i] != NULL) {
printf("%s\n", environ[i]);
i++;
}
}
~~~
- Step 2. Compile the above program, change its ownership to root, and make it a
Set-UID program.
- Asssume the program’s name is `foo`
~~~bash
$ sudo chown root foo
$ sudo chmod 4755 foo
~~~
- Step 3. In your shell (you need to be in a normal user account, not the root account),
use the `export` command to set the following environment variables (they may have
already exist):
- PATH
- LD LIBRARY PATH
- ANY_NAME (this is an environment variable defined by you, so pick whatever name
you want).
These environment variables are set in the user’s shell process. Now, run the Set-UID
program from Step 2 in your shell. After you type the name of the program in your shell,
the shell forks a child process, and uses the child process to run the program. Please
check whether all the environment variables you set in the shell process (parent) get
into the Set-UID child process. Describe your observation. If there are surprises to
you, describe them.
???note Task 6: The PATH Environment Variable and Set-UID Programs
Because of the shell program invoked, calling system()
within a Set-UID program is
quite dangerous. This is because the actual behavior of the shell program can be
affected by environment variables, such as PATH
; these environment variables are
provided by the user, who may be malicious. By changing these variables, malicious users
can control the behavior of the Set-UID program. In Bash, you can change the PATH
environment variable in the following way (this example adds the directory /home/seed
to the beginning of the PATH environment variable):
The Set-UID program below is supposed to execute the /bin/ls
command; however,
the programmer only uses the relative path for the ls command, rather than the absolute
path:
Please compile the above program, change its owner to root, and make it a Set-UID
program. Can you get this Set-UID program to run your own malicious code, instead of
/bin/ls
? If you can, is your malicious code running with the root privilege? Describe
and explain your observations.
- Note: The
system(cmd)
function executes the/bin/sh
program first, and then asks this shell program to run thecmd
command. In Ubuntu 20.04 (and several versions before),/bin/sh
is actually a symbolic link pointing to/bin/dash
. This shell program has a countermeasure that prevents itself from being executed in a Set-UID process. Basically, if dash detects that it is executed in a Set-UID process, it immediately changes the effective user ID to the process’s real user ID, essentially dropping the privilege. Since our victim program is a Set-UID program, the countermeasure in /bin/dash can prevent our attack. To see how our attack works without such a countermeasure, we will link/bin/sh
to another shell that does not have such a countermeasure. We have installed a shell program called zsh in our Ubuntu 20.04 VM. We use the following commands to link/bin/sh
to/bin/zsh
:sudo ln -sf /bin/zsh /bin/sh
???note Task 7: The LD_PRELOAD Environment Variable and Set-UID Programs
In this task, we study how Set-UID programs deal with some of the environment
variables. Several environment variables, including `LD_PRELOAD`, `LD_LIBRARY_PATH,
and other `LD*` influence the behavior of dynamic loader/linker. A dynamic loader/linker
is the part of an operating system (OS) that loads (from persistent storage to RAM)
and links the shared libraries needed by an executable at run time. In Linux, `ld.so` or
`ld-linux.so`, are the dynamic loader/linker (each for different types of binary).
Among the environment variables that affect their behaviors, `LD_LIBRARY_PATH` and
`LD_PRELOAD` are the two that we are concerned in this lab. In Linux, `LD_LIBRARY_PATH` is
a colon-separated set of directories where libraries should be searched for first,
before the standard set of directories. `LD_PRELOAD` specifies a list of additional,
user-specified, shared libraries to be loaded before all others. In this task, we
will only study `LD_PRELOAD`.
- Step 1. First, we will see how these environment variables influence the behavior
of dynamic loader/linker when running a normal program. Please follow these steps:
- 1. Let us build a dynamic link library. Create the following program, and name it
`mylib.c`. It basically overrides the `sleep()` function in `libc`:
~~~c
#include <stdio.h>
void sleep (int s) {
/* If this is invoked by a privileged program, you can do damages here! */
printf("I am not sleeping!\n");
}
~~~
= 2. We can compile the above program using the following commands (in the `-lc` argument, the second character is lower-case L):
~~~bash
gcc -fPIC -g -c mylib.c
gcc -shared -o libmylib.so.1.0.1 mylib.o -lc
~~
- 3. Now, set the LD PRELOAD environment variable:
~~~bash
export LD_PRELOAD=./libmylib.so.1.0.1
~~~
- 4. Finally, compile the following program `myprog`, and in the same directory as
the above dynamic link library `libmylib.so.1.0.1`:
~~~c
/* myprog.c */
#include <unistd.h>
int main() {
sleep(1);
return 0;
}
~~~
- Step 2. After you have done the above, please run `myprog` under the following
conditions, and observe what happens.
- Make myprog a regular program, and run it as a normal user.
- Make myprog a Set-UID root program, and run it as a normal user.
- Make myprog a Set-UID root program, export the LD PRELOAD environment variable again in
the root account and run it.
- Step 3. You should be able to observe different behaviors in the scenarios described
above, even though you are running the same program. You need to figure out what causes
the difference. Environment variables play a role here. Please design an experiment to
figure out the main causes, and explain why the behaviors in Step 2 are different.
(Hint: the child process may not inherit the `LD_*` environment variables).
???note Task 8: Invoking External Programs Using system() versus execve()
Although system()
and execve()
can both be used to run new programs, system()
is quite dangerous if used in a privileged program, such as Set-UID programs. We have
seen how the PATH
environment variable affect the behavior of system()
, because the
variable affects how the shell works. execve()
does not have the problem, because it
does not invoke shell. Invoking shell has another dangerous consequence, and this time,
it has nothing to do with environment variables. Let us look at the following scenario.
Bob works for an auditing agency, and he needs to investigate a company for a suspected
fraud. For the investigation purpose, Bob needs to be able to read all the files in the
company’s Unix system; on the other hand, to protect the integrity of the system, Bob
should not be able to modify any file. To achieve this goal, Vince, the superuser of
the system, wrote a special set-root-uid program (see below), and then gave the
executable permission to Bob. This program requires Bob to type a file name at the
command line, and then it will run /bin/cat
to display the specified file. Since the
program is running as a root, it can display any file Bob specifies. However, since
the program has no write operations, Vince is very sure that Bob cannot use this
special program to modify any file.
int main(int argc, char *argv[]) {
char *v[3];
char *command;
if(argc < 2) {
printf("Please type a file name.\n");
return 1;
}
v[0] = "/bin/cat"; v[1] = argv[1]; v[2] = NULL;
command = malloc(strlen(v[0]) + strlen(v[1]) + 2);
sprintf(command, "%s %s", v[0], v[1]);
// Use only one of the followings.
system(command);
// execve(v[0], v, NULL);
return 0 ;
}
- Step 1: Save the above program as
catall.c
and compile, make it a root-owned Set-UID program. The program will usesystem()
to invoke the command. If you were Bob, can you compromise the integrity of the system? For example, can you remove a file that is not writable to you? - Step 2: Comment out the
system(command)
statement, and uncomment theexecve()
statement; the program will useexecve()
to invoke the command. Compile the program, and make it a root-owned Set-UID. Do your attacks in Step 1 still work? Please describe and explain your observations.
```
3. Submission
You need to submit a detailed lab report, in PDF format , with screenshots, to describe hat you have done and what you have observed. You also need to provide explanation to the observations that are interesting or surprising. Please also list the important code snippets followed by explanation. Simply attaching code without any explanation will not receive credits.