The Set-UID mechanism in Unix-based systems is indeed a powerful feature, but it can also be a significant security risk if not handled correctly. When a program with the Set-UID bit set is executed, it runs with the permissions of the file's owner, not the user who executed it. This can be useful for programs that need to perform specific tasks that require elevated privileges, but it can also be exploited if there are vulnerabilities in the program.
Environment_Variable_and_SetUID.pdf
Objective: Understand how to view environment variables.
Using env:
- To print all environment variables:
env- To print a specific environment variable, such as
PWD, usinggrep:
env | grep PWDObjective: Learn how to set environment variables.
Using export:
- To set a new environment variable named
MY_VARwith the value "Hello":
export MY_VAR="Hello"- To verify that the variable has been set, we can print it:
echo $MY_VARObjective: Learn how to remove environment variables.
Using unset:
- To remove the environment variable named
MY_VAR:
unset MY_VAR- To verify that the variable has been removed, we will try to print it. We shouldn't see any output:
echo $MY_VARObjective: Understand if the child process inherits environment variables from its parent.
Steps:
- Compile the given
myprintenv.cprogram:
gcc myprintenv.c -o myprintenv- Run the compiled program and save the output to a file:
./myprintenv > file1.txt- Observe the contents of
file1.txt. This file contains the environment variables of the child process.
Objective: Understand the environment variables in the parent process.
Steps:
-
Modify the
myprintenv.cprogram:- Comment out the
printenv();statement in the child process case. - Uncomment the
printenv();statement in the parent process case.
- Comment out the
-
Compile the modified program:
gcc myprintenv.c -o myprintenv- Run the compiled program again and save the output to another file:
./myprintenv > file2.txt- Observe the contents of
file2.txt. This file contains the environment variables of the parent process.
Objective: Determine if there's any difference between the environment variables of the parent and child processes.
Steps:
- Use the
diffcommand to compare the two files:
diff file1.txt file2.txt- Observe the output. If there's no difference between the two files, it means the child process inherits all the environment variables from its parent. If there are differences, they will be displayed.
Conclusion: Based on the results of the diff command, it can be concluded whether the child process inherits its environment variables from the parent process or not. Given the nature of the fork() system call, it is expected that the child process will inherit the environment variables from its parent, so there should be no differences between the two files.
Objective: Understand how environment variables are passed when executing a new program using execve().
Steps:
- Compile the given
myenv.cprogram:
gcc myenv.c -o myenv- Run the compiled program:
./myenv- Observe the output. This will display the environment variables of the current process when the
/usr/bin/envprogram is executed without explicitly passing any environment variables.
Objective: Understand how explicitly passing environment variables affects the executed program.
Steps:
- Modify the
myenv.cprogram by changing the invocation ofexecve()to:
execve("/usr/bin/env", argv, environ);- Compile the modified program:
gcc myenv.c -o myenv- Run the compiled program again:
./myenv- Observe the output. This will display the environment variables of the current process when the
/usr/bin/envprogram is executed with explicitly passing the environment variables.
Based on the observations from steps 1 and 2:
- If the environment variables in both runs are the same, this indicates that by default the
execve()function passes the environment variables of the calling process to the new program, even if we do not pass them explicitly. - If there's a difference between the two runs, it indicates that the environment variables are not passed to the new program unless explicitly provided.
Expected Conclusion: The execve() function, when executed without explicitly passing the environment variables, does not automatically pass them to the new program. However, when the environment variables are explicitly passed as the third argument, the new program inherits them.
Objective: Understand how environment variables are passed when executing a new program using system().
Steps:
-
Write the C Program:
Create a file named
mysystem.cand add the following code:
#include <stdio.h>
#include <stdlib.h>
int main() {
system("/usr/bin/env");
return 0;
}- Compile the Program:
gcc mysystem.c -o mysystem- Run the Compiled Binary:
./mysystemThis will execute the /usr/bin/env program using the system() function and print out the environment variables. Since system() internally uses /bin/sh to execute the given command, the environment variables of the calling process (in this case, our C program) are passed to /bin/sh, which in turn passes them to /usr/bin/env.
By observing the output, we can verify that the environment variables of the calling process are indeed passed to the new program executed via system().
We've been provided with a program that prints out all the environment variables in the current process. The code is:
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
int main() {
int i = 0;
while (environ[i] != NULL) {
printf("%s\n", environ[i]);
i++;
}
return 0;
}Save this code in a file named printenv.c.
Steps:
- Compile the program:
gcc printenv.c -o foo- Change the ownership of the compiled binary to
root:
sudo chown root foo- Make the binary a Set-UID program:
sudo chmod 4755 fooSteps:
- Set the
PATH,LD_LIBRARY_PATH, and a custom environment variable (e.g.,ANY_NAME):
export PATH=$PATH:/home/seed/Desktop
export LD_LIBRARY_PATH=/home/seed/Desktop/Environment%20Variable%20and%20Set-UID%20Lab%0A
export ANY_NAME="This is a custom environment variable"- Run the Set-UID program:
./foo- Observe the output. This will display the environment variables of the process running the Set-UID program.
Expected Observations:
- The Set-UID program should inherit all the environment variables from the calling process (our shell). This means we should see
PATH,LD_LIBRARY_PATH, andANY_NAMEin the output. - However, certain environment variables, especially ones that can influence the behavior of dynamically linked programs like
LD_LIBRARY_PATH, can be a security risk for Set-UID programs. Some systems might clear or modify such variables for Set-UID programs to prevent potential security issues.
Conclusion: By observing the output, we'll understand how environment variables are inherited by Set-UID programs and whether any variables are cleared or modified for security reasons.
Here's the provided program:
#include <stdlib.h>
int main() {
system("ls");
return 0;
}Save this code in a file named setuid_ls.c.
Steps:
- Compile the program:
gcc setuid_ls.c -o setuid_ls- Change the ownership of the compiled binary to
root:
sudo chown root setuid_ls- Make the binary a Set-UID program:
sudo chmod 4755 setuid_lsTo bypass the countermeasure in /bin/dash, link /bin/sh to /bin/zsh:
sudo ln -sf /bin/zsh /bin/shObjective: Trick the Set-UID program into running a malicious program instead of /bin/ls.
Steps:
- Create a malicious program named
ls:
echo 'echo "This is a malicious script!"' > ~/malicious_ls.sh
chmod +x ~/malicious_ls.sh- Modify the
PATHvariable to prioritize the directory containing the maliciousls:
export PATH=~/:$PATH- Run the Set-UID program:
./setuid_lsExpected Observations:
- Instead of running the actual
/bin/lscommand, the Set-UID program will run the maliciouslsscript. - If the malicious code runs with root privileges, it indicates that the Set-UID program is vulnerable to a
PATHmanipulation attack.
Conclusion: Using relative paths in Set-UID programs, especially in conjunction with the system() function, is dangerous. Malicious users can manipulate the PATH environment variable to trick the Set-UID program into running arbitrary code with elevated privileges.
-
Write the Library Code:
Create a file named
mylib.cwith the following content:
#include <stdio.h>
void sleep(int s) {
printf("I am not sleeping!\n");
}- Compile the Library:
gcc -fPIC -g -c mylib.c
gcc -shared -o libmylib.so.1.0.1 mylib.o -lc- Set the
LD_PRELOADEnvironment Variable:
export LD_PRELOAD=./libmylib.so.1.0.1-
Write the Test Program:
Create a file named
myprog.cwith the following content:
#include <unistd.h>
int main() {
sleep(1);
return 0;
}Compile the program:
gcc myprog.c -o myprog- Run as a Regular Program:
./myprogObserve the output. The overridden sleep() function should be called, printing "I am not sleeping!".
- Run as a Set-UID Root Program:
sudo chown root myprog
sudo chmod 4755 myprog
./myprogObserve the output.
- Run as Set-UID Root with
LD_PRELOADin Root Account:
First, switch to the root account:
sudo suSet the LD_PRELOAD variable:
export LD_PRELOAD=./libmylib.so.1.0.1Run the program:
./myprogObserve the output.
- Run as Set-UID for Another User:
Create another user:
sudo adduser user1Change the ownership of the program to user1:
sudo chown user1 myprogSwitch to another user account (not root) and set the LD_PRELOAD:
su user1
export LD_PRELOAD=./libmylib.so.1.0.1Run the program:
./myprogObserve the output.
For the output of the Set-UID (./myprog) program, if it printed "I'm not sleeping!", it means that the LD_PRELOAD environment variable is still in effect, and the program is using the sleep(override) function from the custom library. If it didn't print anything, it indicates that the system ignores the LD_PRELOAD variable for Set-UID programs as a security measure.
- Compile the Program:
gcc catall.c -o catall- Make it a Root-Owned Set-UID Program:
sudo chown root catall
sudo chmod 4755 catall-
Attempt to Compromise System Integrity:
The
system()function invokes the shell to execute the command. This means that if we can inject shell characters or commands into the input, we may be able to execute arbitrary commands with root privileges.Try running the program with a malicious argument:
./catall "; rm somefile"In this example, a semicolon (;) allows us to execute multiple commands. After the cat command runs, the rm command will try to remove somefile. If somefile is a file that we normally wouldn't have permission to delete, but it is deleted, then we have successfully compromised system integrity using the Set-UID program.
For example, let's say we have a file named testfile.txt in our current directory. We can use the catall program to display its software:
./catall testfile.txtIf testfile.txt contains the text "Hello, World!", the program should display:
Hello, World!After creating the file, we can then run the catall program with testfile.txt as the argument.
-
Modify the Program:
Comment out the
system(command)line and uncomment theexecve(v[0], v, NULL);line incatall.c. -
Compile and Set Permissions:
gcc catall.c -o catall
sudo chown root catall
sudo chmod 4755 catall-
Test the Program Again:
Try the same attack as before:
./catall "; rm somefile"With execve(), the program should not interpret the semicolon as a command separator, and thus the rm command should not execute. This demonstrates that execve() is safer than system() in this context.
Using system() in Set-UID programs can be dangerous because it invokes the shell, which can interpret and execute additional commands. This can be exploited by attackers to run arbitrary commands with elevated privileges. On the other hand, execve() directly executes the specified program without invoking a shell, making it less susceptible to command injection attacks.
Objective: Understand the vulnerability of capability leaking in Set-UID programs.
Observations:
The program that the assignment provided demonstrates a classic example of a capability leak. Below is a breakdown of the program:
- The program tries to open the file
/etc/zzzwith read and append permissions. If successful, it will have a file descriptorfdthat points to this file. - It then prints the file descriptor value.
- The program then drops its root privilege by setting its effective user ID to the real user ID.
- Finally, it executes
/bin/shto give us a shell.
The vulnerability here is that even though the program drops its privilege, the file descriptor fd that was opened with root privilege is still valid. This means that any process that inherits this file descriptor can write to the file /etc/zzz, even if it's running with normal user privileges.
To exploit this vulnerability:
-
Run the Set-UID program. we'll get a shell.
-
In the shell, use the file descriptor value (let's say it's
x) that was printed out to write to the file/etc/zzz. we can do this with the following command:echo "malicious data" >&x
Replace
xwith the actual file descriptor value that was printed. -
Exit the shell and check the contents of
/etc/zzz. we should see "malicious data" appended to the file.
This demonstrates that even though the program dropped its root privilege, the capability (in this case, the file descriptor with write permission to a root-owned file) was leaked to the unprivileged shell, allowing a normal user to write to a file they shouldn't have access to.
Code Snippet:
void main()
{
int fd;
...
fd = open("/etc/zzz", O_RDWR | O_APPEND);
...
setuid(getuid());
v[0] = "/bin/sh"; v[1] = 0;
execve(v[0], v, 0);
}
Explanation: This code demonstrates the vulnerability of capability leaking. The program opens a file with root privileges but drops its privileges without closing the file descriptor. This allows a normal user to write to a file they shouldn't have access to.














