文章目录
- 一、实验题目
- 二、实验步骤及结果
- Task1:Manipulating Environment Variables
- Task 2: Inheriting environment variables from parents
- Task 3: Environment variables and execve()
- Task 5: Environment variable and Set-UID Programs
- Task 6: The PATH Environment variable and Set-UID Programs
- Task 7: The LD PRELOAD Environment Variable and Set-UID Programs
- Task8:Invoking External Programs Using system() versus execve()
- Task9:Capability Leaking
一、实验题目
本实验室的学习目标是让学生了解环境变量如何影响程序以及系统行为。环境变量是一组动态命名值,可以影响正在运行的进程将在计算机上运行。大多数操作系统都使用它们,因为它们是1979年引入Unix。尽管环境变量会影响程序行为,但它们是如何实现的这一点很多程序员都不太理解。因此,如果程序使用环境变量程序员不知道它们被使用,程序可能有漏洞。
在本实验室中,学生将了解环境变量是如何工作的,它们是如何从父进程到子进程,以及它们如何影响系统/程序行为。我们特别感兴趣的是如何环境变量影响Set-UID程序的行为,这些程序通常是特权程序。
本实验室涵盖以下主题:
•环境变量
•SET-UID程序
•安全地调用外部程序
•能力泄漏
•动态加载程序/链接器
二、实验步骤及结果
Task1:Manipulating Environment Variables
(1)使用printenv命令输出环境变量输入命令printenv,得到如下结果(截取部分):
使用printenv命令查看PATH环境变量,得到如下结果:
(2)使用export和unset设置或删除环境变量:
① 使用export设置环境变量,使用echo显示,$符号实际作用是将变量转换成字符,方便输出。
② 使用unset删除环境变量
Task 2: Inheriting environment variables from parents
(1)编译C文件,将结果保存为a.out文件将代码保存为demo.c文件并放在桌面。进入桌面路径,编译C文件。
执行保存结果的a.out文件,查看代码的运行结果,发现为各个环境变量的值(截取部分):
(2)按题意,将child process中printenv()注释,将process中parent printenv()取消注释,重新保存编译C文件
执行保存结果的b.out文件,查看代码的运行结果,发现为各个环境变量的值(截取部分)。
(3)比较两者结果:
将a.out、b.out文件的结果分别保存为child和parent文件,再使用diff命令比较,发现两者除了文件名外完全相同。这说明子进程环境变量会继承父环境变量。进一步查阅资料了解到,子进程自父进程继承到进程的资格、环境、堆栈、内存等,但子进程所独有的是不同的父进程号、自己的文件描述符和目录流的拷贝、在tms结构中的系统时间、不继承异步输入和输出等。
Task 3: Environment variables and execve()
(1)编译并运行以下程序。描述观察到的实验结果。该程序简单地调用了/usr/bin/env,该系统调用能够打印出当前进程的环境变量。
保存和编译文件,发现执行结果为空:
查询函数execve()的作用,其调用格式如下:
int execve(const char * filename,char * const argv[],char * const envp[])
第一个参数为一个可执行的有效的路径名。第二个参数系利用数组指针来传递给执行文件,argv是要调用的程序执行的参数序列,也就是我们要调用的程序需要传入的参数。envp则为传递给执行文件的新环境变量数。
所以在此处,我们赋予新进程的环境变量为空,自然印出环境变量结果为空。
(2)把execve()的调用改为以下内容,观察结果
将原语句换为:execve("/usr/bin/env", argv, environ);
重新保存和编译文件,得到如下结果:
(3)从以上实验可以看出,execve()产生的新进程的环境变量又调用时重新赋予,而fork()则是直接继承父进程环境变量。
4.Task 4: Environment variables and system()
(1)保存代码至task4.c并编译得到结果:
(2)查阅资料得system()的调用格式如下:
int system(const char * string)
system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命>令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。
具体个描述为这样三个步骤:调用fork()函数新建一个子进程;在子进程中调用exec函数去执行command;在父进程中调用wait去等待子进程结束。
返回值 =-1:出现错误 =0:调用成功但是没有出现子进程 >0:成功退出的子进程的id 如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),则返回非零值>。如果system()调用成功则最后会返回执行shell命令后的返回值,但是此返回值也有可能为 system()调用/bin/sh失败所返回的127,因此最好能再检查errno 来确认执行成功。
Task 5: Environment variable and Set-UID Programs
(1)在当前进程中打印出所有的环境变量
保存、编译和执行给出的代码,得到如下结果(截取部分),此结果就是当前所有环境变量:
(2)将上述程序的所有权改为root,并使它成为一个Set-UID程序
先切换为root账户,使用chown root:root demo5.c将此c文件权限改为root权限:
(3)使用一般用户登录终端,使用export命令设置如下环境变量:PATH 、LD_LIBRARY_PATH、ANY_NAME:
(4)执行已经赋予Set-UID的task5.c程序,得到如下结果:
可以看到,以上三个被定义的环境变量全部被包括在shell中。
Task 6: The PATH Environment variable and Set-UID Programs
(1)编写代码并保存为task6.c
(2)使用命令将/bin/sh链接到zsh
(3)修改环境变量PATH,编译示例代码
编译发现warning,添加头文件 stdlib.h重新编译
(4)修改所有者为Root,并将其设置为Set-UID程序
(5)运行上述编译好的task6,发现执行的是ls命令:
(6)修改环境变量,使得system调用的shell执行自己设定的程序:
System函数中执行的是ls命令,我们的目的是想要system函数执行的ls命令为我们自己设定的程序,所以我们需要将我们自己设定的程序命名为ls,并且将我们自己设定的程序的路径放入PATH环境变量的开头,因为shell程序执行命令时,如果不提供命令的具体路径,shell程序会按照顺序来查找PATH的每一个目录下是否有同名的可执行文件。
设定的程序是输出Hello world!,我们将其编译并命名为ls,使用pwd查看编译好的ls文件的路径,并将其添加到PATH路径的开头,然后再执行task6:
然后执行./task6文件,也就是我们的Set-UID程序。发现成功输出Hello world!,task6中system执行的是ls命令,但实际上执行了我们自己设定的程序。
(7)运行task6获得一个具有root权限的shell
将/bin/sh 拷贝为当前目录下的文件ls
将当前目录/home/seed添加到环境变量PATH中,然后执行exec就可以获得一个具有root权限的shell:
这里我们可以看到命令行已经变成了 ‘#’,也就是root权限的标志。
(8)解释:因为当前路径下的 ls 文件是我们由/bin/sh复制的,所以执行之后会出现shell,而且因为执行Set-UID程序时,会临时获得root权限,所以是root权限的shell。
Task 7: The LD PRELOAD Environment Variable and Set-UID Programs
(1)Step1:构建一个动态链接库
① 创建 mylib.c
② 编译上述程序并设置 LD_PRELOAD 环境变量
③ 保存并编译 myprog.c 文件,并放在与 libmylib.so.1.0.1 相同目录下
(2)Step2:在四个不同场景下运行 myprog 可执行文件
① 在普通用户下运行 myprog
会执行设定的库中的 sleep 函数,输出字符串,不会执行原来的 sleep 函数sleep1 秒
② 将 myprog 设置为 Set-UID 根程序,在普通用户下执行
正常执行程序,sleep 一秒,然后退出
③ 将 myprog 设置为 Set-UID 根程序,并在 root 下设置 LD_PRELOAD 环境变量并运行
- 以 root 用户运行——会执行设定的库中的 sleep 函数
- 以普通用户运行——会正常执行程序,sleep 一秒,然后退出
④ 将 myprog 设置为 Set-UID user1 程序,并在 seed 用户下设置环境变量
- 在 root 用户下,建立新的用户 ashley
- 设置myprog和环境变量
- 在seed用户下运行set-uid程序,程序正常执行
(3)总结:
导致他们不同的原因就在于LD_PRELOAD环境变量。LD_PRELOAD环境变量是Unix动态链接库的世界中的一个环境变量,它可以影响程序的运行时的链接,它允许你定义在程序运行前优先加载的动态链接库。这个功能主要是用来有选择性的载入不同动态链接库中的相同函数。在该实验中,mylib.c通过sleep函数,生成了一个libmylib.so.1.0.1链接库。然后将该链接库添加到LD_PRELOAD环境变量上。比较这三次实验,第一次和第三次实验myprog程序均具有seed用户权限,而在seed用户的LD_PRELOAD环境变量中也添加了该链接库。
Task8:Invoking External Programs Using system() versus execve()
(1)Step1:
① 编译并设置为 Set-UID 根程序
② 在 root 用户下创建一个 hu目录,在该目录下创建一个名为为
hey.c 的文件,并在 seed 下删除 hey.c,发现权限不够
③ 执行 catall 程序,删除 hey.c 文件
成功删除:
(2)Step2:
① 修改文件如下,重新编译,设置为 Set-UID 根程序
② 运行catall程序,结果如下:
③ 总结:
使用 system()可以成功删除不可写文件,是因为 system 会创建一个子进程,然后子进程会调用一个新的 shell 程序,而且因为 task8 是一个 Set-UID 根程序,所以在执行时会以 root 权限执行删除文件的命令,可以成功删除。
使用 execve()不可以成功删除不可写文件,因为 execve 会执行一个新程序,而不会调用新的 shell 程序,所以将我们输入的参数仅仅当成一个字符串,不会执行命令,所以不能删除不可写文件。
Task9:Capability Leaking
(1)root 下创建一个 etc 文件夹,文件夹内创建 hey 文件,并设置其权限为 0644
(2)修改并编译代码,并设置为set-uid根程序,在seed下运行
(3)编译并设置为 Set-UID 根程序
(4)发现字符成功写入