2012-04-08 110 views
8

我想正常化一个来自外部资源的路径,以防止目录遍历攻击。我知道realpath()函数,但遗憾的是,这个函数只返回现有目录的路径。因此,如果目录不存在(但)realpath()函数会切断不存在的整个路径部分。PHP:规范不存在目录的路径,以防止目录遍历?

所以我的问题是:你知道一个PHP函数,只规范化路径?

PS:我也不想提前创造一切可能的目录;-)

回答

4

有没有内置此PHP函数。使用类似替代以下几点:

function removeDots($path) { 
    $root = ($path[0] === '/') ? '/' : ''; 

    $segments = explode('/', trim($path, '/')); 
    $ret = array(); 
    foreach($segments as $segment){ 
     if (($segment == '.') || strlen($segment) === 0) { 
      continue; 
     } 
     if ($segment == '..') { 
      array_pop($ret); 
     } else { 
      array_push($ret, $segment); 
     } 
    } 
    return $root . implode('/', $ret); 
} 
+0

我也虽然关于这样的解决方案,但由于有多种方法来编码点([见维基百科](http://en.wikipedia.org/ wiki/Directory_traversal_attack#URI_encoded_directory_traversal)),这是不够的: -/ – JepZ 2012-04-10 17:08:39

+2

嗯,这是[MVP] [0]的实现。您可以在它之前添加一个rawurldecode()调用和一个正则表达式匹配,以控制您在路径中允许的字符。另一方面,问题是这是否有内建函数。这个代码只能从那里出发。 [0]:http://en.wikipedia.org/wiki/Minimum_viable_product – 2012-04-10 19:29:37

2

由于Benubird/Cragmonkey纠正我,有些情况下,我以前的答案没有工作。 因而我使一个新的,对于原来的目的:执行好,更少的行,并与纯的正则表达式:

这次我如下面更严格的测试案例进行测试。

$path = '/var/.////./user/./././..//.//../////../././.././test/////'; 

function normalizePath($path) { 
    $patterns = array('~/{2,}~', '~/(\./)+~', '~([^/\.]+/(?R)*\.{2,}/)~', '~\.\./~'); 
    $replacements = array('/', '/', '', ''); 
    return preg_replace($patterns, $replacements, $path); 
} 

正确的答案应该是/测试/。

并不意味着做竞争,但性能测试是必须的:

测试用例: for循环10万次,上的Windows 7,i5-3470四核,3.20 GHz的。

mine:1.746 secs。

Tom Imrei:4.548秒。

Benubird:3.593秒。

熊:4.334秒。

这并不意味着我的版本总是更好。在几种情况下,他们执行simular。

+1

这是不正确的。 a/b /../ c归一化为a/c,而不是a/b/c。 – Benubird 2013-08-20 14:30:49

+1

感谢您的纠正。我编辑了我的帖子。 – Val 2014-04-16 08:14:56

+1

除非有多个'/../'实例,否则这个工作正常。例如,'/ a/b/c /../../../d/e/file.txt'应该解析为'/ d/e/file.txt',而不是只返回一个级别( '/ A/b/d/E/file.txt')。另外,它不喜欢偶数的'/../',比如'/ a/b/c /../../d/e/file.txt',它解析为'/ a/b/.d/e/file.txt'(额外期限) – Cragmonkey 2015-08-04 22:54:17

2

我认为Tamas的解决方案可以工作,但也可以用正则表达式来实现,这可能效率较低但看起来整洁。 Val的解决方案不正确;但是这个工作。

function normalizePath($path) { 
    do { 
     $path = preg_replace(
      array('#//|/\./#', '#/([^/.]+)/\.\./#'), 
      '/', $path, -1, $count 
     ); 
    } while($count > 0); 
    return $path; 
} 

是的,它并不处理./ \等所有可能的不同编码,但这不是它的目的;一个函数只应该做一件事,所以如果你还想将%2e%2e%2f转换成../,首先运行它通过一个单独的函数。

实时路径也解决了符号链接,如果路径不存在,这显然是不可能的;但我们可以去除额外的'/./','/../'和'/'字符。

+0

这适用于某些情况,但有时无法正确执行,例如: $ path ='/var/.////./user/./././..//.//../// //../。/。/../。/测试/////'; $ path ='/var/user/.///////././.././.././././test/'; 两者的结果应该是/ test /,但第一个返回“/ var/test”,第二个返回“/ var/user/test /”。 – Val 2015-08-06 08:34:11

+0

@Val你说得很对,那里有一个错误 - 谢谢你指出!虽然,你的例子并不完全正确 - 第一个例子简化为'/../../ test /',而不是'/ test /'。 – Benubird 2015-08-06 09:08:42

+0

@ Benubird我做了额外的工作来移除多余的/../../,因为它在绝对路径下意味着什么,并且看起来更好。但我同意你的看法,如果把它留在那里,会使它在相对路径上工作变得更加灵活。 – Val 2015-08-07 01:50:28

1

严格但安全的实施。如果你只使用ASCII作为文件名,它将是合适的:

/** 
* Normalise a file path string so that it can be checked safely. 
* 
* @param $path string 
*  The path to normalise. 
* @return string 
* Normalised path or FALSE, if $path cannot be normalized (invalid). 
*/ 
function normalisePath($path) { 
    // Skip invalid input. 
    if (!isset($path)) { 
    return FALSE; 
    } 
    if ($path === '') { 
    return ''; 
    } 

    // Attempt to avoid path encoding problems. 
    $path = preg_replace("/[^\x20-\x7E]/", '', $path); 
    $path = str_replace('\\', '/', $path); 

    // Remember path root. 
    $prefix = substr($path, 0, 1) === '/' ? '/' : ''; 

    // Process path components 
    $stack = array(); 
    $parts = explode('/', $path); 
    foreach ($parts as $part) { 
    if ($part === '' || $part === '.') { 
     // No-op: skip empty part. 
    } elseif ($part !== '..') { 
     array_push($stack, $part); 
    } elseif (!empty($stack)) { 
     array_pop($stack); 
    } else { 
     return FALSE; // Out of the root. 
    } 
    } 

    // Return the "clean" path 
    $path = $prefix . implode('/', $stack); 
    return $path; 
} 
+0

这适用于某些情况,但有时无法正确执行,例如: $ path ='/var/.////./user/./././..//.//..// ///../。/。/../。/测试/////'; $ path ='/var/user/./././.././../.././././test/'; 两者的结果应该是/ test /,但返回空字符串。 – Val 2015-08-06 08:29:54

0

我的2美分。正则表达式只用于空块的路径:

<?php 
echo path_normalize('/a/b/c/../../../d/e/file.txt'); 

echo path_normalize('a/b/../c'); 

echo path_normalize('./../../etc/passwd'); 

echo path_normalize('/var/user/.///////././.././.././././test/'); 

function path_normalize($path){ 
    $path = str_replace('\\','/',$path); 
    $blocks = preg_split('#/#',$path,null,PREG_SPLIT_NO_EMPTY); 
    $res = array(); 

    while(list($k,$block) = each($blocks)){ 
     switch($block){ 
      case '.': 
       if($k == 0) 
        $res = explode('/',path_normalize(getcwd())); 
      break; 
      case '..'; 
       if(!$res) return false; 
       array_pop($res); 
      break; 
      default: 
       $res[]=$block; 
      break; 
      } 
     } 
    return implode('/',$res); 
    } 
?>