作业题目
本实验室的学习目标是让学生了解环境变量如何影响程序以及系统行为。环境变量是一组动态命名值,可以影响正在运行的进程将在计算机上运行。大多数操作系统都使用它们,因为它们是1979年引入Unix。尽管环境变量会影响程序行为,但它们是如何实现的这一点很多程序员都不太理解。因此,如果程序使用环境变量程序员不知道它们被使用,程序可能有漏洞。
在本实验室中,学生将了解环境变量是如何工作的,它们是如何从父进程到子进程,以及它们如何影响系统/程序行为。我们特别感兴趣的是如何环境变量影响Set-UID程序的行为,这些程序通常是特权程序。
本实验室涵盖以下主题:
•环境变量
•SET-UID程序
•安全地调用外部程序
•能力泄漏
•动态加载程序/链接器
实验步骤及结果
Task 1: Manipulating Environment Variables
使用printenv打印环境变量:
使用printenv PWD打印出当前工作目录的路径:
使用export设置环境变量,unset删除环境变量:
Task 2: Passing Environment Variables from Parent Process to Child Process
fork()函数是Unix和Linux等操作系统中用于创建新进程的系统调用。它创建一个与原进程几乎完全相同的进程,新的进程称为子进程,原来的进程称为父进程。
在父进程中调用fork()函数,它将返回两次。在父进程中,fork()函数返回新创建的子进程的PID。在子进程中,fork()函数返回0。
编译运行题目给的代码myprintenv.c,会打印子进程的环境变量并存入文件file:
file:
创建代码myprintenv2.c,注释掉子进程case中的printenv(),取消父进程case中printenv()的注释,使它打印父进程的环境变量并存入file2:
file2:
对比file和file2:
在48行有不同:
可以看到,可执行文件名不同,但环境变量是相同的。
所以,父进程的环境变量会被子进程继承。
Task 3: Environment Variables and execve()
execve()是一个系统调用,它可以用来执行一个新的进程,并通过替换当前进程的栈、堆、程序段和数据段,用新进程替换当前进程。execve() 会在当前进程中运行新程序。
execve()函数接收3个参数:①运行的指令;②指令用到的参数;③传入新程序的环境变量。
查看示例代码,argv[1]=NULL,也就是说,代码创建一个新程序并不向新程序传递任何环境变量。
编译并执行代码:
没有输出任何环境变量。
修改代码,将execve()的第三个参数修改为environ,environ(全局变量) 是一个指向环境变量字符串数组的指针,这个数组包含了所有当前进程可用的环境变量。也就是说,代码创建一个新程序并向新程序传递当前进程的环境变量。
编译并执行代码:
输出了环境变量。
所以,新进程通过execve()第三个参数的设置(environ)来继承原先进程的环境变量。
Task 4: Environment Variables and system()
system()通过调用“/bin/sh -c command”命令来执行command,它使用execl()来执行/bin/sh,execl()会调用execve()并将环境变量数组传递给它。
所以,使用system(),会将当前进程的环境变量传递给新程序/bin/sh。如果当前进程有root的执行权限,那么得到的shell也会有root权限。
创建代码task.c,验证以上结论:
system()运行了一个新的shell程序,用shell执行“/usr/bin/env”,如果shell继承了调用程序的环境变量,就会输出。
编译并执行task.c:
输出了调用程序的环境变量。
所以,system()将调用进程的环境变量传递给新程序/bin/sh,验证完成。
Task 5: Environment Variable and Set-UID Programs
Set-UID程序是一种特权程序,它通过设置程序所有者为root,提升程序的权限,从而执行一些需要特权才能操作的文件或命令。
创建一个代码文件foo_seed.c:
它会打印当前进程的环境变量。
查看foo_seed.c的权限,它只有普通用户seed的权限:
修改环境变量PATH,LD_LIBRARY_PATH,和任意变量(dinner),并编译执行foo_seed:
打印出我们更改了的变量。
将foo_seed设置为Set-Uid程序,再次运行:
Set-Uid程序并没有继承被修改的LD_LIBRARY_PATH变量。
LD_LIBRARY_PATH主要用于指定查找共享库(动态链接库)时的路径,通过修改LD_LIBRARY_PATH,可以将共享库文件的搜索路径改为一个包含恶意代码的路径,从而在程序运行时执行这些恶意代码。
所以,Set-Uid程序在运行时的链接器或加载器会忽略LD_LIBRARY_PATH。
Task 6: The PATH Environment Variable and Set-UID Programs
创建代码文件task6.c:
编译并运行task6:
task6打印出当前目录下的所有文件。
查看task6的权限:
task6拥有普通用户seed的权限。
将task6变为Set-Uid程序:
在当前目录创建恶意代码bcode.c:
恶意代码会运行一个新的shell。
PATH指定命令的搜索路径,更改环境变量PATH为当前目录,程序会先在当前目录寻找(找到恶意程序)。
编译bcode.c为ls,再次执行task6:
成功执行恶意程序,但shell并不是用root权限运行的。
这是因为,system()首先执行/bin/sh程序,在Ubuntu20.04(以及之前的几个版本)中,/bin/sh实际上是一个指向/bin/dash的符号链接。这个shell程序有一个对策,可以防止自己在Set-UID进程中被执行: 如果dash检测到它是在Set-UID进程中执行的,它会立即将有效用户ID更改为进程的真实用户ID,放弃特权。
将/bin/sh链接到/bin/zsh,再执行task6:
shell以root权限运行。
Task 7: The LD_PRELOAD Environment Variable and Set-UID Programs
创建代码mylib.c(里面有名为sleep的函数):
编译mylib.c,生成一个名为libmylib.so.1.0.1的共享库,并链接C标准库.
创建代码myprog.c(其中使用sleep函数):
修改环境变量LD_PRELOAD为当前目录下我们自己创建的共享库,在普通用户seed下执行myprog:
将myprog设置成Set-Uid程序,再执行:
sleep一秒后结束程序。
在root用户下修改环境变量LD_PRELOAD,并运行myprog:
退出root用户,在普通用户seed下再运行myprog:
sleep一秒后结束程序。
创建一个新的普通用户user1,将myprog设置为Set-UID user1程序:
sleep一秒后结束程序。
猜想原因:myprog一般会继承环境变量,但动态连接器有保护机制,在一些情况下会防止LD_PRELOAD被改变。
解释现象:
- 创建验证代码test.c:
代码打印子进程环境变量。
修改环境变量:
编译执行test.c,将环境变量存进child_1.txt:
更改test.c,打印父进程环境变量:
编译执行test.c,将环境变量存进parent_1.txt:
比较child_1.txt, parent_1.txt,没有不同。
父进程和子进程的LD_PRELOAD都是./libmylib.so.1.0.1
所以,子/父进程都继承了修改的环境变量。
- test.c:打印子进程环境变量
将test设置为Set-Uid程序,将打印结果存入child_2.txt
test.c:打印父进程环境变量
test是普通权限文件,打印结果存入parent_2.txt,比较child_2.txt和parent_2.txt,parent_2.txt中多出LD_PRELOAD.
所以,父进程(普通程序)继承了修改的环境变量,子进程(特权程序)没有。
- 进入root用户,修改环境变量,打印子/父进程的环境变量
比较环境变量,相同,查看txt,子/父进程的环境变量中都有被修改的LD_PRELOAD。
所以,在root用户下运行,父进程和子进程都能继承修改的环境变量。
在seed用户下运行,子进程不能继承。
- 修改环境变量,在seed用户下打印子进程(用户user1的Set-Uid程序)和父进程(用户seed的普通程序)的环境变量。
比较它们的环境变量,父进程继承了改变的环境变量,子进程没有。
总结:当运行进程的真实用户ID与程序的拥有者的用户ID不一致时,进程会忽略掉父进程的LD_PRELOAD环境变量。
Task 8: Invoking External Programs Using system() versus execve()
创建代码catall.c:
编译代码,设置为Set-Uid程序,执行:
修改使/bin/sh指向zsh:
执行catall “aa;/bin/sh”,它实际上执行了两个命令,“/bin/cat/aa”和”/bin/sh”,catall这个特权程序使用shell执行了/bin/sh的命令,用它的root权限运行了一个shell。
通过这个shell就可以执行一些原来不能做的命令。
改变代码,使用execve():
重复上次的操作:
没有生成特权shell。
system()对于数据和代码没有明确的区分,但execve()明确地要求将输入分成代码(第一个参数)和数据(第二个和第三个参数),因此数据输入无法变成代码。
Task 9: Capability Leaking
在root下创建一个etc文件夹,文件夹内创建zzz文件,并设置其权限为0644:
zzz是只读的没有写入任何内容的文件:
创建代码cap_leak.c:
代码尝试以root用户权限打开文件zzz并写入一些数据,然后永久放弃root权限,最后关闭文件。
编译执行代码并将程序设置为特权程序:
查看zzz,已经写入数据:
运行Set-UID程序时,进程会暂时获得root权限,这时打开zzz文件,就会获得root权限下的读写文件、向文件中添加内容的权限,即使后面使用setuid()释放了root权限,但没有释放的进程已经获得的特权功能。
标签:bin,set,uid,程序,Set,进程,root,环境变量 From: https://www.cnblogs.com/wxrwajiez/p/18516118