PHP:规范化不存在目录的路径以防止目录遍历?

PHP:规范化不存在目录的路径以防止目录遍历?,php,path,normalize,directory-traversal,Php,Path,Normalize,Directory Traversal,我想规范化来自外部资源的路径,以防止目录遍历攻击。我知道这个函数,但遗憾的是,这个函数只返回现有目录的路径。因此,如果目录还不存在,realpath()函数会切断路径中不存在的部分 所以我的问题是:你知道一个只规范化路径的PHP函数吗 PS:我也不想预先创建所有可能的目录;-) 没有内置的PHP函数。改为使用类似以下内容: function removeDots($path) { $root = ($path[0] === '/') ? '/' : ''; $segments

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

所以我的问题是:你知道一个只规范化路径的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);
}

感谢贝努伯德/克雷格蒙基纠正了我之前的回答在某些情况下不起作用的错误。 因此,为了最初的目的,我做了一个新的:使用纯正则表达式执行良好的、更少的行:

这次我使用更严格的测试用例进行测试,如下所示

正确答案应该是/test/

不是为了参加比赛,但必须进行性能测试:

测试用例: 在Windows 7、i5-3470四核、3.20 GHz上,循环次数为10万次

我的:1.746秒

汤姆·伊姆瑞:4.548秒

贝努伯:3.593秒

熊:4.334秒

这并不意味着我的版本总是更好。在几种情况下,它们执行相同的操作


我认为Tamas的解决方案会起作用,但也可以使用regex,它可能效率较低,但看起来更整洁。Val的解决方案不正确;但是这个有效

function normalizePath($path) {
    do {
        $path = preg_replace(
            array('#//|/\./#', '#/([^/.]+)/\.\./#'),
            '/', $path, -1, $count
        );
    } while($count > 0);
    return $path;
}
是的,它不能处理所有可能存在的./\etc的不同编码,但这不是它的目的;一个函数只能做一件事,因此如果您还想将
%2e%2e%2f
转换为
。/
,请先通过单独的函数运行它


Realpath还解析符号链接,如果路径不存在,这显然是不可能的;但是我们可以去掉额外的“/../”、“/../”和“/”字符。

严格但安全的实现。如果仅使用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;
}

我的2美分。regexp仅用于路径的空块:

<?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);
}
?>

我也考虑过这样一个解决方案,但由于有多种方法来编码dots(),这是不够的:-/嗯,这就是[MVP][0]实现。您可以添加一个rawurldecode()调用,并在其前面添加一个regexp匹配,以控制允许在路径中使用哪些字符。另一方面,问题是是否有一个内置函数用于此。这段代码是唯一可行的方法。[0]:除非存在多个
/../
实例,否则此操作正常。例如,
/a/b/c/../../../../../d/e/file.txt
应解析为
/d/e/file.txt
,而只返回一个级别(
/a/b/d/e/file.txt
)。另外,它不喜欢偶数的
/../../../../code>,例如
/a/b/c/../../d/e/file.txt
,它解析为
/a/b/.d/e/file.txt
(额外句点)@Cragmonkey,谢谢您的更正!我编辑了我的帖子。这在某些情况下有效,但有时无法正确执行,例如:$path='/var////user//../../..//////////////////test//////$path='/var/user/../../../../../../../../test/';两者的结果都应为/test/,但返回空字符串。这在某些情况下有效,但有时无法正确执行,例如:$path='/var/////user///../../../../../../../../../test//'$path='/var/user////../../..////test/';两者的结果都应该是/test/,但是第一个返回“/var/test”,第二个返回“/var/user/test/”@Val你说得很对,那里有一个错误-谢谢你指出!虽然,您的示例并不完全正确-第一个示例简化为
/../../../test/
,而不是
/test/
@Benubird我做了额外的工作来删除冗余的/../../。但是我同意你的观点,把它放在那里会使使用相对路径更灵活。这对
/smth//smth/././
不起作用,请使用
'./(?:([^/]+)/\./.\./.+\./.\./.\./.+\.
。此外,不幸的是,您的实现没有解析
/folder.with.dots/./
<?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);
}
?>