Lab Tasks
task 1:manipulate environment variable
- 打印环境变量
- 使用export和unset来设置和取消设置环境变量
这两条命令不是单独的程序,是bash的内部命令,而shell默认的程序是/bin/bash
task2:Passing environment variable
step 1
对program进行编译,这个program会使得它的子进程打印出来字符指针数组的信息
可以看到,相应的环境变量的信息是被打印出来了,之后将这些信息存储在文件当中
step 2
修改之后重新进行编译运行
然后存储在file1文件当中
step 3
之后通过diff file file1
来进行对比,会看到除了第48行中可执行文件的名字不同之外,environment variable并没有区别
总结,通过fork()产生的子进程拥有和父进程完全一致的环境变量
task 3:Environment Variables and execve()
step 1
在这个task当中,我们所要研究的是exevce()函数以及在调用前后environment variable的变化
此时,不打印任何东西,分析原因,会发现是由于在execve当中传递的第三个指针数组为空,所以此时没有环境变量可打印
step 2
修改指针数组内容为全局环境变量,可以看到相应的变量信息被打印出来
step 3
再次修改传递的指针数组的内容,之后编译运行
可以看到,新的环境变量就是我们所设置的指针数组里面的内容
总结:通过execve()运行的新的进程是通过指针数组,以传参的形式获取environment variable的
task 4:Environment Variables and system()
逻辑是这样的,system()和execve()类似,调用一个新的程序,只不过system()已经设置好了所需要调用的文件/bin/sh,而system()回调用execl()函数,而execl()则会调用execve()函数
而如果直接运行该命令,可以看到输出的内容是一样的
总结:当我们运行system()函数的时候,抛开外表,也是通过传递指针数组的形式来传递环境变量的
Task 5: Environment Variable and Set-UID Programs
在这个task当中,我们aim at set-uid
程序是否会继承来自用户程序的environment variable,关于set-uid程序,参考setuid函数解析
step 1
step 2
之后需要让这个program具有root权限以及使它成为一个Set-UID
程序
chmod 4755与chmod 755对比多了附加权限值4,这个4表示其他用户执行文件时,具有与所有者同样的权限(设置了SUID)。
为什么要设置4755 而不是 755?
假设netlogin是root用户创建的一个上网认证程序,如果其他用户要上网也要用到这个程序,那就需要root用户运行chmod 755 netlogin命令使其他用户也能运行netlogin。但假如netlogin执行时需要访问一些只有root用户才有权访问的文件,那么其他用户执行netlogin时可能因为权限不够还是不能上网。这种情况下,就可以用 chmod 4755 netlogin 设置其他用户在执行netlogin也有root用户的权限,从而顺利上网。
step 3
把对应的environment variable在用户环境下设置好
之后运行,可以看到我们设置的environment variable中的MYSHELL是被打印出来了,说明该环境变量是从shell 程序当中继承过来的
修改一下code,然后依旧给他root的权限以及set-uid的权限
可以看到,我们能找到PATH以及自己定义的环境变量,无法找到第二个环境变量LD LIBRARY PATH
为什么LD_LIBRARY_PATH环境变量不会被包含到子进程的环境变量中呢?
参考blog:SEEDLab Environment Variable and Set-UID Program Lab 实验报告
Task 6: The PATH Environment Variable and Set-UID Programs
首先,task6告诉我们在set-uid当中调用sytem()函数是很危险的,因为shell程序可能会受到环境变量的影响。
攻击逻辑是这样的,我们通过修改环境变量来针对于set-uid程序进行攻击(其实实际上是针对于set-uid程序当中的system()函数来进行的)
- step1
首先,code进行编译运行,注意在这里为防止/bin/sh的不在set-uid当中执行的策略,我们需要
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
system("ls");
return 0;
}
- step2
接下来,我们aim at如何执行自己的恶意代码而不是/bin/ls,并且能否获得root权限
第一个攻击的点在于ls命令是相对寻址的,而不是根据绝对地址来进行运行,所以可以在寻找命令的时候让解析器去找我们所想要执行的恶意代码,需要修改PATH路径,同时,为了得到root权限,我们需要由前两个lab一样呢进行reverse shell下面是具体步骤
首先,在reverse shell当中,都是执行/bin/sh,通过上面命令来讲/bin/sh文件复制到/home/seed/Desktop/lab3/Labsetup
当中,并且重命名为ls
之后将相对路径加入到PATH环境变量当中,可以看到会首先寻找我的相对路径,之后再次运行task6,就可以获得root权限
总结:在这一步当中,我们利用了environment variable中的PATH变量来针对ls命令进行攻击,并且通过set-uid程序获得了root权限
Task 7: The LD PRELOAD Environment Variable and Set-UID Programs
task7先告诉了我们LD_PRELOAD这个环境变量是用来加载用户指定的共享库的,并且是优先于其他所有的动态库的,下面来看一下具体是如何影响的以及我们能怎么利用
动态链接器程序有一个防御机制,当进程的真实用户ID与有效用户ID不一样时,或者真实组ID与有效组ID不一致时,进程将会忽略LD_PRELOAD,LD_LIBRARY_PATH环境变量.
step 1
首先按照步骤建立连接库并且修改LD_PRELOAD环境变量
step 2
- 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.
此时,结果是sleep一秒之后退出,连接到的是原来的库文件
Make myprog a Set-UID root program, export the LD PRELOAD environment variable again in the root account and run it.
此时,在root用户下运行会发现连接到的是重新编译后的库文件
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.
创建用户test并且在seed账户中export,此时sleep一秒之后结束程序,连接到的是原来的库文件
而当我们是在test账户当中进行export的时候,会发现此时打印消息,连接到的是重新编译后的共享库
step 3
在这一步当中,我们aim at背后的机制,可以肯定的是一定和环境变量有关,并且根据hint可以得知,有的子shell可能不会继承父shell的LD*的环境变量,所以设计思路就是在我们myprog.c文件当中打印出LD_PRELOAD的值
动态链接器防御机制
动态链接器程序有一个防御机制,当进程的真实用户ID与有效用户ID不一样时,或者真实组ID与有效组ID不一致时,进程将会忽略LD_PRELOAD,LD_LIBRARY_PATH环境变量.
- 针对Make myprog a regular program, and run it as a normal user.
此时myprog的实际用户是seed,而有效用户也是seed,所以此时会发生重载,并且运行myprog的shell程序会是继承父shell程序的environment variable
- 针对Make myprog a Set-UID root program, export the LD PRELOAD environment variable again in the root account and run it.
此时myprog的实际用户是seed,而有效用户是root,所以会忽略LD_PRELOAD(实际上执行myprog的shell进程就没有继承来自父shell的LD_PRELOAD的环境变量)
后面两个实际上是同理的
Task 8: Invoking External Programs Using system() versus execve()
这个task的target是看system()和execve()之间的区别以及调用shell的另一个危险之处?还与环境变量无关?
step 1
在这一步当中,我们就把seed当作是Bob,所以需要做的就是运行catall文件来进行删除不属于我们权限的文件
发现这个文件是没有了的,而secret这个文件有效用户为root,其他的用户权限仅为r;我们将这个文件删除了,说明在catall这个set-uid程序当中,我们以root权限执行来rm的操作,同理,我们也可以通过这个方法来获取shell权限
step 2
当我们把上面的system()函数换成execve()函数的时候,会发现
此时无法执行多条命令,无法删除secret文件同时也不能获得root权限
探究
首先是从system()函数上来看
总结来看,调用system()的话首先运行fork,产生一个子进程,然后使用execl函数进行运行命令/bin/sh,产生一个shell程序,运行command命令,同时,将环境变量显式传递给新程序.同时在父进程中调用wait去等待子进程结束.
环境变量经过了三个阶段:
- 进程本身拥有
- fork时复制给子进程
- execl函数运行时,显式赋值给新程序
在第二步中,引入了外部程序shell,而在shell中,是可以执行任何指令的,所以,我们可以执行多条指令,在设置setuid程序后,可以获取root权限.
而execve函数为执行一个系统调用函数
int execve(const char *filename, char *const argv[],char *const envp[]);
第二个参数中如果包含额外的指令,他们仍然会被视为一个参数,并非一个指令.所以才会出现:
/bin/cat: ‘secret;/bin/sh’: No such file or directory
的错误,secret;/bin/sh被视为了一个字符串参数.
system()函数违背了最小权限原则,调用了shell,而shell可以执行任意命令.以及输入验证原则,过分信任了用户的输入.所以,system()函数需要谨慎使用,推荐使用execve()函数,进行了运行程序与程序参数的分类,更安全.
Task 9: Capability Leaking
首先来看setuid()函数作用
进程有root权限
当有效用户id为root,而真实用户ID和保留用户ID为普通用户,即Set-UID进程,时,会设置有效用户ID,真实用户ID,保留用户ID为参数uid,包括设置为0,即可以通过在Set-UID程序中使用:setuid(geteuid());代码,使进程的真实用户ID,保留用户ID均为0.
进程不具有 root 权限
若进程不具有 root 权限,那么普通用户使用 setuid() 时参数 uid 只能是自己的,没有权限设置别的数值,否则返回失败.
有疑问的地方在于运行setuid()函数之后不应该就没有特权了吗?
修改cap_leap.c文件
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
void main()
{
int fd;
char *v[2];
/* Assume that /etc/zzz is an important system file,
* and it is owned by root with permission 0644.
* Before running this program, you should create
* the file /etc/zzz first. */
fd = open("/home/seed/Desktop/lab3/Labsetup/zzz", O_RDWR | O_APPEND);
if (fd == -1) {
printf("Cannot open /home/seed/Desktop/lab3/Labsetup/zzz\n");
exit(0);
}
// Print out the file descriptor value
printf("fd is %d\n", fd);
sleep(1);
// Permanently disable the privilege by making the
// effective uid the same as the real uid
setuid(getuid());
if (fork()){ /* In the parent process */
close (fd);
exit(0);
}else
{
write (fd, "Malicious Data\n", 15);
close (fd);
}
// Execute /bin/sh
// v[0] = "/bin/sh"; v[1] = 0;
// execve(v[0], v, 0);
}
之后运行可以发现已经进行了写入
这是因为,在子进程中,文件描述符fd在进入close()函数前仍然有效,此时虽然进行了setuid(getuid());进行进程的特权解除,随后的非特权进程仍然可以进行修改文件.所以应该在降低特权前运行close(fd),销毁文件描述符.
附加题
编译challenge.c
gcc challenge.c -o challenge
题目要求
challenge.c 会读取环境变量”PWD”(当前路径,如”/home/seed/env_lab”), 然后将其中的值传给buffer. 由于程序使用了危险的函数”strcpy”, 因此如果”PWD”的长度过长,会在栈上造成溢出. 本题需要大家通过栈溢出将buffer上面的数组overflowIt的一个位置的值修改成0x01020304.
如果攻击成功,程序会输出”Congratulations, you pwned it!”.
提示
- 需要创建新的文件夹, 可能需要GDB调试.
- 如果下课前半小时没有完成,可以找助教要一个方便调试的pwn脚本.
Summary
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!