在PHP中清理文件路径

31

我希望能够让我的小程序更加安全,这样潜在的恶意用户就无法查看服务器上的敏感文件。

    $path = "/home/gsmcms/public_html/central/app/webroot/{$_GET['file']}";


    if(file_exists($path)) {
        echo file_get_contents($path);
    } else {
        header('HTTP/1.1 404 Not Found');
    }

从我的经验来看,我知道像 '../../../../../../etc/passwd' 这样的输入会带来麻烦,但是我想知道还有哪些恶意输入需要考虑以及如何防止它们。


1
请仅排除包含../的输入。 - halfdan
3
同意这可以解决一个问题,但我假设还有许多其他危险需要注意。我正在寻找一个铁证似的解决方案来解决所有这些问题。 - SeanDowney
3
始终避免采用黑名单方式的安全策略,因为总会有一些你会忽略的东西。例如退格字符、制表符、换行符、空字符、其他 Unicode 字符或者故意破坏的 Unicode 字符可能会通过你的过滤器,但仍然可能导致 PHP 函数执行你试图保护它免受的某些操作。测试你真正想要的内容:结果路径是否在安全位置下。 - Cheekysoft
9个回答

39

realpath() 将允许您将可能包含相对信息的任何路径转换为绝对路径...然后,您可以确保该路径位于您希望允许下载的特定子目录下。


5
这是我的最终解决方案:$baseDir = "/home/gsmcms/public_html/central/app/webroot/";$path = realpath($baseDir . $_GET['file']);// 如果$baseDir不在路径的开头 0==strpos,很可能是黑客攻击 if(strpos($path, $baseDir)) { die('无效路径'); } elseif(file_exists($path)) { echo file_get_contents($path); } else { header('HTTP/1.1 404 Not Found'); echo "无法找到请求的文件"; } - SeanDowney
2
@SeanDowney,你的解决方案中有一个错误,即你应该检查strpos是否返回false或非零值,而你没有这样做。 - Michael
3
realpath()是一个很好的朋友,但它并不足够——只有在引用一个现有的路径时才返回完整的路径。否则它会返回false;在某些情况下这不会造成任何问题,但考虑到“另存为”操作并自动创建子目录,它可能会破坏游戏。(用户告诉你文件要放在哪里,但由于realpath返回false,你回应“无效路径”)。这可能不是你正在做的事情;只是想提一下。 - dkellner
@dkellner您能否将验证分成两个部分:存在的路径和末尾新自动创建的目录?例如:给定“/this/path/exists/autoCreatedDirectory/file”,请拆分最后一个目录和文件,并使用realpath验证“/this/path/exists”。然后验证“autoCreatedDirectory”和文件名(例如,它们不应该是“..”等)。 - kapace
1
@kapace - 这是一种可能性,但你需要检查很多。由于你不知道哪个子字符串已经存在,你需要通过每个“/”将路径分成段,然后逐步添加一个段并检查你目前拥有的是否存在。这很少需要,但显然是一种解决方案。 - dkellner

14
使用basename而不是试图预测用户可能提供的所有不安全路径。

这可能在某些情况下有效,但我期望输入也包括目录,例如:'/js/jquery/jquery.js'。 - SeanDowney

9

作者的解决方案:

$baseDir = "/home/gsmcms/public_html/central/app/webroot/"; 
$path = realpath($baseDir . $_GET['file']); 

// if baseDir isn't at the front 0==strpos, most likely hacking attempt 
if(strpos($path, $baseDir) !== 0) { 
   die('Invalid Path'); 
} elseif(file_exists($path)) { 
   echo file_get_contents($path); 
} else { 
   header('HTTP/1.1 404 Not Found'); 
   echo "The requested file could not be found"; 
} 

1
请学习使用高级的SO功能! :) 它简化了复制粘贴 - Yauhen Yakimovich
我发现这个解决方案中有一个错误,但是我的修复编辑被拒绝了。 - Michael
请检查上面的内容。为了保持清晰,请更详细地说明并给出理由。 - Yauhen Yakimovich
2
假设如果 strpos($path, $baseDir) === false,那么 strpos($path, $baseDir) !== 0,因为我期望在 PHP 中 false !== 0。所以似乎第五行测试的第二部分是不必要的。 - PypeBros
如果文件不存在,realpath()会返回false,因此elseif(file_exists($path)是多余的。 - Haskell McRavin
这是一个极端情况,但我认为这个示例可以通过在向上移动目录并正确猜测目录名称(“目录神谕”?)时返回有效响应来发现机器上的目录结构。例如,如果我使用file设置为../webroot/some-valid-file进行请求,则会泄漏文件位于webroot目录中。我可以继续猜测树的上下方向,直到找到/home。此时,我有一种方法可以找到机器上的用户名。 - monkeysplayingpingpong

6
如果可能的话,可以使用一个白名单(类似于允许文件的数组),并根据此检查输入的内容:如果用户请求的文件不在列表中,则拒绝请求。

这可能是最好的想法,但它可能比我想做的更费力 :) - SeanDowney
除非你想泄露你网站根目录下所有文件的源代码,否则你可能确实需要这样做。 - Cheekysoft

5

这里存在一个额外且重要的安全风险。该脚本将文件源代码注入输出流,没有任何服务器端处理。这意味着您所有可访问文件的源代码都将泄露到互联网上。


好主意,我会添加一个允许的扩展名白名单,例如:js、css、jpg、gif... - SeanDowney

4
即使您使用 realpath,仍应在使用它之前删除所有“..”。否则,攻击者可以通过暴力破解读取服务器的整个目录结构,例如:“valid_folder /../../ test_if_this_folder_name_exists / valid_folder”-如果应用程序接受此路径,则攻击者知道该文件夹存在。

1
另一种方法:

$path = "/app/webroot/{$_GET['file']}";
$realTarget = realpath($path);

if( strtolower($path) !== strtolower($realTarget) ) {
    // invalid path!
}
// life goes on

1

我认为这是关于PHP7的最佳答案。

这将只允许人们查看他们拥有绝对路径的文件。

它不会让人们通过使所有失败条件报告相同来钓取指定路径之外的有效文件名。

$base_dir = $temp_path;
$path = "";
if(isset($_GET['filename'])) {
    $path = realpath($base_dir.$_GET['filename']);
    //realpath returns false if the file doesnt exist
    if(!$path ||
    //dont look outside temp path 
        substr($path, 0, strlen($base_dir)) != $base_dir){
            header('HTTP/1.1 404 Not Found'); 
            echo "The requested file could not be found";
            die;
    }
}

0

去掉所有的/. /..或\.\.并转换为所有正斜杠,因为不同的环境将接受正斜杠。这应该为路径输入提供相当安全的过滤器。在您的代码中,您应该将其与您不想要访问的父目录进行比较,以防万一。

 $path = realpath(implode('/', array_map(function($value) {return trim($value, '.');}, explode('/', str_replace('\\', '/', $path)))));  

@Koby:为什么您编辑了我的答案? str_replace将所有正斜杠放入,realpath使其成为单个斜杠..不要编辑我的代码。 - snoop_dog

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接