0%

网络攻防-环境变量攻击

网络攻防-软件漏洞3-环境变量攻击,涉及实验:SEED Labs – Environment Variable and Set-UID Program Lab ,代码见:https://github.com/Seanxz401/seed-labs

理论

C语言访问环境变量:

  1. 从主函数中访问:void main(int argc, char* argv[], char* envp[])
  2. 使用全局变量:extern char** environ

ps.当对环境变量进行更改时,存储环境变量的位置可能会移动到堆中,因此environ会发生改变而envp[]不会。因为envp是main函数中的局部变量,从缓冲区溢出那一节的知识可以知道,函数内局部变量存储在栈中。

进程获取环境变量:

  1. fork创建新进程,子进程继承父进程的环境变量
  2. execve启动新程序(此程序中的旧环境变量会丢失),通过参数传递环境变量int execve(const char* filename, char* const argv[], char* const envp[])
    • filename是命令对应的文件,如/bin/bash
    • argv是命令及对应参数,如bash -c ...

/proc是linux中的一个虚拟文件系统。它包含每个进程的一个目录,使 用进程ID作为目录的名称。每个进程目录都有一个名为environ的虚拟文件,其中包含进程的环境。当在bash shell中调用env程序时,它将在子进程中运行。因此,它打印 出shell子进程的环境变量,而不是它自己的环境变量。

shell命令变量与环境变量:当shell程序启动时,它会将环境变量复制到自己的shell变量中。对 shell变量所做的更改将不会反映出在环境变量上。但是exported后的shell变量可以传到子进程。

setuid

允许用户以程序所有者的权限运行程序

每个进程都有两个用户ID:

  • RUID:进程的真正所有者
  • EUID:进程的权限,访问控制。

攻击原理:当执行setuid程序时,RUID≠EUID,RUID是当前用户的UID,而EUID为程序所有者的UID,如果程序归root所有,则程序以root权限运行。

设置为setuid程序:

1
2
sudo chown root a.out
sudo chmod 4755 a.out

通过动态链接器攻击

链接:

  • 静态链接:包含源代码和涉及的外部库的代码(编译出的程序较大)
  • 动态链接:在运行时,使用环境变量,将外部库的可执行文件加载到内存(.so/.dll)。(可以通过ldd a.out来查看a程序依赖什么共享库)

通过动态链接器进行攻击:设置用户变量,控制链接过程的结果

  • LD_PRELOAD包含一个共享库的,链接器将首先搜索它
  • 如果没找到,将在几个文件夹列表中搜索,包括LD_LIBRARY_PATH指定的文件夹

eg.用户创建一个共享库(链接到用户指定的函数),并将共享库添加到LD_PRELOAD环境变量中。执行其他调用sleep函数程序时,执行的sleep为用户自定义的函数。

1
2
3
4
5
# sleep.c中自定义sleep函数,main.c是调用了sleep函数的程序
gcc -c sleep.c -o sleep.o
gcc -shared -o libmylib.so.1.0.1 sleep.o
export LD_PRELOAD=./libmylib.so.1.0.1
./main.o

ps.当EUID≠RUID时,动态链接器会忽略LD_PRELOAD和LD_LIBRARY_PATH

通过外部程序攻击

调用外部程序:

  • exec函数家族,它们以不同的形式调用execve,直接运行程序
  • system,调用execl,execl最终调用execve运行/bin/sh,然后通过shell运行程序。shell程序会继承环境变量

操作PATH变量进行攻击:shell运行命令而没有提供绝对路径时,它将使用PATH变量来找到该命令

eg.正常的程序(vul.c)中存在system("cal");,通过操作路径变量,使shell将cal命令定位到自定义的cal程序(攻击者可在这个程序中通过system继续继承环境变量,以root权限执行其他命令)

1
2
3
4
5
sudo chown root vul.o
sudo chmod 4755 vul.o
gcc cal.c -o cal.o
export PATH=.:$PATH # 将当前路径添加到PATH变量中
./vul.o

ps.由此可知,当在特权程序中调用外部程序时,应该使用execve,不会继承环境变量。

通过外部库的攻击

程序通常使用来自外部库的函数。如果这些函数使用环境变量,则用户可以设置利用。

通过应用程序代码攻击

程序可以直接使 用环境变量。如 果这些是特权程 序,它可能会导 致不可信任的输 入。

eg.getenv("PWD");获取环境变量PWD,PWD的值来自于shell程序,用户可以改变

服务方法

允许正常用户执行特权操作的两种方法:

  • setuid:普通用户必须运行一个特殊的程序才能临时获得根权限
  • 服务方法:普通用户必须请求特权服务才能为他们执行操作。(多了一个服务请求的过程)

实验

Task1

查看指定环境变量PWDimage-20221031162155728

export设置环境变量image-20221031162426853

unset取消环境变量image-20221031163326167

Task2

  1. 编译运行:gcc myprintenv.c
  2. 将运行结果保存到文件中:a.out > child
  3. 修改myprintenv.c文件,注释子进程中的printenv函数,并取消父进程中的注释,再次编译,将结果保存在另一个文件中:a.out > parent
  4. 比较两个文件diff child parent,发现是相同的,即子进程完全继承父进程的环境变量。

image-20221031170727647

Task3

myenv.c

1
2
execve("/usr/bin/env", argv, NULL);//修改前
execve("/usr/bin/env", argv, environ);//修改后

image-20221031171909216

修改前没有输出,修改后能输出环境变量。原因:

  1. extern char **environ;获取环境变量表;
  2. execve的第三个参数就是传入环境变量的位置;

Task4

编译运行,能输出环境变量。

image-20221107162241366

system('/usr/bin/env')调用execl()执行/bin/sh,execl()调用execve()并将环境变量传给它。

Task5

编译得到foo后,修改权限:

1
2
sudo chown root foo
sudo chmod 4755 foo

以普通用户修改环境变量ANY_NAME,执行foo后可以看到修改后的ANY_NAME,即父进程中设置的环境变量能进入到setuid(foo)的子进程中。image-20221107164732358

Task6

  • 在原有PATH变量首部添加/home/seed路径: export PATH=/home/seed:$PATH,寻找命令时会优先选择靠前的。
  • 关闭保护措施:sudo ln -sf /bin/zsh /bin/sh
  • 新建两个文件并编译。
1
2
3
4
5
6
7
8
9
10
//ls.c 真的调用ls命令,编译为ls,修改权限类似task5
int main()
{
system("ls");
return 0;
}
//fakels.c 恶意代码,编译为ls(放在/home/seed路径下)
int main(){
printf("you are in fake ls!\n");
}
  • 执行真的ls程序时,system调用首选了/home/seed下的ls程序,即调用了恶意代码。image-20221110112150784

Task7

1
2
3
gcc -fPIC -g -c mylib.c
gcc -shared -o libmylib.so.1.0.1 mylib.o -lc
export LD_PRELOAD=./libmylib.so.1.0.1
  • 普通程序+普通用户:seed用户已经拥有LD_PRELOAD环境变量image-20221110125137468
  • setuid_root程序+普通用户:sleep 1秒;(进程ID与当前用户ID不一致时,忽略LD_PRELOAD环境变量)
  • setuid_root程序+LD_PRELOAD环境(root):(sudo /bin/bash进入root)image-20221110125536637
  • setuid_user1程序+LD_PRELOAD环境(user2):
    • seed用户编译程序后将权限改为sean用户;
    • seed用户导入环境变量后执行,睡眠1s。

结论:LD_PRELOAD环境变量让链接器将sleep()函数和用户的代码链接起来,将该环境变量加入新的共享库,程序会调用用户的sleep()函数,输出I am not sleeping! 但是当进程的真实用户ID和有效用户ID不一致时,进程忽略LD_PRELOAD环境变量。因此第二个和第四个的sleep没有被替换。

Task8

system()

  1. 创建test.txt文件,修改权限

    1
    2
    sudo chown root test.txt # 修改为root用户所有
    sudo chmod 600 test.txt # 修改权限为所属用户能读写
  2. 编译catall.c,此时仍不能读取test.txt,修改权限如task5后可以读取。image-20221111194500374

  3. 执行./cat "test.txt;rm test.txt",会先输出内容再删除该文件。(以分号分割两条命令)

execve()

  1. 创建test.txt步骤类似;
  2. 注释掉system函数,取消execve的注释,编译,修改cat权限,仍然没有权限读取test.txt;
  3. 构建删除语句,execve无法识别为两条命令

image-20221111195529773

可以看出execve()比system()更安全。

Task9

  1. 创建文件:sudo vim /etc/zzz

  2. 注释execve函数,添加代码:

    1
    2
    3
    4
    5
    6
    7
    if(fork()){//创建进程
    close(fd);
    exit(0);
    }else{//在子进程中,向已经打开的文件描述符中写入字符
    write(fd,"\ntask9 attack\n",15);
    close(fd);
    }
  3. 编译,修改权限,运行,发现指定字符串被写入文件image-20221111205820482

在此实验过程中,通过setuid(getuid());降低了程序的权限,但是由于文件已经被打开,降级后的进程仍可访问该文件。