我有一个基本路径 /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
的输出不会有任何 "虚拟目录",例如 .
或 ..
...
ircmaxell的答案并不完全正确。我在几个片段中都看到了这个解决方案,但它有一个与realpath()
的输出相关的bug。 realpath()
函数会删除尾部的目录分隔符,因此请想象两个连续的目录:
/foo/bar/baz/
/foo/bar/baz_baz/
由于 realpath()
会删除最后一个目录分隔符,因此如果$_GET['path']
等于"../baz_baz",那么您的方法将返回"good 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!
}
($realUserPath === false || strcmp($realUserPath, $realBase . DIRECTORY_SEPARATOR) !== 0)
也可以起作用。 - Goozak仅检查类似../等模式是不够的。以"../"为例,它的URI编码为"%2e%2e%2f"。如果您的模式检查发生在解码之前,您将错过这种遍历尝试。黑客可以使用某些其他技巧来规避模式检查,特别是在使用编码字符串时。
我成功地阻止了大多数此类攻击,方法是使用类似realpath()的东西将任何路径字符串规范化为其绝对路径。然后,我开始通过将它们与我预定义的基本路径匹配来检查遍历攻击。
你可能会尝试使用正则表达式来删除所有的../,但是 PHP 中有一些很好的函数可以更好地完成这项工作:
$page = basename(realpath($_GET));
basename - 去除路径中的所有目录信息,例如../pages/about.php
将变为about.php
realpath - 返回文件的完整路径,例如about.php
将成为/home/www/pages/about.php
,但仅当文件存在时。
组合使用它们只返回文件名,但仅当文件存在时。
在创建新文件或文件夹时,我发现可以使用两阶段方法:
首先,使用一个类似于realpath()
的自定义实现来检查遍历尝试,但可以处理任意路径而不仅仅是现有文件。可以从这里 here入手,再加上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 不存在时工作。
$baseDirectory = '/path/to/file/'; // Define the base directory where the file(s) is/are located
if ($_SERVER['HTTP_HOST'] == 'localhost') {
$baseDirectory = 'C:\xampp\htdocs'; // For localhost use
}
// Check if the 'REQUEST_URI' is set in the server variables
if (!isset($_SERVER['REQUEST_URI'])) {
$this->logger->logEvent('REQUEST_URI NOT SET: '.__LINE__.' '.__FILE__);
die('Invalid file request');
}
// Get the requested URI from the server variables
$requestedUri = $_SERVER['REQUEST_URI'];
// Extract the file path from the requested URI
$requestedFile = parse_url($requestedUri, PHP_URL_PATH);
// Normalize the file path to remove any relative components
$requestedFile = realpath($baseDirectory . $requestedFile);
// Check if the normalized file path starts with the base directory
if (strpos($requestedFile, $baseDirectory) !== 0) {
$this->logger->logEvent('Directory Traversal: '.$requestedUri);
die('Invalid file path');
}
// Check if the requested file exists
if (!file_exists($requestedFile)) {
$this->logger->logEvent($requestedFile. ' not found: '.__LINE__.' '.__FILE__);
die('File not found');
}
// Serve the file to the user
将一个空的index.htm放在-Index块中
按开始筛选 sQS
// Path Traversal Attack
if( strpos($_SERVER["QUERY_STRING"], "../") ){
exit("P.T.A. B-(");
}
../
的位置 === 0
,则此操作失败。 - mickmackusa