我有一个基本路径 /whatever/foo/
并且
$_GET['path']
应该是相对的。
但是如何在不允许目录遍历的情况下完成此操作(读取目录)?
例如。
/\.\.|\.\./
不会正确过滤。
我有一个基本路径 /whatever/foo/
并且
$_GET['path']
应该是相对的。
但是如何在不允许目录遍历的情况下完成此操作(读取目录)?
例如。
/\.\.|\.\./
不会正确过滤。
好吧,一种选择是比较真实路径:
$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!
}
基本上,realpath()
将提供的路径解析为实际的硬物理路径(解析符号链接、..
、.
、/
、//
等)...因此,如果实际用户路径不是以实际基本路径开头,则它正在尝试进行遍历。请注意, 的输出realpath
不会有任何“虚拟目录”,例如.
or ..
...
ircmaxell 的回答并不完全正确。我已经在几个片段中看到了该解决方案,但它有一个与realpath()
. 该realpath()
函数删除了尾随目录分隔符,因此想象两个连续的目录,例如:
/foo/bar/baz/
/foo/bar/baz_baz/
与realpath()
删除最后一个目录分隔符一样,如果等于“../baz_baz”,您的方法将返回“好路径”,$_GET['path']
因为它类似于
strpos("/foo/bar/baz_baz", "/foo/bar/baz")
也许:
$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!
}
检查 ../ 之类的模式是不够的。以“../”为例,哪个 URI 编码为“%2e%2e%2f”。如果您的模式检查发生在解码之前,您将错过此遍历尝试。黑客可以采取其他一些技巧来规避模式检查器,尤其是在使用编码字符串时。
正如 ircmaxwell 所建议的那样,通过使用诸如 realpath() 之类的东西将任何路径字符串规范化到其绝对路径,我在阻止这些方面取得了最大的成功。只有这样,我才开始通过将遍历攻击与我预定义的基本路径进行匹配来检查它们。
您可能很想尝试使用正则表达式来删除所有 ../s,但是 PHP 中内置了一些不错的函数,它们会做得更好:
$page = basename(realpath($_GET));
basename - 从路径中删除所有目录信息,例如../pages/about.php
将变为about.php
realpath - 返回文件的完整路径,例如about.php
会变成/home/www/pages/about.php
,但前提是文件存在。
结合起来,它们只返回文件名,但前提是文件存在。
在研究新文件或文件夹的创建时,我认为我可以使用两阶段方法:
首先使用 like 函数的自定义实现检查遍历尝试realpath()
,但它适用于任意路径,而不仅仅是现有文件。这里有一个很好的起点。urldecode()
用你认为值得检查的任何东西来扩展它。
现在使用这种粗略的方法,您可以过滤掉一些遍历尝试,但是您可能会错过一些特殊字符、符号链接、转义序列等的骇人听闻的组合。但是既然您确定目标文件不存在(检查使用file_exists
)没有人可以覆盖任何东西。最坏的情况是有人可以让您的代码在某处创建文件或文件夹,这在大多数情况下可能是可接受的风险,前提是您的代码不允许他们立即写入该文件/文件夹。
最后,路径现在指向现有位置,因此您现在可以使用上面建议的方法使用realpath()
. 如果此时事实证明发生了遍历,只要您确保防止任何尝试写入目标路径,您或多或少仍然是安全的。现在您也可以删除目标文件/目录并说这是一次遍历尝试。
我并不是说它不能被黑客入侵,因为毕竟它仍然可能允许对 FS 进行非法更改,但仍然比仅进行无法使用的自定义检查更好realpath()
,并且通过临时打开滥用窗口并且某处的空文件或文件夹低于允许他们将其永久化甚至写入其中,因为只有自定义检查可能会错过一些边缘情况。
如果我错了也请纠正我!
我写了一个函数来检查遍历:
function isTraversal($basePath, $fileName)
{
if (strpos(urldecode($fileName), '..') !== false)
return true;
$realBase = realpath($basePath);
$userPath = $basePath.$fileName;
$realUserPath = realpath($userPath);
while ($realUserPath === false)
{
$userPath = dirname($userPath);
$realUserPath = realpath($userPath);
}
return strpos($realUserPath, $realBase) !== 0;
}
仅此行就if (strpos(urldecode($fileName), '..') !== false)
足以防止遍历,但是,黑客可以通过多种不同的方式遍历目录,因此最好确保用户从真实的基本路径开始。
仅仅检查用户从真正的基本路径开始是不够的,因为黑客可以遍历当前目录并发现目录结构。
允许代码在while
$fileName 不存在时工作。
我假设您的意思是不允许用户遍历目录是吗?
如果你试图阻止你自己的 PHP 遍历目录,你应该首先让 php 正常工作。
您需要阻止用户的是修改后的 .htaccess 文件...
Options -Indexes
(这一切都假设您在谈论用户)
为 -Index 块放置一个空 index.htm
在开始时过滤 sQS
// Path Traversal Attack
if( strpos($_SERVER["QUERY_STRING"], "../") ){
exit("P.T.A. B-(");
}