我想正常化一个来自外部资源的路径,以防止目录遍历攻击。我知道realpath()函数,但遗憾的是,这个函数只返回现有目录的路径。因此,如果目录不存在(但)realpath()函数会切断不存在的整个路径部分。PHP:规范不存在目录的路径,以防止目录遍历?
所以我的问题是:你知道一个PHP函数,只规范化路径?
PS:我也不想提前创造一切可能的目录;-)
我想正常化一个来自外部资源的路径,以防止目录遍历攻击。我知道realpath()函数,但遗憾的是,这个函数只返回现有目录的路径。因此,如果目录不存在(但)realpath()函数会切断不存在的整个路径部分。PHP:规范不存在目录的路径,以防止目录遍历?
所以我的问题是:你知道一个PHP函数,只规范化路径?
PS:我也不想提前创造一切可能的目录;-)
有没有内置此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);
}
由于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。
这是不正确的。 a/b /../ c归一化为a/c,而不是a/b/c。 – Benubird 2013-08-20 14:30:49
感谢您的纠正。我编辑了我的帖子。 – Val 2014-04-16 08:14:56
除非有多个'/../'实例,否则这个工作正常。例如,'/ 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
我认为Tamas的解决方案可以工作,但也可以用正则表达式来实现,这可能效率较低但看起来整洁。 Val的解决方案不正确;但是这个工作。
function normalizePath($path) {
do {
$path = preg_replace(
array('#//|/\./#', '#/([^/.]+)/\.\./#'),
'/', $path, -1, $count
);
} while($count > 0);
return $path;
}
是的,它并不处理./ \等所有可能的不同编码,但这不是它的目的;一个函数只应该做一件事,所以如果你还想将%2e%2e%2f
转换成../
,首先运行它通过一个单独的函数。
实时路径也解决了符号链接,如果路径不存在,这显然是不可能的;但我们可以去除额外的'/./','/../'和'/'字符。
这适用于某些情况,但有时无法正确执行,例如: $ path ='/var/.////./user/./././..//.//../// //../。/。/../。/测试/////'; $ path ='/var/user/.///////././.././.././././test/'; 两者的结果应该是/ test /,但第一个返回“/ var/test”,第二个返回“/ var/user/test /”。 – Val 2015-08-06 08:34:11
@Val你说得很对,那里有一个错误 - 谢谢你指出!虽然,你的例子并不完全正确 - 第一个例子简化为'/../../ test /',而不是'/ test /'。 – Benubird 2015-08-06 09:08:42
@ Benubird我做了额外的工作来移除多余的/../../,因为它在绝对路径下意味着什么,并且看起来更好。但我同意你的看法,如果把它留在那里,会使它在相对路径上工作变得更加灵活。 – Val 2015-08-07 01:50:28
严格但安全的实施。如果你只使用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;
}
这适用于某些情况,但有时无法正确执行,例如: $ path ='/var/.////./user/./././..//.//..// ///../。/。/../。/测试/////'; $ path ='/var/user/./././.././../.././././test/'; 两者的结果应该是/ test /,但返回空字符串。 – Val 2015-08-06 08:29:54
我的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);
}
?>
我也虽然关于这样的解决方案,但由于有多种方法来编码点([见维基百科](http://en.wikipedia.org/ wiki/Directory_traversal_attack#URI_encoded_directory_traversal)),这是不够的: -/ – JepZ 2012-04-10 17:08:39
嗯,这是[MVP] [0]的实现。您可以在它之前添加一个rawurldecode()调用和一个正则表达式匹配,以控制您在路径中允许的字符。另一方面,问题是这是否有内建函数。这个代码只能从那里出发。 [0]:http://en.wikipedia.org/wiki/Minimum_viable_product – 2012-04-10 19:29:37