Environment Variable and Set-UID Program Lab操作系统实验


2021年3月22日

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 seperate programs; they are two of the Bash’s internal commands (you will not be able to find them outside of Bash).
创建
执行结果,成功导入
删除
删除成功

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. Because the output contains many strings, you should save the output into a file, such as using a.out > child (assuming that a.out is your executable file name).

#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();exit(0);  
	default: /* parent process */  
		//printenv();   ➁
		exit(0);  
	}  
}  

Step 2.

Now comment out the printenv() statement in the child process case (Line ➀), and uncomment the printenv() statement in the parent process case (Line ➁). Compile and run the code again, and describe your observation. Save the output in another 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();   
		exit(0);  
	default: /* parent process */  
		printenv();   
		exit(0);  
	}  
}  

Step 3.

Compare the difference of these two files using the diff command. Please draw your conclusion.
执行结果的difference

结论:输出结果相同,父子进程的环境变量相同,子进程会完全继承父进程的环境变量

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 following 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.

#include <stdio.h>  
#include <stdlib.h>  
#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);   
	return 0 ;  
}  

运行结果:无输出

Step 2.

Change the invocation of execve() in Line ➀ to the following; describe your observation. execve("/usr/bin/env", argv, environ);

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
extern char **environ;  
int main()  
{  
	char *argv[2];  
	argv[0] = "/usr/bin/env";  
	argv[1] = NULL;  
	execve("/usr/bin/env", argv, environ);   
	return 0 ;  
}  

运行结果:与“env”指令相同

Step 3.

Please draw your conclusion regarding how the new program gets its environment variables.
man execve

结论:man execve查询得知,execve函数接收三个变量,第一个是打开执行文件,第二个是完整命令,第三个是执行该命令的环境变量。Step1中未导入环境变量,故运行未找到env执行文件,执行失败。Step2中设置全局环境变量environ,execve接收后在环境变量中查找到env并执行。

补充:为什么使用environ而不是main函数自带的envp?
environ是全局变量,若程序执行过程中环境变量发生变化,则上述execve会采用最新的环境变量。envp是main函数运行时导入,若在main函数执行过程中环境变量发生修改,envp并不会修改,则execve函数使用旧版的环境变量,需要putenv(),setenv()重新获取。
关于为什么使用environ而不是envp
关于这点的参考文件
也可以参考这篇文章:链接

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.

#include <stdio.h>  
#include <stdlib.h>  
int main()  
{  
	system("/usr/bin/env");  
	return 0 ;  
}  

运行结果:与“env”指令相同,截图重复不放

system()运行原理

int system(const char * cmdstring)   
{   
	pid_t pid;   
	int status;   
	if(cmdstring == NULL)   
	{   
		return (1); //如果cmdstring为空,返回非零值,一般为1   
	}   
	if((pid = fork())<0)   
	{   
		status = -1; //fork失败,返回-1   
	}   
	else if(pid == 0)   
	{   
		execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);   
		_exit(127); // exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在啦~~   
	}   
	else //父进程   
	{   
		while(waitpid(pid, &status, 0) < 0)   
		{   
			if(errno != EINTR)   
			{   
				status = -1; //如果waitpid被信号中断,则返回-1   
				break;   
			}   
		}   
	}   
	return status; //如果waitpid成功,则返回子进程的返回状态   
}  

与execve()直接执行命令不同,system()实际上执行“ / bin / sh -c命令”,即它执行/bin/sh,将原命令重写为新的字符串命令,并要求shell执行命令。
参考文章

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, then 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 it escalates the user’s privilege when executed, making it 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.

#include <stdio.h>  
#include <stdlib.h>  
extern char **environ;  
void main()  
{  
	int i = 0;  
	while (environ[i] != NULL) {  
		printf("%s\n", environ[i]);  
		i++;  
	}  
}  

运行结果:与“env”指令相同

Step 2.

Compile the above program, change its ownership to root, and make it a Set-UID program
设置为SetUID
test2被设置为SetUID

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.

引入变量

发现root属的test2查找不到LD_LIBRARY_PATH路径,但PATH与自定义路径均查找成功

发现:root属的test2查找不到LD_LIBRARY_PATH路径,但PATH与自定义路径均查找成功
结论:test2被设定为setuid程序,可以提权为root用户,所以在执行env程序时Ubuntu16执行保护程序,将关键路径(LD_LIBRARY_PATH)过滤掉了。

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):
$ export PATH=/home/seed:$PATH
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:

int main()  
{  
	system("ls");  
	return 0;  
}  

Please compile the above program, and change its owner to root, and make it a Set-UID program. Can you let this Set-UID program run your code instead of /bin/ls? If you can, is your code running with the root privilege? Describe and explain your observations.
使用zsh规避Ubuntu16.04保护措施
复制进/tmp
加入路径
进入root的shell

实验思路:

  1. 将自己编译的文件代替/bin/ls,可行的方式就是将该文件的路径搜索优先级提高到/bin/ls前,这里将/bin/zsh代替/bin/sh,以ls命名放入/tmp,并将/tmp路径加入PATH最高优先搜索路径
  2. 由于程序是Set-UID,所以在运行时有root权限,运行上述文件时会进入root的shell

结论:
已知test为setuid程序,通过system()执行ls程序,此时有root权限。临时环境变量指向自己定义的ls程序,而实际是zsh程序。因为root权限执行zsh所以得到了有root权限的shell

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:
#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");  
}  
  1. We can compile the above program using the following commands (in the -lc argument, the second character is `):
% gcc -fPIC -g -c mylib.c 
% gcc -shared -o libmylib.so.1.0.1 mylib.o -lc
  1. Now, set the LD PRELOAD environment variable:
% export LD_PRELOAD=./libmylib.so.1.0.1
  1. Finally, compile the following program myprog, and in the same directory as the above dynamic link library libmylib.so.1.0.1:
/* myprog.c */  
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.

    • Set-UID程序运行
      在这里插入图片描述
    • 非Set-UID程序运行
      在这里插入图片描述
  • Make myprog a Set-UID user1 program (i.e., the owner is user1, which is another user account), export the LD PRELOAD environment variable again in a different user’s account (not-root user) and run it.

    • 其他用户执行,非setuid,函数被重载在这里插入图片描述

    • 其他用户执行,setuid,函数未被重载在这里插入图片描述

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).

发现与结论

当程序设置了setuid,若不是程序的拥有用户,则会略过由拥有用户设置的LD_PRELOAD路径(这里直接执行了系统函数sleep())。当程序不是setuid,无论有效用户是谁,都会执行新的LD_PRELOAD路径。
(说明:ls -l展示的两个用户,第一个是有效用户,第二个是文件拥有者)

这里与task5的实验主旨相似,即setuid程序下的环境变量会被保护。一个setuid程序的在真实用户与有效用户一致时,不会忽略环境变量重载,而当以其他用户的权限执行时会自动忽略一些关键路径,拒绝内部函数重载,保护关键信息。

LD_PRELOAD变量的保护策略

通过静态链接。使用gcc的-static参数可以把libc.so.6静态链入执行程序中。但这也就意味着你的程序不再支持动态链接。
通过设置执行文件的setgid /setuid标志。在有Set-UID权限的执行文件,系统会忽略LD_PRELOAD环境变量。也就是说,如果你有以root方式运行的程序,最好设置上SUID权限.( 如:chmod 4755 test)原文链接

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.

Step 1.

Compile the below program, make it a root-owned Set-UID program. The program will use system() 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?

#include <string.h>  
#include <stdio.h>  
#include <stdlib.h>  
  
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]);  
	  
	system(command);  
	return 0;  
}  

在这里插入图片描述

发现:ttt.c为只读文件,却被test1成功删除

Step 2.

Comment out the system(command) statement, and uncomment the execve() statement; the program will use execve() 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.

#include <string.h>  
#include <stdio.h>  
#include <stdlib.h>  
  
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]);  
	  
	execve(v[0], v,NULL);  
	return 0;  
}  

在这里插入图片描述

发现:step1中的攻击方法失效

结论:

  1. system()函数实现原理见task4。Step1中相当于执行:/bin/cat ./ttt.c;rm ./ttt.c,分号将命令分为两句,但由于都是system()创建的子进程执行,拥有root权限,所以ttt.c文件被成功删除。
  2. execve()函数实现原理见task3。Step2中由于是获得的字符串数组,” ./ttt.c;rm ./ttt.c”被当做一个字符串元素,cat程序执行查找不到这个文件,所以攻击失败。

Task 9: Capability Leaking

To follow the Principle of Least Privilege, Set-UID programs often permanently relinquish their root privileges if such privileges are not needed anymore. Moreover, sometimes, the program needs to hand over its control to the user; in this case, root privileges must be revoked. The setuid() system call can be used to revoke the privileges. According to the manual, “setuid() sets the effective user ID of the calling process. If the effective UID of the caller is root, the real UID and saved set-user-ID are also set”. Therefore, if a Set-UID program with effective UID 0 calls setuid(n), the process will become a normal process, with all its UIDs being set to n.
When revoking the privilege, one of the common mistakes is capability leaking. The process may have gained some privileged capabilities when it was still privileged; when the privilege is downgraded, if the program does not clean up those capabilities, they may still be accessible by the non-privileged process. In other words, although the effective user ID of the process becomes non-privileged, the process is still privileged because it possesses privileged capabilities.
Compile the following program, change its owner to root, and make it a Set-UID program. Run the program as a normal user, and describe what you have observed. Will the file /etc/zzz be modified? Please explain your observation.

#include <stdio.h>  
#include <stdlib.h>  
#include <fcntl.h>  
void main()  
{ 
	int fd;  
	/* Assume that /etc/zzz is an important system file, 
	* and it is owned by root with permission 0644. 
	* Before running this program, you should creat 
	* the file /etc/zzz first. */  
	fd = open("/etc/zzz", O_RDWR | O_APPEND);  
	if (fd == -1) {  
		printf("Cannot open /etc/zzz\n");  
		exit(0);  
	}  
	/* Simulate the tasks conducted by the program */  
	sleep(1);  
	/* After the task, the root privileges are no longer needed, 
	it’s time to relinquish the root privileges permanently. */  
	setuid(getuid()); /* getuid() returns the real uid */  
	if (fork()) { /* In the parent process */  
		close (fd);  
		exit(0);  
	} else { /* in the child process */  
	/* Now, assume that the child process is compromised, malicious 
	attackers have injected the following statements 
	into this process */  
	write (fd, "Malicious Data\n", 15);  
	close (fd);  
	}  
}  

在这里插入图片描述

发现:程序在执行完第19行后已经自行降权为普通用户,但fd句柄因为是在拥有root权限时设置的,仍能打开/etc/zzz,即拥有root权限,造成了内存泄漏。

Summary

重新整理一遍报告,总结得到以下一些收获:

  1. Task1:export与unset,环境变量概念的进入
  2. Task2:父进程与子进程的关系,子进程会完全继承父进程的环境变量
  3. Task3:execve()函数的使用及原理。事实上execve()若执行程序不需要环境变量,第三个参数可以设为NULL,本实验执行env程序,所以需要传入环境变量
  4. 全局变量environ与main函数参数envp的区别
  5. Task4:system()执行/bin/sh,将原命令重写为新的字符串命令,并要求shell执行命令
  6. Task5&7:setuid程序会保护系统关键路径不被其他用户获取,在动态链接库中也会排除其他用户的重载
  7. Task6&8:system()会通过本程序的提权获取root权限,又因为自身创建子进程执行命令的方式,容易造成权限外泄。程序内部调用系统命令时最好使用execve()
  8. Task9:在root权限下打开的文件,在自身完成需要root权限的工作并降权后,应立即关闭,否则会造成内存外泄。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

winnower-sliff

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值