在PHP中阻止目录遍历但允许路径

Joh*_*nny 33 php security directory-traversal

我有一个基本路径/无论/ foo /

并且 $_GET['path']应该是相对的.

但是,如何在不允许目录遍历的情况下完成此操作(读取目录)?

例如.

/\.\.|\.\./
Run Code Online (Sandbox Code Playgroud)

不会过滤得当.

irc*_*ell 110

那么,一种选择是比较真实路径:

$basepath = '/foo/bar/baz/';
$realBase = realpath($basepath);

$userpath = $basepath . $_GET['path'];
$realUserPath = realpath($userpath);

if ($realUserPath === false || strpos($realUserPath, $realBase) !== 0) {
    //Directory Traversal!
} else {
    //Good path!
}
Run Code Online (Sandbox Code Playgroud)

基本上,realpath()将解决提供路径到实际硬盘的物理路径(解决符号链接..,.,/,//,等)......所以,如果真实的用户路径不与真正的基本路径开始,它试图做一个遍历.请注意,输出realpath没有任何"虚拟目录",如......

  • 写新文件怎么样?如果文件不存在,realpath似乎返回空. (3认同)
  • @petah符号链接将通过realpath解析为规范路径.对于不存在的文件,我怀疑它是否是一个可解决的问题,我建议不要首先这样做(永远不允许用户直接指定新文件)... (2认同)

小智 15

ircmaxell的回答并不完全正确.我已经在几个片段中看到了这个解决方案,但它有一个与输出有关的bug realpath().该realpath()函数删除了尾随目录分隔符,因此想象两个连续的目录,例如:

/foo/bar/baz/

/foo/bar/baz_baz/
Run Code Online (Sandbox Code Playgroud)

正如realpath()删除最后一个目录分隔符一样,如果$_GET['path']等于"../baz_baz" ,你的方法将返回"good path",因为它会像

strpos("/foo/bar/baz_baz", "/foo/bar/baz")
Run Code Online (Sandbox Code Playgroud)

也许:

$basepath = '/foo/bar/baz/';
$realBase = realpath($basepath);

$userpath = $basepath . $_GET['path'];
$realUserPath = realpath($userpath);

if ($realUserPath === false || strcmp($realUserPath, $realBase) !== 0 || strpos($realUserPath, $realBase . DIRECTORY_SEPARATOR) !== 0) {
    //Directory Traversal!
} else {
    //Good path!
}
Run Code Online (Sandbox Code Playgroud)


Cow*_*lby 5

仅检查../之类的样式是不够的。以“ ../”为例,其中URI编码为“%2e%2e%2f”。如果您的模式检查发生在解码之前,那么您将错过这种遍历尝试。黑客还有其他技巧可以规避模式检查器,尤其是在使用编码字符串时。

通过使用ircmaxwell建议的诸如realpath()之类的方法将任何路径字符串规范化为其绝对路径,我获得了最大的成功。只有到那时,我才开始通过将遍历攻击与我预定义的基本路径进行匹配来检查遍历攻击。