Task 1: Manipulating Environment Variables
Step1. 使用env查看系统变量
Step2. 使用export添加环境变量,可以再子进程中看到testEnv,使用unset移除testEnv,子进程也没有了这个环境变量
Step3. 原因:export和unset都是shell自身的命令,在 shell 中执行程序时,shell 会提供一组环境变量。使用export和unset 可新增,修改或删除环境变量,供后续执行的程序使用,子进程使用env也能看到export设置的环境变量。
Task 2: Passing Environment Variables from Parent Process to Child Process
Step1. 编译如下代码,将该程序运行的输出保存至文件中:
#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);
}
}
Step2. 分别注释掉两处的printenv()函数。编译并再次运行代码,并将输出保存到另一个文件中。
- 注释掉defalut的printenv()并运行改代码,运行结果如下:
- 注释掉case 0的printenv()并运行改代码,运行结果如下:
- 将两次的结果分别保存至a.out 和 b.out中,使用diff对比两文件的区别,结果如下:
可以看到最后一个环境变量有所不同,为各自的文件名,因为两次运行的文件名不一样,所以这个环境变量不一样。重新编译,使得两次的文件名相同,再用diff比较区别,结果如下:
Step3. 分析原因: - 两次结果相同,说明在Fork之后,父子进程有着相同的环境变量。使用man fork查看说明:
- 两次的输出相同,一次是父进程的输出,一次是子进程的输出。结合说明文档,可以看出父子进程有着相同的环境变量。一个进程调 用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同(子进程有自己唯一的进程ID)。相当于克隆了一个自己。
Task 3: Environment Variables and execve()
Step1. 将如下代码编译执行。(这个程序只是执行一个名为/usr/bin/env的程序,它输出当前进程的环境变量。)
#include <stdio.h>
#include <stdlib.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;
}
可以看到没有环境变量的输出。
Step2. 将execve("/usr/bin/env", argv, NULL);改为execve("/usr/bin/env", argv, environ);编译执行,结果如下:
输出了很多环境变量
Step3. 分析原因
- 使用man execve查看帮助文档:
2. execve()父进程中fork一个子进程,在子进程中调用exec函数启动新的程序,用来执行参数filename字符串所代表的文件路径,第二个参数是利用数组指针来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。可以通过execve等函数运行新程序,可以在参数中指定环境变量,这也是step1和step2输出结果有差异的原因。
Task 4: Environment Variables and system()
Step1. 将如下代码编译执行:
#include <stdio.h>
#include <stdlib.h>
int main() {
system("/usr/bin/env");
return 0 ;
}
结果如下:
输出了很多环境变量
Step2. 分析原因
- 使用man System查看帮助文档:
- system()会调用fork()产生子进程,,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程。
结论: System()通过运行shell程序来执行命令,system()会继承环境变量,通过环境变量可能会造成系统安全的问题。
Task 5: Environment Variable and Set-UID Programs
Step1. 将如下代码编译执行。(该程序可以打印出当前进程中的所有环境变量。)
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
void main() {
int i = 0;
while (environ[i] != NULL) {
printf("%s\n", environ[i]);
i++;
}
}
Step2. 编译上述程序,将其所有权更改为root,并使其成为Set-UID程序。
Step3. 在shell中,使用export命令设置以下环境变量:修改已经存在环境变量PATH, LD_LIBRARY_PATH ,并且加入我们自定义的环境变量。
- 添加之前的环境变量:
- 分别添加如下环境变量:
- 运行上面编译好的程序,输出如下:
进一步筛选:
Step3. 结论: 可以看到输出的环境变量中包含PATH 和 TEST_ENV,且环境变量PATH为我们修改后的值。但其中没有我们用 export 命令设置的环境变量LD_LIBRARY_PATH,可能这是系统的一种保护策略,在普通用户的状态下,修改 LD_LIBRARY_PATH,如果这个修改能传递到 set-uid 程序中,那么我们就能通过修改 LD_LIBRARY_PATH 来改变 set-uid程序的库的寻找位置来实施攻击。
Task 6: The PATH Environment Variable and Set-UID Programs
Step1. 示例代码如下:
int main() {
system("ls");
return 0;
}
Step2. 编译,并将用户改成 root,设置 set-uid,再将程序 id 拷贝到 /home/seed/目录下,并重命名为 ls,最后修改环境变量PATH, 添加目录 /home/seed
此时运行上面示例代码编译的程序,结果如下:
可以看到 原本应该执行的是命令 ls,打印文件夹内容和信息,但是由于我们修改了PATH,并提供了一个假冒的程序 ls,实际上是程序 id,所以示例代码实际执行的是程序id,程序 id 是 set-uid 程序运行的。
按理说应该也有 root 权限,但从程序 id 的输出来看,其并没有获得 root 权限。这是因为我用的是 Ubuntu 16.04 VM,其默认的 shell 是 /bin/dash。 Ubuntu 16.04 的 dash 中有一个对抗攻击措施,当 dash 发现自己自己在一个 set-uid程序中执行时,它会把 effective user-id 改成 real user-id, 也就是放弃了特权,所以攻击失败。所以这里先将默认 shell 更改成 zsh,它没有这种防御措施。
执行上述代码后,再次运行示例程序,发现改程序成功获取root权限。
结论:从原理上来说,Set-DID机制是安全的。因为即便Set-UID 程序允许普通用户提升权限,但这和直接赋予普通用户权限还是不同的。在直接赋予权限的情况下,普通用户获得权限后可以执行任何操作。而在使用Set-UID程序的情况下, 普通用户只能执行Set-UID程序中已经定义好的操作。也就是说用户的行为是受限的。但是,并非所有的程序变成Set-UID特权程序后都是安全的。比如改示例种,= system 函数是调用 shell 去执行命令,而 shell 依赖于各种环境变量,这些环境环境变量普通用户可以修改,进而可以影响到 set-uid程序的执行状态,从而产生漏洞。
Task 7: The LD PRELOAD Environment Variable and Set-UID Programs
Step1. 首先,我们将了解这些环境变量在运行正常程序时如何影响动态加载程序/链接程序的行为。
- 创建以下程序,并将其命名为mylib.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");
}
-
使用以下命令编译上述程序
gcc -fPIC -g -c mylib.c
gcc -shared -o libmylib.so.1.0.1 mylib.o -lc
-
设置LDPRELOAD环境变量:
export LD_PRELOAD=./libmylib.so.1.0.1
-
最后,编译以下程序myprog,并在与上述动态链接库libmylib.so相同的目录中
/* myprog.c */ int main()
{ sleep(1); return 0;
}
**Step2.**完成上述操作后,在以下条件下运行myprog。
• 使myprog成为常规程序,并以普通用户身份运行它。
运行结果:输出I am not sleeping!
• 使myprog成为Set-UID根程序,并以普通用户身份运行它。
运行结果:等待1s后结束,没有输出。
• 使myprog成为Set-UID根程序,在root中再次导出LDPRELOAD环境变量并运行它。
运行结果: 没有停顿,直接输出I am not sleeping!
• 使myprog成为Set-UID user1程序,将LDPRELOAD环境变量再次导出到其他用户帐户(非root用户)中并运行它。(user1=tom,user2=seed)
运行结果:停顿1s后, 没有输出。
Step3. 分析原因。
- LD_PRELOAD 环境变量可以影响程序的运行时的链接,它允许你定义在程序运行前优先加载的动态链接库。该功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们 可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。
- LD_LIBRARY_PATH环境变量用于在程序加载运行期间查找动态链接库时指定除了系统默认路径之外的其他路径,LD_LIBRARY_PATH中指定的路径会在系统默认路径之前进行查找。
- real uid 和effective uid不相等,动态链接就不会生效。
Task 8: Invoking External Programs Using system() versus execve()
Step1. 编译一下程序,使其成为根拥有的Set-UID程序。程序将使用system()调用该命令。
#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]);
// Use only one of the followings.
system(command);
// execve(v[0], v, NULL);
return 0;
}
- 编译执行结果如下:(正常运行)
- 在root账户/root 目录下创建一个test.txt文件,切换到seed账户,尝试删除改文件,发现权限不能删除。
- 运行 ./f ./f.c; rm /root/test.txt, 可删除刚才的文件。
Step2. 注释掉system(command)语句,并取消注释execve()语句;程序将使用execve()调用该命令。编译程序,并使其成为根拥有的Set-UID。
重复上述流程
文件没有被删除:
Step3. 分析原因:
- 使用 system函数时,最终是 shell 去执行命令,改成使用 execve 之后,是通过系统调用的方式去执行。
- 使用 system函数时,没有仔细检查用户输入,使得其执行了两条命令(传入一个有多个命令的字符串,都会运行)。改成使用 execve 之后,只能执行指定进程的程序(文件名,参数,环境变量),无法执行多跳命令,无法攻击成功,不会产生该漏洞。
Task 9: Capability Leaking
Step1. 使用root权限创建/etc/zzz文件,编译以下程序,将其所有者更改为root,并使其成为Set-UID程序。以普通用户的身份运行程序。
#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);
}
}
运行结果:使用普通用户执行,发现该程序停顿了1s,没有输出,/etc/zzz文件内容被修改.
Step2. 原因分析
这个set-uid特权程序打开了一个重要的的系统文件,并且在放弃特权时没有关闭该文件,而后调用Fork,子进程会继承这个文件描述符,造成特权泄露。子进程可以通过泄露的文件描述符向文件写入恶意内容。