是否应重命名上传的文件?

时间:2013-07-25 18:22:57

标签: php security file-upload

我一直在阅读PHP文件上传安全性,一些文章建议重命名文件。例如,OWASP文章Unrestricted File Upload 表示:

  

建议使用算法来确定文件名。对于   例如,文件名可以是文件名的MD5哈希加上   当天的日期。

如果用户上传名为Cake Recipe.doc的文件,是否真的有理由将其重命名为45706365b7d5b1f35

如果答案是肯定的,无论出于何种原因,那么如何跟踪原始文件名和扩展名呢?

3 个答案:

答案 0 :(得分:13)

对于您的主要问题,重命名文件是一种好习惯,答案肯定是肯定的,特别是如果您要创建一个文件存储库形式,用户上传他们选择的文件(和文件名),原因如下: / p>

  1. 安全性 - 如果你的应用程序写得不好,允许通过名称或直接访问来下载文件(这很可怕,但它会发生),对于用户来说,无论是恶意的还是故意的,都要“猜测” “文件的名称。
  2. 唯一性 - 两个不同的人上传同名文件的可能性非常高(即avatar.gif,readme.txt,video.avi等)。使用唯一标识符可以显着降低两个文件名称相同的可能性。
  3. 版本控制 - 使用唯一名称保留文档的多个“版本”要容易得多。它还避免了需要额外的代码来解析文件名以进行更改。一个简单的例子就是document.pdf到document(1).pdf,当你不低估用户创建可怕名字的能力时会变得更加复杂。
  4. 长度 - 使用已知文件名长度总是比使用未知文件名长度更好。我总是可以知道(我的文件路径)+(X个字母)是一定的长度,其中(我的文件路径)+(随机用户文件名)是完全未知的。
  5. 操作系统 - 在尝试将极随机/长文件名写入驱动器时,上述长度也会产生问题。您必须考虑特殊字符,长度和修剪文件名的问题(用户可能无法接收工作文件,因为扩展已被修剪)。
  6. 执行 - 操作系统很容易执行名为.exe,.php或(插入其他扩展名)的文件。没有扩展名时很难。
  7. URL编码 - 确保名称是URL安全的。 Cake Recipe.doc不是URL安全名称,并且可以在某些系统(服务器或浏览器端)/某些情况下,当名称应为urlencode d值时导致不一致。
  8. 至于存储信息,你通常会在数据库中执行此操作,与您已经存在的需求没有什么不同,因为您需要一种方式来返回文件(谁上传,名称是什么,偶尔会在哪里存储,上传时间,有时是大小)。除了文件的用户名之外,您只需添加文件的实际存储名称。

    OWASP建议并不错 - 使用文件名和时间戳(不是日期)将是最独特的。我更进一步,将microtime包含在时间戳中,并经常包含其他一些独特的信息,以便在同一时间范围内重复上传小文件 - 我还会存储上传日期这是针对md5冲突的附加保险,它在存储许多文件和多年的系统中具有更高的概率。你不可能在同一天使用文件名和microtime生成两个像md5s。一个例子是:

    $filename = date('Ymd') . '_' . md5($uploaded_filename . microtime());
    

    我的2美分。

答案 1 :(得分:2)

当我上传文件时,我使用PHP的unique_id()函数存储在服务器上的文件名(我保留了文件扩展名,因为当我查看存储目录中的所有文件时,它更容易让我本地文件系统)。

我将文件保存在网站文件系统之外(也就是说你永远不能直接浏览文件)。

我总是使用php的move_uploaded_file()函数将文件保存到服务器。

我将原始文件名,存储它的路径/文件名以及您可能需要的任何其他项目相关信息存储在数据库中。

在我的一些实现中,我还创建了文件内容的哈希值,并将其保存在数据库中。然后使用其他上传的文件查看数据库,看看我是否已经存储了该确切文件的副本。

一些代码示例:

表格:

 form method="post" enctype="multipart/form-data" action="your_form_handler.php">



<input type="file" name="file1" value="" />



 <input type="submit" name="b1" value="Upload File" />

 </form>

表单处理程序:

 <?php

 // pass the file input name used in the form and any other pertinent info to store in the db, username in this example
_process_uploaded_file('file1', 'jsmith');

exit;



function _process_uploaded_file($file_key, $username='guest'){    
    if(array_key_exists($file_key, $_FILES)){
        $file = $_FILES[$file_key];
        if($file['size'] > 0){
            $data_storage_path = '/path/to/file/storage/directory/';
            $original_filename = $file['name'];
            $file_basename     = substr($original_filename, 0, strripos($original_filename, '.')); // strip extention
            $file_ext          = substr($original_filename, strripos($original_filename, '.'));
            $file_md5_hash     = md5_file($file['tmp_name']);
            $stored_filename   = uniqid();
            $stored_filename  .= $file_ext;                        
            if(! move_uploaded_file($file['tmp_name'], $data_storage_path.$stored_filename)){
                 // unable to move,  check error_log for details
                 return 0;
            }
            // insert a record into your db using your own mechanism ...
            // $statement = "INSERT into yourtable (original_filename, stored_filename, file_md5_hash, username, activity_date) VALUES (?, ?, ?, ?, NOW())";

            // success, all done
            return 1;
        }
    }    
    return 0;
}

?>

处理下载请求的程序

 <?php

    // Do all neccessary security checks etc to make sure the user is allowed to download the file, etc..

    // 

    $file = '/path/to/your/storage/directory' . 'the_stored_filename';
$filesize = filesize($file);
header('Content-Description: File Transfer');
header("Content-type: application/forcedownload");
header("Content-disposition: attachment; filename=\"filename_to_display.example\"");
header("Content-Transfer-Encoding: Binary");
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header("Content-length: ".$filesize);
ob_clean();
flush();
readfile("$file");
exit;

如果您想在用户请求的同一页面中显示下载内容,请查看我对此帖子的回答:Dowloading multiple PDF files from javascript

答案 2 :(得分:1)

有一个很好的理由需要重命名上传的文件,它是, 如果两个上传相同的文件,或同名的文件,后一个文件将替换不合适的前一个文件。

您可以使用像

这样的哈希算法
$extensions =  explode(".",$file-name);
$ext = $extensions[count($extensions)-1]; 
$file-name = md5($file-name .$_SERVER['REMOTE_ADDR']) .'.' .$ext;

然后你可以保存文件名,散列文件名,上传者详细信息,日期,时间跟踪文件的详细信息