Set_UID程序实验
本文进行SEED_LAB中关于Set_Uid一节的实验学习:笔者将按照官方网站中的实验指导文档进行操作。
Ps:本文实验环境采取SEED Ubuntu 20.04 VM
实验一
本节进行linux下环境变量操作的说明:
env #获取当前环境变量信息
export YSSX=123 #设置名为YSSX的环境变量,值为123
unset YSSX #取消YSSX的环境变量
注意!!!export与unset为bash中的内置命令,而不是可运行的程序。因此别的shell中无法使用
实验二
本节研究父子进程间的环境变量继承情况,我们通过以下代码来进行测试
#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);
}
}
分别解开父子进程中的printenv注释,并将结果输出到a,b两个文件中,对其输出内容进行diff操作

由此可知,fork出的子进程将继承父进程的环境变量信息
实验三
本节探讨execve形式的进程创建与环境变量继承的关系。execve函数的范式定义为 int execve(const char *pathname, char *const argv[], char *const envp[]);分别为程序名,程序参数,环境变量列表。
根据上述分析,我们可以给出以下形式的代码进行验证
#include <unistd.h>
extern char **environ;
int main()
{
char *argv[2];
argv[0] = "/usr/bin/env";
argv[1] = NULL;
execve("/usr/bin/env", argv, environ);
//execve("/usr/bin/env", argv, NULL);
return 0 ;
}
分别使用NULL列表和environ列表进行编译,可以看到,execve必须由调用者主动给出环境变量列表,否则无法继承父进程环境变量

实验四
本节探讨system形式的进程创建与环境变量继承的关系。不同于直接执行命令的execve,system()实际执行的是”/bin/sh -c command”,即它会先执行/bin/sh,然后让shell来执行命令。、
同时,system的函数实现通过execl()来执行/bin/sh,execl()会调用execve(),同时将环境变量传递给这个系统调用。因此该形式创建进程应当会继承环境变量,我们编译如下代码进行测试
#include <stdio.h>
#include <stdlib.h>
int main(){
system("whoami");
system("printenv");
return 0;
}
发现确实继承了环境变量

实验五、六
本节介绍Set_Uid程序与环境变量间的关系。首先,Set_Uid权限位是Linux操作系统中的一种特殊权限操作:拥有该权限位的程序,在被任意用户运行时,都会使用该程序拥有者的系统权限进行程序中的操作。
sudo chown root your_program #更改拥有者为root
sudo chmod 4755 your_program #更改Set_uid权限位
同时由于system()会保留用户的环境变量,因此在Set_uid程序中使用system是极为危险的。
比如我们自定义ls程序如下,并将其编译为 gcc ls.c -o ls
//ls.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
system("whoami");
printf("This is Seed's fake ls!You are foo!");
return 0;
}
我们可以看到,在 export PATH=/home/seed/Desktop/Labsetup:$PATH 后,ls指令已经被替换为了我们的自定义代码

此时,我们编译如下代码,并为其设置Set_uid位,查看普通用户的环境变量能否影响到该程序的运行结果
#include <stdio.h>
#include <stdlib.h>
int main(){
system("ls");
return 0;
}
可以发现,即使是在用户环境下的恶意代码,仍有可能通过Set_uid进行提权

实验七
Linux中的一些环境变量通常会影响程序的运行。比如经典的LD_PRELOAD,该环境变量一旦被设置,程序链接过程中将最优先查找该变量所定义的库。由此,我们可以通过库劫持的操作实现恶意代码的执行。
首先编译我们自己的sleep函数,用来替换c标准库中的sleep函数
#include <stdio.h>
void sleep (int s){
printf("I wont sleep!! \n");
}
其次将其编译成我们自己的共享库
gcc -fPIC -g -c mylib.c #仅编译为mylib.o 不链接;同时设置PIC位置无关
gcc -shared -o libmylib.so.1.0.1 mylib.o -lc #生成共享库,,并且通过-lc链接c标准库
接下来进行声明 export LD_PRELOAD=./libmylib.so.1.0.1,然后使用以下代码进行测试 sleep(1);发现库劫持实现

接下来,我们分别在不同权限位下进行验证
将myprog设置为Set-UIDroot程序,并以普通用户运行。正常睡眠
将myprog设置为Set-UIDroot程序,在root账户中设置LD_PRELOAD环境变量后运行该程序。库劫持成功

将myprog设置为Set-UIDuser1程序(即拥有者为user1,这是另一个用户账户),在user2账户(非root用户)中设置LD_PRELOAD环境变量后运行该程序。正常睡眠

综上,我们可以发现,现代操作系统在父子进程的环境变量继承中,会主动过滤类似高风险变量,从而防止恶意库代码的劫持
实验八
前文已提到,sysytem()会保留父进程的环境变量,有一定程度的风险。并且该函数实现的原理是调用一个shell来执行命令,因此还会存在字符串拼接执行的风险
观察下述代码,可以看出,其核心功能是使用cat程序,完成任意文件内容的读取。但由于代码中没有写操作,理应无法借助root权限修改任何文件
#include <string.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 ;
}
但是可以看到,此处一开始的子程序创建借助system完成。从system原理可知 /bin/cat your_argv是在shell中进行解析的。同时shell同行中可以使用;来衔接两条命令依次执行。
因此我们可以使用 ./catall "123;whoami"的操作来进行任意命令的提权,如下图所示,成功执行whoami,并且当前权限为root

将system换为execve后,没有shell的中转,无法进行拼接字符串的解析,无法实现上述攻击

实验九
为了遵循最小权限原则,Set-UID程序通常会在不再需要权限时永久放弃其root权限。但在放弃权限后,一些已分配已申请的资源依旧存在,这些特权功能的遗留导致了权限泄露漏洞
分析以下代码,在以root权限申请/etc/zzz的文件描述符后,直接降权,fd的遗留导致了权限泄露。
#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);
}
在 /bin/sh时使用的是user权限,理应无法对/etc/zzz进行修改.
但是由于上述所说的权限泄露问题,我们进入新的shell时,依然保留了/etc/zzz的文件描述符,因此我们可以直接对该文件符进行读写操作,从而修改指定文件的内容。


成功以普通用户身份修改系统关键文件