在没有realpath()的PHP中清理文件路径

aco*_*der 3 php security sanitization

有没有办法安全地消毒路径输入,而不使用realpath()

目标是防止恶意输入 ../../../../../path/to/file

 $handle = fopen($path . '/' . $filename, 'r');
Run Code Online (Sandbox Code Playgroud)

Gum*_*mbo 6

RFC 3986中描述了一种删除点序列算法,用于在相对URI参考解析过程中解释和删除引用路径中的特殊和完整路径段....

您也可以将此算法用于文件系统路径:

// as per RFC 3986
// @see http://tools.ietf.org/html/rfc3986#section-5.2.4
function remove_dot_segments($input) {
    // 1.  The input buffer is initialized with the now-appended path
    //     components and the output buffer is initialized to the empty
    //     string.
    $output = '';

    // 2.  While the input buffer is not empty, loop as follows:
    while ($input !== '') {
        // A.  If the input buffer begins with a prefix of "`../`" or "`./`",
        //     then remove that prefix from the input buffer; otherwise,
        if (
            ($prefix = substr($input, 0, 3)) == '../' ||
            ($prefix = substr($input, 0, 2)) == './'
           ) {
            $input = substr($input, strlen($prefix));
        } else

        // B.  if the input buffer begins with a prefix of "`/./`" or "`/.`",
        //     where "`.`" is a complete path segment, then replace that
        //     prefix with "`/`" in the input buffer; otherwise,
        if (
            ($prefix = substr($input, 0, 3)) == '/./' ||
            ($prefix = $input) == '/.'
           ) {
            $input = '/' . substr($input, strlen($prefix));
        } else

        // C.  if the input buffer begins with a prefix of "/../" or "/..",
        //     where "`..`" is a complete path segment, then replace that
        //     prefix with "`/`" in the input buffer and remove the last
        //     segment and its preceding "/" (if any) from the output
        //     buffer; otherwise,
        if (
            ($prefix = substr($input, 0, 4)) == '/../' ||
            ($prefix = $input) == '/..'
           ) {
            $input = '/' . substr($input, strlen($prefix));
            $output = substr($output, 0, strrpos($output, '/'));
        } else

        // D.  if the input buffer consists only of "." or "..", then remove
        //     that from the input buffer; otherwise,
        if ($input == '.' || $input == '..') {
            $input = '';
        } else

        // E.  move the first path segment in the input buffer to the end of
        //     the output buffer, including the initial "/" character (if
        //     any) and any subsequent characters up to, but not including,
        //     the next "/" character or the end of the input buffer.
        {
            $pos = strpos($input, '/');
            if ($pos === 0) $pos = strpos($input, '/', $pos+1);
            if ($pos === false) $pos = strlen($input);
            $output .= substr($input, 0, $pos);
            $input = (string) substr($input, $pos);
        }
    }

    // 3.  Finally, the output buffer is returned as the result of remove_dot_segments.
    return $output;
}
Run Code Online (Sandbox Code Playgroud)


pax*_*blo 5

不确定为什么你不想使用,realpath但路径名称sanitisation是一个非常简单的概念,沿着以下几行:

  • 如果路径是相对的(不是以/)开头,则将其作为当前工作目录的前缀/,并使其成为绝对路径.
  • 取代多于一个的所有序列/与单个一个(a)中.
  • 替换所有出现的/.//.
  • /.如果在最后删除.
  • 替换/anything/..//.
  • /anything/..如果在最后删除.

anything在这种情况下,文本意味着最长的字符序列不是/.

请注意,这些规则应该持续应用,直到它们都不会导致更改为止.换句话说,做所有六个(一次通过).如果字符串改变了,那么回去再做六次(另一次通过).继续这样做,直到字符串与刚刚执行的传递之前相同.

完成这些步骤后,您将拥有可以检查有效模式的规范路径名称.最有可能是任何不能开始的东西../(换句话说,它不会试图超越起点.可能还有其他规则想要应用,但这超出了这个问题的范围.


(a)如果您正在处理//在路径开头处理特殊的系统,请确保/在开头用两个字符替换多个字符.这是POSIX允许(但不强制要求)对倍数进行特殊处理的唯一地方,在所有其他情况下,多个/字符相当于一个字符.

  • 当检查时路径不存在时,`realpath`可能不合适(不是在这种情况下,因为OP将打开文件进行读取,但只是为了清楚) (4认同)