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

在PHP中防止目录遍历但允许路径,php,security,directory-traversal,Php,Security,Directory Traversal,我有一个基本路径/随便什么/foo/ 及 $\u GET['path']应该是相对于它的 但是,在不允许目录遍历的情况下,如何实现这一点(读取目录) 例如 无法正确过滤。我猜您的意思是不允许用户遍历目录是吗 如果您试图阻止自己的PHP遍历目录,那么首先应该让PHP正常工作 您需要停止用户的是一个修改的.htaccess文件 Options -Indexes (这一切都假设您谈论的是用户)好吧,一个选项是比较实际路径: $basepath = '/foo/bar/baz/'; $realBase

我有一个基本路径/随便什么/foo/

$\u GET['path']
应该是相对于它的

但是,在不允许目录遍历的情况下,如何实现这一点(读取目录)

例如


无法正确过滤。

我猜您的意思是不允许用户遍历目录是吗

如果您试图阻止自己的PHP遍历目录,那么首先应该让PHP正常工作

您需要停止用户的是一个修改的.htaccess文件

Options -Indexes

(这一切都假设您谈论的是用户)

好吧,一个选项是比较实际路径:

$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
的输出将没有任何“虚拟目录”,例如
..

仅检查诸如../之类的模式是不够的。以“./”为例,哪个URI编码为“%2e%2e%2f”。如果模式检查发生在解码之前,您将错过此遍历尝试。黑客还可以使用其他一些技巧绕过模式检查器,尤其是在使用编码字符串时


正如ircmaxwell所建议的,我通过使用realpath()之类的东西将任何路径字符串规范化为其绝对路径,从而最成功地阻止了这些行为。只有这样,我才能通过将遍历攻击与我预定义的基本路径进行匹配来检查遍历攻击。

ircmaxell的答案并不完全正确。我在几个代码片段中看到了这个解决方案,但它有一个bug,它与
realpath()
的输出有关。
realpath()

/foo/bar/baz/
/富/巴/巴兹巴兹/
由于
realpath()
将删除最后一个目录分隔符,因此如果
$\u 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!
}

您可能会尝试使用regex删除所有../s,但PHP中内置了一些很好的函数,可以做得更好:

$page=basename(realpath($\u GET));
basename-从路径中删除所有目录信息,例如,
。/pages/about.php
将变成
about.php

realpath-返回文件的完整路径,例如,
about.php
将成为
/home/www/pages/about.php
,但仅当文件存在时

组合后,它们只返回文件名,但仅当文件存在时返回

一, 为-index块放置空index.htm

2. 启动时过滤sQS

// Path Traversal Attack
if( strpos($_SERVER["QUERY_STRING"], "../") ){
    exit("P.T.A. B-(");
}

在研究创建新文件或文件夹时,我认为我可以使用两个阶段的方法:

首先使用类
realpath()
函数的自定义实现检查遍历尝试,但该函数适用于任意路径,而不仅仅是现有文件。有一个很好的起点。使用
urldecode()
和任何您认为值得检查的内容来扩展它

现在,使用这种粗糙的方法,您可以过滤掉一些遍历尝试,但可能会遗漏一些特殊字符、符号链接、转义序列等的黑客组合。但是,由于您确定目标文件不存在(请使用
文件\u exists
)没有人可以覆盖任何内容。最坏的情况是,有人可以让你的代码在某处创建文件或文件夹,这在大多数情况下是可以接受的风险,前提是你的代码不允许他们直接写入该文件/文件夹

最后,路径现在指向一个现有位置,因此您现在可以使用上面建议的方法,使用
realpath()
执行正确的检查。如果此时发生了遍历,只要确保防止任何写入目标路径的尝试,您或多或少还是安全的。现在还可以删除目标文件/dir,并说这是一次遍历尝试

我并不是说它不能被黑客攻击,因为毕竟它仍然允许对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不存在的情况下工作。

MainMa理解我试图实现的目标。他提出了
$\u GET
,很明显,他试图防止黑客的目录遍历攻击,所以请不要说“你应该首先让php正常工作”编者:strpos已经是多字节安全的了。引入mb替代方案可能会引入其他漏洞…符号链接呢?或者如果我们要检查的文件还不存在怎么办?(即在预期路径中创建新文件)。@petah符号链接将由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;
}