环境变量攻击
环境变量
- 一组动态的定义值
- OS运行环境的一部分
- 影响正在运行进程的行为方式(加载哪些外部DLL)
- 当执行一个程序时,如果没有提供完整的路径,shell进程将使用环境变量来找到程序的位置
使用Windows系统,我们安装编程相关软件时有时会问是否加入到环境变量,比如python(最好要加),这样我们在任何目录下cmd都可以运行python(现在本目录下找,没有就去环境变量里找)
如何访问环境变量
计算机右键属性>高级系统设置>环境变量
代码访问环境变量
编译运行后如下
更简单的方法是printenv或env查看环境变量
查看特定变量的方法:env | grep variable,或直接printenv variable
设置自己的环境变量:export variable=value
清除自定义的环境变量:unset variable
进程如何获取环境变量
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); } }
代码解释:父进程fork后返回子进程id给pid,由于不为0将执行default中内容,在子进程中,fork返回0,执行case 0里的内容
https://blog.csdn.net/hzrandd/article/details/50724697
http://www.csl.mtu.edu/cs4411.ck/www/NOTES/process/fork/create.html
分别注释两个进程,编译运行后打印环境变量,可以看到fork的父子进程环境变量一模一样
execve()启动一个新程序,在此场景中内存空间被覆盖,旧环境变量将丢失
#include <unistd.h> extern char **environ; int main() { char *argv[2]; argv[0] = "env"; argv[1] = NULL; execve("/usr/bin/env", argv, NULL); return 0 ; }
运行后打印环境变量为空
int execve(const char filename, char const argv[ ], char *const envp[ ]);
- filename:执行命令的文件路径
- argv[ ]:使用数组指针来传递给执行文件,必须以NULL结尾
- envp[ ]:传递给执行文件的新环境变量数组
execve传递给新进程旧环境变量的方法:NULL改为外部变量environ,也可以传递自定义的环境变量
/proc文件系统
/proc是linux下的一个虚拟文件系统,包含每个进程的一个目录,使用进程ID作为目录名称
- 每个进程目录都有一个名为environ的虚拟文件,其中包含进程的环境
- 查看当前进程的环境变量:strings /proc/$$/environ (shell自动将\$$替换为进程ID)
shell命名变量
当shell程序启动时,会将环境变量复制到自己的shell变量中,对shell变量的改动不会改动环境变量
如下图:显示了shell变量如何影响子进程的环境变量;同时显示了父shell的环境变量如何成为子进程的环境变量
从图中可以看到:只有原本的环境变量、export设置的环境变量才可以进入进程新建的子进程(反映在下图就是LOGNAME2没有出现在子进程的环境变量中【env执行,shell将创建一个子进程】)
环境变量的攻击
通过Set-UID攻击
Set-UID
- 允许用户以程序所有者的权限运行程序
- 允许用户以临时提升的权限运行程序
在UNIX中,每个进程都有两个用户ID
- real UID(RUID):确定进程的真正所有者
- effective UID(EUID):标识进程的权限(访问控制)
当执行正常程序时,RUID=EUID=运行程序的用户的ID;当执行Set-UID时,RUID≠EUID,RUID还是运行程序的用户的ID,EUID是程序所有者的ID
即,若程序归root所有,执行set-UID将以root权限执行
攻击
由于用户可以设置环境变量,因此可以将其作为set-UID程序攻击的一部分
- 添加/home/seed 目录到 PATH 环境变量中,使用export,冒号是分割的作用
- 编写使用 system 调用 ls 的程序
- 编译后修改所有者权限,使之称为set-UID程序
补充:4755(4 表示其他用户执行文件时具有与所有者相当的权限;7 表示文件所有者的权限为全部;5 表示与文件所有者同属一个用户组的其他用户的权限为可读可执行;第二个 5 表示其他用户组的权限为可读可执行)
- 执行后发现输出 ls 打印的内容
- 我们可以利用之前添加的路径/home/seed,将/bin/sh 拷贝到那里并修改为 ls,这时再去执行 ls 其实会直接执行 sh,获取 shell,但是发现没有获取的 root 权限,uid 没有发生变化
- 主要原因是/bin/sh 链接到了/bin/dash,具有一定的防御措施,检测到 uid 攻击后会将 euid 设置回进程真实的 uid,因此我们做一定的修改,将/bin/sh 链接到/bin/zsh,此时获取 root 权限(多了个euid=0,Set-UID 程序和其他 UNIX 程序的唯一区别是具有 Set-UID 比特位)
通过动态链接器攻击
动态链接
链接再运行时完成,在运行使用动态链接编译的程序前,首先将其可执行文件加载到内存(静态链接生成的程序比动态链接生成的大得多)
- LD_PRELOAD包含一个共享库的列表,链接器将首先搜索它
- 如果没有找到所有函数,链接器将在几个文件夹中搜索,包括LD_LIBRARY_PATH指定的文件夹
- 这两个变量可以被用户设置,使得有机会控制链接过程
攻击
- 编写 sleep 函数的 lib 文件
- 编译文件(-shared 生成共享目标文件,通常用在建立共享库时)
- 设置 LD_PPRELOAD 环境变量为之前生成的共享目标文件
- 编写 myprog.c 并编译运行,可以看到并没有调用本来的 sleep,而是调用了我们编写的 带有 printf 的 sleep
- 但是如果程序为set-UID程序,程序没有调用我们自定义的链接库
原因:动态链接器实现了一个对策,当EUID和RUID不同时,它会忽略LD_PRELOAD和LD_LIBRARY_PATH环境变量
示例:复制env程序,并将其设置为Set-UID程序;导出上面两个环境变量
通过外部程序攻击
应用程序可以调用外部程序,本身可能不使用环境变量,但是被调用的外部程序可能会使用
方法
- 执行exec()家族
- system(),调用execl,execl调用execve运行/bin/sh,然后shell运行程序
与system()相比,execve()攻击面更小,不受环境变量影响;当在特权程序中调用外部程序时,应该使用execve()
其他方法:通过外部库、应用程序代码攻击