第一部分:实验描述
该实验的学习任务是理解环境变量是如何影响程序和系统行为的。环境变量是一组动态命名的变量
第二部分:实验任务
2.1 任务一:操作环境变量
在这个任务中,我们研究可以用来设置和取消设置环境变量的命令。我们在seed实验环境中使用Bash。用户使用的默认shell在/etc/passwd文件(每个条目的最后一个字段)中设置。您可以使用命令chsh 将其更改为另一个shell程序(请不要在该实验中实现)。执行以下任务:
- 使用printenv或env命令打印出环境变量。也可以单独打印出某个感兴趣的环境变量的值。例如:PWD,可以用如下命令:”printenv PWD “或者“env | grep PWD”单独打印出来。
- 使用export或者unset命令设置或去掉环境变量。需要注意的是,这两个命令是Bash内部分命令。
2.2 任务二:继承环境变量
在这个任务中,我们学习环境变量是如何通过子进程完成继承机制。在Unix操作系统中,fork()通过负值调用进程建立一个新的进程。新的进程称为子进程,与其父进程完全相同。但是,子进程没有继承父进程的某些特性。(man fork)。在该任务中,我们会了解原坏境变量是否被子进程继承。
- 步骤一:编译并运行程序:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h> extern char **environ; void printenv()
{
int i = ;
while (environ[i] != NULL) {
printf("%s\n", environ[i]);
i++;
}
} void main()
{
pid_t childPid; switch(childPid = fork()) {
case : /* child process */
printenv();
exit();
default: /* parent process */
//printenv();
exit();
}
}运行的结果(保存在child文件中):
- 步骤二:在代码的switch语句中,子进程的情形下,注释掉printenv语句;父进程保持不变。编译并运行程序,将结果保存在child2文件中。
- 步骤三:比较child和child2文件,写出结论。
2.3 任务三:环境变量和execve()
在该任务中,我们研究当通过execve()执行新程序时环境变量如何受到影响。函数execve()调用系统调用来加载新的命令并执行它。该函数不会返回,也咩没有新的过程被创建。相反,调用进程的文本,数据,bss和堆栈被加载的程序覆盖。基本上,execve()函数在调用进程内运行新的程序。我们对环境变量会发生什么感兴趣;他们是否自动继承了新程序?
- 步骤一:编译并运行以下程序。描述观察到的实验结果。该程序简单地调用了/usr/bin/env,该系统调用能够打印出当前进程的环境变量。
- 步骤二:现在,改变execve()函数的参数,描述你观察到的结果。
- 步骤三:请得出关于新程序如何获取其环境变量的结论。
2.4 任务四:环境变量和system()
在该任务中,我们研究当通过system()执行新程序时华环境变量如何受到影响。system()函数被用于执行命令,但是不像execve()那样直接执行一个命令。system()函数实际上执行的是如下命令:“/bin/sh -c command”,例如执行“/bin/sh”,即请求一个shell。
如果你看system()函数的实现,你会看到它使用execl()执行/bin/sh;excel()调用execve(),传递给它的环境变量数组。因此,使用system(),调用进程的环境变量被传递被新的程序/bin/sh。请编译并运行以下程序来验证这一点。
#include <stdio.h>
#include <stdlib.h> int main()
{
system("/usr/bin/env"); return ;
}
2.5 任务五:环境变量和Set-UID程序
Set-UID是一种重要的安全机制。当一个Set-UID程序运行时,它会获得程序所有者的特权。例如,如果程序所有者是root用户,那么任何人运行该程序时,该程序都会获得root用户的特权。Set-UID允许我们做许多有趣的事情,但是当它运行时,会升级用户的权限,会带来很大的风险。尽管Set-UID程序的行为是由代码逻辑决定的,而不是用户,用户能够通过环境变量来影响行为。为了理解Set-UID程序是如何被影响的,让我们首先指出哪些环境变量是否由用户进程的Set-UID程序的进程继承。
- 步骤一:在当前步骤中写一个能够输出所有环境变量的程序
#include <stdio.h>
#include <stdlib.h> extern char **environ; void main()
{
int i = ;
while (environ[i] != NULL) {
printf("%s\n", environ[i]);
i++;
}
}
- 步骤二:编译以上程序,将其权限改为roo权限,使其成为一个Set-UID程序。
- 步骤三:使用一般用户登录终端,使用export命令设置如下环境变量:PATH LD_LIBRARY_PATH ANY_NAME
这些环境变量被设置在终端进程中。运行该Set-UID程序。在终端输入程序的名字后,终端会建立一个子进程,并用该子进程去运行程序。请检查上一步骤中设置的环境变量是否在子进程的shell中。描述观察到的结果。
2.6 任务六:PATH环境变量和Set-UID程序
由于调用了shell程序,所以在set-UID程序中调用system()是非常危险的。这是因为shell程序的实际行为可能受到环境变量的影响,如PATH这些环境变量由恶意用户提供。
通过改变这些变量,恶意用户可以控制Set-UID程序的行为。在Bash中,您可以通过以下方式更改PATH环境变量(此示例将目录/home/seed添加到PATH环境变量的开头):
$ export PATH=/home/seed:$PATH
以下Set-UID程序应该执行/bin/ls命令。但是,程序员只能使用ls命令的相对路径,而不是绝对路径:
int main()
{
system("ls");
return ;
}
编译上述程序,并将其所有者改为root,将其设置为Set-UID程序。你可以让这个Set-UID程序运行你的代码而不是/bin/ls吗?描述和解释你的观察。
任务七:LD_PRELOAD环境变量和Set-UID程序
在这个任务中,我们研究Set-UID程序如何处理环境变量。一些影响动态链接器的环境变量,包括LD_PRELOAD、LD_LIBRARY_PATH和其他LD_*。一个动态装载器/链接器是操作系统的一部分,用于下载并链接可执行文件运行过程中需要的公共库。
ld.so或者ld-linux.so是动态加载器/链接器。在那些影响他们行为的环境变量中,LD_LIBRARY_PATH和LD_PRELOAD是该实验涉及到的两个环境变量。在linux中,LD_LIBRARY_PATH是一个冒号分隔的目录集,首先需要在标准的目录集之间搜索库。LD_PRELOAD指定要在所有其他库之前加载的其他用户指定的共享库列表。在这个任务中,我们只研究LD_PRELOAD。
- 步骤一:首先,我们通过一个正常的程序理解环境变量如何影响动态加载器/链接器的行为。请按照以下步骤执行:
1.我们新建一个动态链接库。命名下面的代码为mulib.c,该程序基本上覆盖了libc中的sleep函数:
#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.用下列命令编译mylib.c:
% gcc -fPIC -g -c mylib.c
% -shared -o libmylib.so.1.0.1 mylib.o -lc
3.设置LD_PRELOAD环境变量:
% export LD_PRELOAD=./libmylib.so.1.0.
4.编译myprog程序,在链接库libmylib.so.1.0.1的相同目录下:
/* myprog.c */
int main()
{
sleep();
return ;
}
- 步骤二:在以下情况中运行myprog程序,观察发生了什么。
1.以普通用户的身份运行myprog程序。
2.以普通用户运行拥有root权限的myprog程序。
3使myprog成为一个Set-UID user1程序,在user2用户(非root用户)中再次设置PD_PRELOAD环境变量,运行myprog程序。
- 步骤三:观察以上三次程序的执行结果,理解导致他们不同的原因。环境变量起了作用。设计实验证明主要因素,并解释第二步中行为的不同。
任务八:使用system()和execve()调用外部程序
虽然system()和execve()都可以用于运行新程序,但是如果在特权程序(如Set-UID程序)中使用,则system()非常危险。我们已经看到PATH环境变量是如何影响system()的行为,因为该变量会影响shell的工作原理。execve()没有问题,因为它没有调用shell。调用shell会导致非常危险的后果,而这与环境变量无关。来看下面这种情况:鲍勃为一家审计机构工作,他需要调查一家公司是否有涉嫌欺诈。为了调查目的,鲍勃需要能够读取该公司Unix系统中的所有文件;另一方面,为了保护系统的完整性,鲍勃不能修改任何文件。为了实现这一目标,系统超级用户文斯写了一个特殊的设置root-UID程序(见下文),然后给出了对鲍勃的可执行权限。该程序要求鲍勃在命令行中键入文件名,然后运行/bin/cat显示了指定的文件。由于程序在root权限下运行,它可以显示鲍勃指定的任何文件。然而,由于程序没有写操作的权限,所以文斯非常确定鲍勃不能使用这个特殊的程序来修改任何文件。
#include <string.h>
#include <stdio.h>
#include <stdlib.h> int main(int argc, char *argv[])
{
char *v[];
char *command; if(argc < ) {
printf("Please type a file name.\n");
return ;
} v[] = "/bin/cat"; v[] = argv[]; v[] = NULL; command = malloc(strlen(v[]) + strlen(v[]) + );
sprintf(command, "%s %s", v[], v[]); // Use only one of the followings.
system(command);
// execve(v[0], v, NULL); return ;
}
- 步骤一:编译上面的程序,赋予其root用户权限,并将其变为SET-UID程序。该程序将会使用system()来调用命令。如果你是鲍勃,你能否打破系统的完整性吗?例如,你可以删除不可写文件吗?
- 步骤二:注释掉system(command)语句,并取消注释execve()语句;程序将使用execve()来调用命令。编译程序,并使其成为Set-UID程序。那么在步骤一中的攻击是否仍然有效?
任务九:权能泄露
遵循最低权限原则,如果不再需要这种特权,Set-UID程序通常会永久放弃其root权限。此外,有时程序需要将其控制权交给用户,在这种情况下,root权限必须被撤销。setuid()系统调用可以用来撤销权限。根据手册,setuid()设置调用进程的有效用户ID。如果调用程序的有效UID是root,真实的UID和保存的set-user-id也被设置“。因此,如果一个有有效UID的set-uid程序没有调用setuid(n),则该进程将成为正常进程,其所有的UID都设置为n。当撤销权限的时候,最常见的错误就是权能泄露。这个进程在获得一些特权的时候可能已经获得了一些特权。当特权降级时,如果程序没有清理这个功能,则它们仍然可以由非特权进程访问。换句话说,虽然进程的有效用户ID变为非特权,但是该进程仍具有特权,因为它具有特权能力。
编译以下程序,将其所有者更改为root,并将其设置为Set-UID程序。以普通用户身份运行程序,并描述您所观察到的内容。文件/etc/zzz是否被修改?
请解释你的观察过程。
#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 == -) {
printf("Cannot open /etc/zzz\n");
exit();
} /* Simulate the tasks conducted by the program */
sleep(); /* 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();
} 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", );
close (fd);
}
}