突破disable_functions执行命令·上
PHP disable_functions
disable_functions是php.ini中的一个设置选项。相当一个黑名单,可以用来设置PHP环境禁止使用某些函数,通常是网站管理员为了安全起见,用来禁用某些危险的命令执行函数等。

先来看看一般是哪些函数需要放入 disable_functions:
| 禁用函数 | 功能描述 | 危险等级 |
|---|---|---|
| system() | 允许执行一个外部程序并回显输出 | 高 |
| exec() | 允许执行一个外部程序 | 高 |
| shell_exec() | 通过 Shell 执行命令,并将执行结果作为字符串返回。 | 高 |
| passthru() | 允许执行一个外部程序并回显输出 | 高 |
| popen() | 可通过 popen() 的参数传递一条命令,并对 popen() 所打开的文件进行执行。 | 高 |
| proc_open() | 执行一个命令并打开文件指针用于读取以及写入。 | 高 |
| proc_get_status() | 获取使用 proc_open() 所打开进程的信息。 | 高 |
| chroot() | 可改变当前 PHP 进程的工作根目录,仅当系统支持 CLI 模式PHP 时才能工作,且该函数不适用于 Windows 系统。 | 高 |
| chgrp() | 改变文件或目录所属的用户组。 | 高 |
| chown() | 改变文件或目录的所有者。 | 高 |
| ini_set() | 可用于修改、设置 PHP 环境配置参数。 | 高 |
| ini_alter() | 是 ini_set() 函数的一个别名函数,功能与 ini_set() 相同。 | 高 |
| ini_restore() | 可用于恢复 PHP 环境配置参数到其初始值。 | 高 |
| dl() | 在 PHP 进行运行过程当中(而非启动时)加载一个 PHP 外部模块。 | 高 |
| pfsockopen() | 建立一个 Internet 或 UNIX 域的 socket 持久连接。 | 高 |
| symlink() | 在 UNIX 系统中建立一个符号链接。 | 高 |
| putenv() | 用于在 PHP 运行时改变系统字符集环境。在低于 5.2.6 版本的 PHP 中,可利用该函数修改系统字符集环境后,利用 sendmail 指令发送特殊参数执行系统 SHELL 命令。 | 高 |
| phpinfo() | 输出 PHP 环境信息以及相关的模块、WEB 环境等信息。 | 中 |
| scandir() | 列出指定路径中的文件和目录。 | 中 |
| syslog() | 可调用 UNIX 系统的系统层 syslog() 函数。 | 中 |
| readlink() | 返回符号连接指向的目标文件内容。 | 中 |
| stream_socket_server() | 建立一个 Internet 或 UNIX 服务器连接。 | 中 |
| error_log() | 将错误信息发送到指定位置(文件)。 安全备注:在某些版本的 PHP 中,可使用 error_log() 绕过 PHP safe mode,执行任意命令。 |
低 |
注:
eval()并非PHP函数,放在disable_functions中是无法禁用的,若要禁用需要用到PHP的扩展Suhosin。
由于很多 PHP 站点往往设置了disable_functions来禁止用户调用某些危险函数,给 Getshell 带来了很大的不便,这里总结了以下绕过方法来绕过与突破disable_functions,欢迎大佬指正。
寻找黑名单遗漏的危险函数
disable_functions 是基于黑名单来实现对某些函数使用的限制的,既然是黑名单有时候就难免会有漏网之鱼。
拿到WebShell之后可以通过phpinfo来寻找黑名单遗漏的危险函数。

以下一些比较严格的disable_functions限制项:
1 | passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,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_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv |
除了可以使用
phpinfo()函数获取disable_functions外,还可以使用ini_get()函数来获取php.ini的内容:
1 ini_get('disable_functions');
利用 LD_PRELOAD 环境变量
原理简介:
LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的攻击目的。
我们通过环境变量 LD_PRELOAD 劫持系统函数,可以达到不调用 PHP 的各种命令执行函数(system()、exec() 等等)仍可执行系统命令的目的。
想要利用LD_PRELOAD环境变量绕过disable_functions需要注意以下几点:
能够上传自己的.so文件 能够控制LD_PRELOAD环境变量的值,比如
putenv()函数 因为新进程启动将加载LD_PRELOAD中的.so文件,所以要存在可以控制PHP启动外部程序的函数并能执行,比如mail()、imap_mail()、mb_send_mail()和error_log()函数等
漏洞利用条件:
Linux 操作系统
putenv可用mailorerror_log可用,本例中禁用了mail但未禁用error_log存在可写的目录,需要上传
.so文件
靶场环境:
项目地址:https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypass_disable_functions/1
启动环境:
1 | docker-compose up -d |
我们的最终目的是获取 /flag 的内容, 这个文件是 644 权限,www-data 用户无法通过读文件的形式读到内容, 需要执行拥有 SUID 权限的 tac 命令来获取 flag。

方法一:劫持函数
一般而言,利用漏洞控制 web 启动新进程 a.bin(即便进程名无法让我随意指定),新进程 a.bin 内部调用系统函数 b(),b() 位于 系统共享对象 c.so 中,所以系统为该进程加载共享对象 c.so,想办法在加载 c.so 前优先加载可控的 c_evil.so,c_evil.so 内含与 b() 同名的恶意函数,由于 c_evil.so 优先级较高,所以,a.bin 将调用到 c_evil.so 内的b() 而非系统的 c.so 内 b(),同时,c_evil.so 可控,达到执行恶意代码的目的。
基于这一思路,常见突破 disable_functions 限制执行操作系统命令的思路:
1.找到一个可以启动新进程的函数,如
mail()函数会启动新进程/usr/sbin/sendmail2.书写一个会被
sendmail调用的C函数(函数最好不带参数),内部为恶意代码,编译为.so文件,如geteuid()函数3.运行PHP函数
putenv(),设定我们的so文件为LD_PRELOAD,设置后新进程启动时将优先加载我们设置的so文件4.运行PHP的
mail()函数,这时sendmail会优点调用我们书写的getegid同名函数,达到劫持执行恶意代码的效果
首先查看sendmail会调用那些函数,这里我们选择getegid函数,也可以是其他函数进行劫持
1 | readelf -Ws /usr/sbin/sendmail |

靶场突破:
首先在本地编写hack.c文件:
1 |
|
将c文件编译为so文件
1 | gcc hack.c -o hack.so -shared -fPIC |
使用蚁剑将hack.so上传至目标靶机

使用蚁剑在目标靶机上写入php文件,设置环境变量并执行mail()函数
1 |
|
但是在浏览器中访问.php文件,未出现flag,猜测mail函数被禁用,可以通过写入phpinfo()查看

sendmail也会调用error_log,修改php文件如下:
1 |
|
浏览器访问.php文件,在蚁剑中可以看到生成了flag.txt文件

方法一:预加载共享对象
在实际情况中,很多机器尚未安装或者禁止了sendmail功能,通常的 www-data 权限又不可能去更改 php.ini 配置、去安装 sendmail 软件,所以可以采用另一种方式绕过disable_function。
系统通过LD_PRELOAD预先加载共享对象,如果在加载时就执行代码,就不用劫持函数以此绕过disable_function。
gcc允许为函数设置如下属性,可以让其修饰的函数在mail()函数之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,将立即执行。
1 | __attribute__((__constructor__)) |
编写hack.c代码
1 |
|
将c文件编译为so文件,并使用蚁剑将hack.so上传至目标靶机
1 | gcc hack.c -o hack.so -shared -fPIC |
使用蚁剑在目标靶机上写入php文件,设置环境变量并执行error_log()函数
1 |
|
浏览器访问.php文件,在蚁剑中可以看到生成了flag.txt文件

注:unsetenv()可能在CentOS上无效,因为CentOS自己也hook了unsetenv(),在其内部启动了其他进程,来不及删除LD_PRELOAD就又被劫持,导致无限循环,可以使用全局变量 extern char** environ删除,实际上,unsetenv()就是对 environ 的简单封装实现的环境变量删除功能。
1 |
|
使用for循环修改LD_PRELOAD的首个字符改成\0这样可以使系统原有的环境变量自动失效。
利用 GCONV_PATH 与 iconv
原理简介:
php在执行iconv函数时,实际上是调用glibc中的iconv相关函数,其中一个很重要的函数叫做iconv_open()。
linux系统提供了一个环境变量:GCONV_PATH,该环境变量能够使glibc使用用户自定义的gconv-modules文件,因此,如果指定了GCONV_PATH的值,iconv_open函数的执行过程会如下:
iconv_open函数依照GCONV_PATH找到gconv-modules文件,这个文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个.so文件中,即gconv-modules文件提供了各个字符集的.so文件所在位置。- 根据
gconv-modules文件的指示找到参数对应的.so文件。 - 调用
.so文件中的gconv()和gonv_init()函数。 - 一些其他步骤。
我们的利用方式就是首先在某一文件夹(一般是/tmp)中上传gconv-modules文件,文件中指定我们自定义的字符集文件的.so,然后我们再在.so文件中的gonv_init()函数中书写命令执行函数,之后上传php的shell,内容是使用php设定GCONV_PATH指向我们的gconv-modules文件,然后使用iconv函数使我们的恶意代码执行。
漏洞利用条件:
- Linux 操作系统
putenv可用- PHP安装了
iconv相关模块 - 存在可写的目录,需要上传
.so文件

靶场环境:
项目地址:https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypass_disable_functions/9
靶场突破:
首先上传gconv-modules文件于/tmp文件夹,其内容如下:
1 | module PAYLOAD// INTERNAL ../../../../../../../../tmp/payload 2 |
在本地编写payload.c文件,内容如下:
1 |
|
将c文件编译为so文件
1 | gcc payload.c -o payload.so -shared -fPIC |
使用蚁剑将payload.so上传至目标靶机的/tmp/目录下

编写exp.php上传至web目录下
1 |
|
访问exp.php页面即可执行命令

利用 Apache Mod CGI
原理简介:
CGI:CGI ,公共网关接口,它是 Web 服务器与外部应用程序(CGI 程序)之间传递信息的接口。通过 CGI 接口 Web 服务器就能够将客户端提交的信息转交给服务器端的 CGI 程序处理,最后返回结果给客户端。CGI是放在服务器上的可执行程序,CGI编程没有特定的语言,C语言、linux shell、perl、vb等等都可以进行CGI编程。
MOD_CGI:任何具有MIME类型application/x-httpd-cgi或者被cgi-script处理器处理的文件都将被作为CGI脚本对待并由服务器运行,它的输出将被返回给客户端。可以通过两种途径使文件成为CGI脚本,一种是文件具有已由AddType指令定义的扩展名,另一种是文件位于ScriptAlias目录中。
漏洞利用条件:
- Linux 操作系统
- Apache + PHP (apache 使用 apache_mod_php)
- Apache 开启了
cgi,rewrite - Web 目录给了
AllowOverride权限 - 当前目录可写
靶场环境:
项目地址:https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypass_disable_functions/3
disable_functions比之前的多了
putenv
靶场突破:
若是想临时允许一个目录可以执行cgi程序并且使得服务器将自定义的后缀解析为cgi程序,则可以在目的目录下使用.htaccess文件进行配置
1 | Options +ExecCGI |
然后设置.dizzle结尾的shell文件(shell.dizzle)
1 | !/bin/bash |
将shell.dizzle的权限改为0777

访问shell.dizzle即可执行命令

注:由于Windows 系统中:每行结尾是 “
<回车><换行>“,即 “\r\n“;而UNIX/Linux中:每行结尾是 “<换行>“,即 “\n“,所以在写shell.dizzle文件时必须使用手打,复制粘贴会报错误。
也可以使用如下EXP进行自动生成:
1 |
|
浏览器访问test.php后,会生成.htacess和shell.dizzle文件,文件内容与前面的是一样的。
最后访问shell.dizzle可以得到flag。
攻击 PHP-FPM 监听端口
有关PHP-FPM我在利用SSRF渗透内网主机·中一节中有提到过,不过讲的并不透彻。
推荐大家可以参考一下P神的这篇文章:Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写 。
原理简介:
我查阅了一些相关文章,有关利用PHP-FPM绕过disable_functions,很多都是套用P神的脚本,利用PHP-FPM未授权伪造发送请求。但是这种情况并不能绕过disable_functions,因为本质上还是原来的php解释器来解析,还是会加载php.ini。
而使用蚁剑扩展插件的原理简单的来讲就是:利用PHP-FPM加载一个恶意的ext,使得新启动一个PHP Server后,流量通过.antproxy.php转发到无disabe_functions的PHP Server上,以此达成bypass。
所以这一部分我是直接使用蚁剑的扩展插件进行突破,再对其原理进行分析。
漏洞利用条件:
- Linux 操作系统
- PHP-FPM
- 存在可写的目录, 需要上传
.so文件
靶场环境:
项目地址:https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypass_disable_functions/5
靶场突破:
在蚁剑的插件中心找到“绕过disable_functions”插件并下载安装

也可选择下载源码并拷贝至
antSword/antData/plugins/进行手动安装。项目地址:https://github.com/Medicean/as_bypass_php_disable_functions
添加WebShell完成后,使用「绕过 disable_functions」插件

选择 PHP-FPM/FastCGI 模式进行

注意该模式下需要选择 PHP-FPM 的接口地址,需要自行找配置文件查 FPM 接口地址,默认的是 unix:/// 本地 socket 这种的,如果配置成 TCP 的默认是 127.0.0.1:9000。在本例中,FPM 运行在 127.0.0.1:9000端口
点击「开始」按钮,可以看到成功上传了一个ext、执行了某项操作与上传了一个代理脚本。

成功后可以看到 /var/www/html/ 目录下新建了一个 .antproxy.php 文件。我们创建副本,并将连接的 URL shell 脚本名字改为 .antproxy.php,就可以成功执行命令。


原理分析:
很明显的是蚁剑在服务器上传了 1 个 so 库,和一个.antproxy.php
上服务器先看下.antproxy.php
1 |
|
核心代码:
1 | ... ... |
可以看到它正在与61568端口进行通信(靶场中默认没有安装ps工具,需要手动安装:apt upgrade && apt install procps)

/bin/sh -c php -n -S 127.0.0.1:61568 -t /var/www/html
看来是起了一个新的 PHP Server,-n 就是不使用 php.ini,从而实现了 bypass disable_functions。大致推测出是利用之前上传的 so 库实现的命令执行,然后跑了个 PHP Server。
根据扩展插件的项目仓库(https://github.com/Medicean/as_bypass_php_disable_functions),找到主要代码的位置: core/php_fpm/index.js 。
启动 PHP Server 的代码,然后生成 ext(so/dll扩展) 传到服务器上。
1 | let port = Math.floor(Math.random() * 5000) + 60000; // 60000~65000 |
通过self.generateExt(cmd)生成了一个fileBuffer,然后通过下面代码传了上去。
1 | core.filemanager.upload_file({ |
构造攻击 PHP-FPM 的 Payload,加载扩展库:

触发 Payload 后,就会执行启动一个新的 PHP Server。
后续 shell 都通过.antproxy.php 代理用 61568 的 PHP 解析,也就没有 disable_functions 了。
那么重点就在于generateExt函数了,这个在core/base.js里面。
1 | // 生成扩展 |
直接对二进制数据操作,在 start 到 end 中填入 cmd(就是前面说到的命令/bin/sh -c php -n -S 127.0.0.1:61568 -t /var/www/html)。
由于没有找到ext中so/dll的源码,只能放在IDA中逆向一下。

简单粗暴,so/dll 文件给cmd留点位置,需要执行啥命令就写啥命令进去。然后执行 so/dll 就可以执行该命令了。
利用 PHP7.4 FFI 扩展执行命令
原理简介:
FFI(Foreign Function Interface),即外部函数接口。是指在一种语言里调用另一种语言代码的技术。PHP在7.4版本中新增加了此扩展,PHP 的 FFI 扩展就是一个让你在 PHP 里调用 C 代码的技术。FFI的使用只需声明和调用两步。
漏洞利用条件:
- Linux 操作系统
- PHP >= 7.4
- 开启了 FFI 扩展且
ffi.enable=true

靶场环境:
项目地址:https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypass_disable_functions/8
靶场突破:
上传EXP:
1 |
|
直接访问提交即可。
劫持 GOT 表
原理简介:
在linux系统中,procfs 文件系统是个特殊的存在,对应的是 /proc目录,php 可以通过/proc 目录读写自己所在进程的内存,将非敏感函数地址替换成glibc 中的system地址,从而执行命令,其涉及的技术叫做 GOT表劫持。
通过正常函数实现敏感行为绕过 RASP ,举个例子,如果能将open函数地址换成system地址,那么便可以将fopen打开文件的命令,最终变成glibc调用system执行命令。
详细原理讲解可参考:
漏洞利用条件:
- 内核版本>=2.98
- PHP < 5.6
- 基于www权限的php-fpm/php-cgi work进程必须有权限读写 /proc/self/目录。
- open_basedir=off(或者能绕过open_basedir读写 /lib/ 和/proc/)
注:apache+php 由于 apache调用setuid设置www权限工作进程,/proc/self/目录属于root用户,导致没有权限读写。
nginx+php,对于低版本的php -fpm www权限工作进程, /proc/self/目录属于www用户可以读写。经不完全测试,php<5.6 版本是可以使用GOT表劫持。
靶场环境:
- Linux系统:CentOS 7.6(内核版本3.10.0)
- Web服务:Nginx 1.18.0
- PHP版本:5.4.16
靶场突破:
以下EXP来源:https://github.com/beched/php_disable_functions_bypass
1 |
|
利用 Windows 系统组件 COM
原理简介:
COM(Component Object Model)组件对象模型,是一种跨应用和语言共享二进制代码的方法。COM 可以作为 DLL 被本机程序载入也可以通过 DCOM 被远程进程调用
C:WindowsSystem32 下的 wshom.ocx 能够提供 WshShell 对象和 WshNetwork 对象接口的访问,也就是提供对本地 Windows shell 和计算机所连接的网络上共享资源的访问
漏洞利用条件:
- Windows系统
- php.ini 中开启
com.allow_dcom - 到 php.ini 中开启拓展
extension=php_com_dotnet.dll
靶场环境:
在Windows2008R2服务器上搭建WAMP环境。
php.ini 中开启 com.allow_dcom
1 | com.allow_dcom = true |
因为是在 Windows,如果在拓展文件夹 php/ext/ 中存在 php_com_dotnet.dll
到 php.ini 中开启拓展
1 | extension=php_com_dotnet.dll |
重启服务在 phpinfo 中就能看到开启了 com_dotnet

靶场突破:
上传EXP:
1 |
|
使用上面的 PHP 代码通过 COM 对象的 exec() 方法即可绕过 disable_functions 执行命令。

参考资料
- 简单讲解如何绕过PHP disable_function_h0ld1rs的博客-CSDN博客_disabled function
- PHP 突破 disable_functions 常用姿势以及使用 Fuzz 挖掘含内部系统调用的函数 - 安全客,安全资讯平台 (anquanke.com)
- php中函数禁用绕过的原理与利用 - SegmentFault 思否
- PHP disable_functions Bypass 的方法探究 - Tr0y’s Blog
- PHP之绕过disable_function_shy的博客-CSDN博客_disable_function 绕过
- disable_functions绕过总结 - 雷神众测的个人空间 - OSCHINA - 中文开源技术交流社区
- ByteCTF WP-无需mail bypass disable_functions - 先知社区 (aliyun.com)
- Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写 | 离别歌 (leavesongs.com)
- 攻击PHP-FPM 实现Bypass Disable Functions - 知乎 (zhihu.com)
- 蚁剑 disable_functions 研究 | 明天的乌云 (xlab.app)
- 使用GCONVPATH与iconv进行bypass disablefunctions_lesion的博客-CSDN博客
- 从代码层分析蚁剑“绕过disable_function”插件功能(中) | Erikten
- 针对宝塔的RASP及其disable_functions的绕过 - 先知社区 (aliyun.com)
- 劫持got表绕过disable_functions (qq.com)
- RASP攻防 —— RASP安全应用与局限性浅析 - 博客 - 腾讯安全应急响应中心 (tencent.com)