PHP保护临时文件以供下载

时间:2014-02-13 04:09:56

标签: php apache security download

我是PHP的半新手,我开始潜入文件下载。我使用.xlsx创建.csvPHPExcel文件,并将它们放在临时目录中以供下载。我找到了一个很好的下载脚本,我添加了一些我需要的调整。脚本如下。我已经阅读了这些帖子:

Secure file download in PHP, deny user without permission ...和... Secure files for download ...和... http://www.richnetapps.com/the-right-way-to-handle-file-downloads-in-php/

的download.php

<?php

/*====================
START: Security Checks
====================*/

//(1) Make user it's an authenicated/signed in user with permissions to do this action.
require("lib_protected_page.php");

//(2) Make sure they can ONLY download .xlsx and .csv files
$ext = pathinfo($_GET['file'], PATHINFO_EXTENSION);
if($ext != 'xlsx' && $ext != 'csv') die('Permission Denied.');

//(3) Make sure they can ONLY download files from the tempFiles directory
$file = 'tempFiles/'.$_GET['file'];

//ABOUT ITEM 3 - I still need to change this per this post I found....
/*
http://www.richnetapps.com/the-right-way-to-handle-file-downloads-in-php/

You might think you’re being extra clever by doing something like
$mypath = '/mysecretpath/' .  $_GET['file'];
but an attacker can use relative paths to evade that.
What you must do – always – is sanitize the input. Accept only file names, like this:
$path_parts = pathinfo($_GET['file']);
$file_name  = $path_parts['basename'];
$file_path  = '/mysecretpath/' . $file_name;
And work only with the file name and add the path to it youserlf.
Even better would be to accept only numeric IDs and get the file path and name from a
database (or even a text file or key=>value array if it’s something that doesn’t change
often). Anything is better than blindly accept requests.
If you need to restrict access to a file, you should generate encrypted, one-time IDs, so you can be sure a generated path can be used only once.
*/

/*====================
END: Security Checks
====================*/

download_file($file);

function download_file( $fullPath )
{
    // Must be fresh start
    if( headers_sent() ) die('Headers Sent');

    // Required for some browsers
    if(ini_get('zlib.output_compression'))
        ini_set('zlib.output_compression', 'Off');

    // File Exists?
    if( file_exists($fullPath) )
    {
        // Parse Info / Get Extension
        $fsize = filesize($fullPath);
        $path_parts = pathinfo($fullPath);
        $ext = strtolower($path_parts["extension"]);

        // Determine Content Type
        switch ($ext) {
            case "pdf": $ctype="text/csv"; break;
            case "pdf": $ctype="application/pdf"; break;
            case "exe": $ctype="application/octet-stream"; break;
            case "zip": $ctype="application/zip"; break;
            case "doc": $ctype="application/msword"; break;
            case "xls": $ctype="application/vnd.ms-excel"; break;
            case "xlsx": $ctype="application/vnd.ms-excel"; break;
            case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
            case "gif": $ctype="image/gif"; break;
            case "png": $ctype="image/png"; break;
            case "jpeg":
            case "jpg": $ctype="image/jpg"; break;
            default: $ctype="application/force-download";
        }

        header("Pragma: public"); // required
        header("Expires: 0");
        header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
        //Now, the use of Cache-Control is wrong in this case, especially to both values set to zero, according to Microsoft, but it works in IE6 and IE7 and later ignores it so no harm done.
        header("Cache-Control: private",false); // required for certain browsers
        header("Content-Type: $ctype");
        header("Content-Disposition: attachment; filename=\"".basename($fullPath)."\";" );
        //Note: the quotes in the filename are required in case the file may contain spaces.
        header("Content-Transfer-Encoding: binary");
        header("Content-Length: ".$fsize);
        ob_clean();
        flush();
        readfile( $fullPath );
    }
else
    die('File Not Found');
}
?>

我的问题是......

  1. 我的安全检查是否足够?我只希望具有适当权限的经过身份验证的用户能够仅从.xlsx目录下载.csvtempFiles个文件。但我读过可下载的文件应该在webroot之外,为什么?通过这些检查,我不明白为什么这很重要?
  2. 如果您在地址栏(www.mySite.com/tempFiles)上输入tempFiles目录,则禁止download.php目录,但如果用户以某种方式猜到文件名(这将很难,它们是然后他们可以在地址栏上输入并获取文件(www.mySite.com/tempFiles/iGuessedIt012345.csv)。那么有没有办法不允许(我正在运行Apache),所以他们被迫通过我的脚本(tempFiles)?
  3. 谢谢!安全是我的第一关注点,因此我想在上线之前了解我能做的每一件小事。我在字面上看到的一些示例下载脚本会让你传入一个php文件名,从而允许人们窃取你的源代码。仅供参考,我会定期清理{{1}}目录。永远将文件留在那里将是一个安全问题。

5 个答案:

答案 0 :(得分:2)

我建议您不要让用户通过提供完整路径来请求文件。

过滤'文件'参数。确保它不以点开头,以避免人们请求相对路径到其他文件。

在你的行中:

$file = 'tempFiles/'.$_GET['file'];

如果用户请求文件“../../var/www/my-site/index.php”作为例子,$ file变量的值将成为index.php文件的路径,假设你的tempfiles /目录比你的/ var / www更深两层。

这只是一个例子,你应该明白这个想法。

因此,我认为最重要的是过滤收到的文件参数。您可以通过以下方式检查文件参数中是否存在两个点(..):

if (strpos($GET['file'], "..") !== false) {
  // file parameters contains ..
}

如果按照Ashish的建议,您可以使用与文件和用户关联的令牌开发填充数据库表,那么您可以增加用户请求文件的时间。经过一定量的下载后,您可以拒绝下载请求。

此方法可让您对文件下载保持一定的控制,同时仍为用户提供一定的灵活性,例如,如果用户从不同的位置/设备访问您的Web应用程序,并且需要下载相同的文件一次

答案 1 :(得分:2)

另一种选择是生成文件内容,而不是将其保存在服务器上,而是将该内容推送到具有适当标头的客户端浏览器,以便客户端浏览器将其解释为要下载的文件。最后客户端获取了他的文件而没有访问你的tmp文件夹,你不必担心清理tmp,它是安全的,因为你没有在你的服务器上保存任何东西,“你没有的数据不能被盗”。

pdf示例:

....
$content = $MyPDFCreator->getContent();

header('Content-Type: application/pdf');
header('Content-Length: '.strlen( $content ));
header('Content-disposition: inline; filename="downloadme.pdf"');
header('Cache-Control: public, must-revalidate, max-age=0');
header('Pragma: public');
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');

echo $content;

答案 2 :(得分:1)

将您的文件放在webroot中将允许访问者直接访问您的文件(因为他们可以“运行”webroot中的任何内容 - 在合理范围内)。什么是最好的将有这种设置,或类似的;

/var
  /www
    /html
      /my-site
        index.php
        download.php
        ...
  /tmpFiles
   iGuessedIt012345.csv

这种方式 - 通过一些配置 - 外部世界无法进入tmpFiles/。 这还允许您在download.php

中对具有正确权限的经过身份验证的用户进行检查

另一种可以将它们排除在tmpFiles/目录之外的方法是在该目录中有一个.htaccess文件,其中包含以下内容;

deny from all

这会向尝试访问该目录的任何人发出403 Forbidden条消息。

答案 3 :(得分:0)

我认为您应该提供一个带有临时令牌的唯一网址来下载该文件。此令牌应为一次性使用令牌。一旦用户使用了该令牌,它就会失效,如果用户想要下载该文件,他需要使用提供的和经过验证的方式重新生成下载链接。

例如,您可以提供以下网址:

http://www.somedomain.com/download.php?one_time_token=<some one time token>

访问网址后,您应该使给定的令牌无效。 我认为使用此令牌方法可以保护文件下载过程。 对于文件位置您应避免将文件存储在公共可访问的位置。您应该将文件存储在系统中的其他位置,并仅从那里读取文件。

答案 4 :(得分:0)

经过身份验证的用户可以访问可以在tempFiles中查看文件的页面(download.php)

在tempFiles中将.htaccess设置为“全部拒绝”,以便没有人可以直接访问,然后在download.php中,每个文件都应该可以使用令牌下载,因为Ashish Awasthi很伤心

如果您不喜欢令牌,您可以执行类似下载的操作?file = iGuessedIt012345.csv,但如果您这样做,请使用白名单正则表达式来检查一切是否正确!

示例:

$var="iGuessedIt012345.csv";

if (preg_match('#^[[:alnum:]]+\.csv$#i', $var)){
    echo "ok";
}else{
    echo "bad request";
}

例2:

$var="iGuessed_It-012345.csv";

if (preg_match('#^[a-zA-Z0-9\-\_]+\.csv$#i', $var)){
    echo "ok";
}else{
    echo "bad request";
}