网络攻防技术——环境变量和Set-UID攻击

发布时间 2024-01-05 14:47:41作者: Leo1017

实验6:环境变量与set-uid实验

实验内容:

本实验室的学习目标是让学生了解环境变量如何影响程序以及系统行为。环境变量是一组动态命名值,可以影响正在运行的进程将在计算机上运行。大多数操作系统都使用它们,因为它们是1979年引入Unix。尽管环境变量会影响程序行为,但它们是如何实现的这一点很多程序员都不太理解。因此,如果程序使用环境变量程序员不知道它们被使用,程序可能有漏洞。

在本实验室中,学生将了解环境变量是如何工作的,它们是如何从父进程到子进程,以及它们如何影响系统/程序行为。我们特别感兴趣的是如何环境变量影响Set-UID程序的行为,这些程序通常是特权程序。

本实验室涵盖以下主题:

•环境变量

•SET-UID程序

•安全地调用外部程序

•能力泄漏

•动态加载程序/链接器

 

 

  1. 使用 `printenv` 或 `env` 命令可以打印当前shell中的全部环境变量

    也可以指定打印某个环境变量

    使用`export`命令和`unset`命令可以分别设置和删除环境变量,作用域是当前的shell

  2. environ是一个全局变量,通常用于在C语言中获取当前进程的环境变量。它是一个指向字符指针数组的指针,这个字符指针数组中的每个元素都是一个指向以null结尾的字符串的指针,表示一个环境变量的键值对,environ[i]获取到了一个字符指针,指向其中某个环境变量,C语言中通过打印字符串指针可以打印出其指向的字符串该声明使用了extern关键字,因此environ变量的定义通常是在操作系统或C运行时库中,而不是在用户程序中。

使用fork()函数创建一个子进程,分别在父子进程中打印环境变量

首先在子进程中打印环境变量如下

父进程中打印环境变量如下

使用`diff`命令发现父子进程的环境变量相同,说明子进程会继承父进程的环境变量,还包括父进程的堆栈、内存等,但进程id和fork()函数返回值不同

  1. `execve()`函数执行参数指定的命令时不会产生一个新进程(或者一个新的shell),而是将被加载的程序的数据和堆栈等覆盖到调用进程上,我们先不传入环境变量,打印现在环境的环境变量,发现不存在环境变量,说明原有进程的环境变量被覆盖

    传入进程的环境变量,发现传入新环境

    `execve`函数会将传入的环境变量覆盖到原有环境变量上,若没有传入,则环境变量为空

  2. system()函数也可以用来执行新的程序,与`execve()`函数不同,system()函数实际上执行"/bin/sh -c命令",即打开了一个新的shell,并要求shell执行该命令

    system()会调用fork()函数产生子进程,然后子进程调用/bin/sh -c string 来执行传入参数的命令。此命令执行完后随即返回父进程,环境变量从父进程传给了子进程中的shell程序,在shell程序输出了全部的环境变量

  3. Set-UID是Unix下的一种安全机制,当该机制下的程序运行时,它将拥有该程序所有者的权限。例如,如果程序的所有者是root,那么当任何人运行此程序时,程序在执行过程中都会获得root的权限,而普通用户运行该程序时就会存在越权的漏洞

    编写程序打印环境变量

    将用户设置成root并赋予执行权限

    修改成功

    我们在seed用户下使用export命令设置环境变量,我们设置的Task5=Task5 被成功传入,但 LD_LIBRARY_PATH 传入失败;

    这是一种动态链接器的保护策略,因为这个程序用到了动态链接库。我们是在seed用户的状态下修改的LD_LIBRARY_PATH,在Set-UID的机制下,对动态链接相关环境变量的修改不能被传递到可执行程序中,这是为了抵御修改LD_LIBRARY_PATH来改变set-uid程序的库的寻找位置来实施攻击的攻击方法动态链接器在effective uid(root)和real uid(seed)不一致时,会将LD_LIBRARY_PATH和LD_PRELOAD环境变量忽略掉,所以子进程是看不到的。而环境变量PATH以及ANY_NAME与动态链接器无关,没有被屏蔽,所以子进程能够看到

  4. 编写一个调用ls的程序,并将程序所有者设置为root。程序在执行时如果不提供命令的具体路径,会在环境变量的路径下依次搜索,直到找到路径下和准备执行的程序同名的程序,然后拿去执行,这里程序会找到 /etc/bin/ls 去执行

由于/bin/sh默认链接到dash,而dash的保护机制会组织有效ID和真实ID不一致的程序的执行,所以需要将/bin/sh链接到zsh

编写一个假的ls程序,并将编译后的执行文件也命名成ls

把假的ls文件所在的目录添加到PATH环境变量的开头,这时再使用system()函数调用ls执行的就是我们写的ls程序

注意路径最后有一个"/"

直接shell中执行ls调用的也是我们的ls程序

eid指的是用户的有效用户ID(effective user ID),也就是用户在执行特定进程时所拥有的权限

将我们编写的ls程序加上调用`id`命令的语句,发现打印的eid是root,证明文件执行时是以root权限执行的

  1. `gcc -fPIC -g -c mylib.c`:编译源代码文件mylib.c,生成目标文件mylib.o。选项-fPIC表示生成位置无关的代码(Position Independent Code)以便编译器能够在不同地址空间中执行该代码。选项-g表示生成调试信息,以便能够使用调试器来调试该代码。

    `gcc -shared -o libmylib.so.1.0.1 mylib.o -lc`:将目标文件mylib.o链接成一个动态链接库libmylib.so.1.0.1。选项 -shared表示生成动态链接库,选项 -o指定输出文件名。-lc选项表示动态链接库需要链接C运行时库

    这里只是修改了sleep函数,但实际攻击中可以放入恶意代码,当用户的正常程序链接到我们的恶意动态库时就会执行这些恶意代码

    环境变量LD_PRELOAD用于在程序运行时强制优先加载指定的共享库文件。这个功能可以用来替换已经存在的库中的函数或者添加新的函数,修改这个环境变量到我们的动态链接库

    

  1. 链接到了我们的库

  2. 链接到了系统的库

  3. 链接到了我们的库

  4. 链接到了系统的库

是否能成功链接到我们的库取决于我们设置的LD_PRELOAD 环境变量有没有被动态连接器屏蔽,当执行环境的用户ID和程序的用户ID不一致时,动态链接器出于安全考虑,会将环境变量的改变屏蔽掉,因此我们设置的环境变量不会生效

  1. 编译给出的catall文件,并将执行文件用户设置为root

    在root权限下创建一个名为task8的文件,发现在seed用户下无法将其删除

    在catall执行后的参数中写入两条shell语句,以";"作为分隔,system()函数拿到字符串后会直接将其作为整个shell命令去执行,发现第二条删除语句成功执行,这是因为catall是一个root权限的文件,执行时具有root权限,这里就发生了普通用户执行root程序的越权行为

    换成execve()函数,发现无法执行,原因是execve()函数并不会启动一个shell,传入的字符串整个作为一个程序去执行,自然会报无法找到这个程序/命令的错误

  2. 在root用户下新建一个只读的文件/etc/zzz

    编译给出的cap_leak程序,并将用户设置成root

    在程序的执行过程中,遇到下面的语句时,程序会将程序的用户ID设置成当前用户ID,即seed,这样看起来程序之后就不会再以root权限执行了

    这时再启动一个shell

    我们发现在这个shell中还是成功地对zzz文件进行了写操作

    这是因为程序在切换权限之前没有关闭该文件,在新权限下继承了该文件描述符,文件描述符泄露,在关闭文件描述符之前,root操作都能成功执行

     

  3. 环境变量是一组动态命名值,可以影响正在运行的进程将在计算机上的运行,我们需要了解环境变量是如何工作的,它们是如何从父进程到子进程,以及它们如何影响系统/程序行为,尤其是针对Set-UID程序,这会带来巨大的安全漏洞。`fork()`、`execve()`和`system()`函数对环境变量传递的方式不尽相同,fork()函数会将父进程环境变量复制到子进程中,execve()函数,不会传递环境变量,但可以将环境变量作为参数进行传递,system()函数新建一个进程并开启一个shell,将环境变量传递进去。对于Set-UID来说普通环境变量的传递同上,但涉及到动态链接的环境变量时,动态链接器在 effective uid 和 real uid 不一致时,会将 LD_LIBRARY_PATH 和 LD_PRELOAD 环境变量忽略掉,所以不会传递成功。通过修改环境变量,可以修改其他用户调用程序的执行程序,从而执行我们的恶意程序。最后,Set-UID程序很有可能发生权限溢出的问题,攻击者可能利用被泄露的权限对我们的系统造成攻击(如对文件的修改,甚至拿到该权限下的shell),所以使用时一定要谨慎。