SEEDLAB-Set_uid


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;
}

发现确实继承了环境变量

system验证

实验五、六

本节介绍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的文件描述符,因此我们可以直接对该文件符进行读写操作,从而修改指定文件的内容。

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


文章作者: Yssx
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yssx !
评论
  目录