我想从外部资源规范化路径以防止目录遍历攻击。我知道realpath()函数,但遗憾的是这个函数只返回现有目录的路径。因此,如果目录不存在(还),则realpath()函数会切断不存在的路径的整个部分。
所以我的问题是:你知道一个只能规范化路径的PHP函数吗?
PS:我也不想提前创建所有可能的目录; - )
答案 0 :(得分:6)
没有内置的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);
}
答案 1 :(得分:2)
感谢Benubird / Cragmonkey纠正我,在某些情况下我的回答没有用。 因此,我创造了一个新的,原始目的:表现良好,更少的线条,并使用纯正则表达式:
这次我测试了更严格的测试用例,如下所示。
$path = '/var/.////./user/./././..//.//../////../././.././test/////';
function normalizePath($path) {
$patterns = array('~/{2,}~', '~/(\./)+~', '~([^/\.]+/(?R)*\.{2,}/)~', '~\.\./~');
$replacements = array('/', '/', '', '');
return preg_replace($patterns, $replacements, $path);
}
正确的答案是/ test /.
不打算参加比赛,但性能测试是必须的:
测试用例: for循环100k次,在Windows 7,i5-3470四核,3.20 GHz。
我的:1.746秒。Tom Imrei:4.548秒。
Benubird:3.593秒。
Ursa:4.334秒。
这并不意味着我的版本总是更好。在几种情况下,他们执行类似的。
答案 2 :(得分:2)
我认为Tamas的解决方案可行,但也可以使用正则表达式,这可能效率较低但看起来更整洁。 Val的解决方案不正确;但是这个有效。
function normalizePath($path) {
do {
$path = preg_replace(
array('#//|/\./#', '#/([^/.]+)/\.\./#'),
'/', $path, -1, $count
);
} while($count > 0);
return $path;
}
是的,它没有处理可能存在的所有可能的./ \等编码的不同编码,但这不是它的目的;一个函数应该只做一件事,所以如果你想将%2e%2e%2f
转换成../
,首先通过一个单独的函数运行它。
Realpath还解析了符号链接,如果路径不存在,这显然是不可能的;但我们可以删除额外的'/./','/../'和'/'字符。
答案 3 :(得分: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;
}
答案 4 :(得分: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);
}
?>