11

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

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

PS:我也不想提前创建所有可能的目录;-)

4

5 回答 5

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);
}
于 2012-04-09T01:24:36.080 回答
2

我认为 Tamas 的解决方案会起作用,但也可以使用正则表达式来完成,这可能效率较低但看起来更整洁。Val 的解决方案不正确;但这一个有效。

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

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

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

于 2013-08-20T14:44:53.037 回答
2

感谢 Benubird / Cragmonkey 纠正了我在某些情况下我之前的答案不起作用。因此我制作了一个新的,最初的目的是:性能好,行数更少,并且使用纯正则表达式:

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

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

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

正确答案是 /test/。

不是为了比赛,但性能测试是必须的:

测试用例:循环 100k 次,在 Windows 7、i5-3470 四核、3.20 GHz 上。

我的:1.746 秒。

汤姆·伊姆雷:4.548 秒。

贝努鸟:3.593 秒。

大熊座:4.334 秒。

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

于 2013-01-16T09:24:49.917 回答
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;
}
于 2014-11-15T00:59:54.507 回答
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);
}
?>
于 2016-11-07T14:57:01.407 回答