提问人:Nate 提问时间:7/26/2013 更新时间:5/24/2017 访问量:5272
上传的文件应该重命名吗?
Should uploaded files be renamed?
问:
我一直在阅读有关PHP文件上传安全性的文章,并且有几篇文章建议重命名文件。例如,OWASP文章“不受限制的文件上传”说:
建议使用算法来确定文件名。为 实例,文件名可以是文件名的 MD5 哈希值加上 当天的日期。
如果用户上传了一个名为 真的有什么理由将其重命名为 ?Cake Recipe.doc
45706365b7d5b1f35
如果答案是肯定的,无论出于何种原因,那么您如何跟踪原始文件名和扩展名?
答:
当我上传文件时,我使用 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;
如果您想在用户请求下载的同一页面中显示下载,请查看我对这篇文章的回答:从 javascript 下载多个 PDF 文件
对于您的主要问题,重命名文件是否是一种好的做法,答案是肯定的,特别是如果您正在创建一种文件存储库形式,用户可以在其中上传他们选择的文件(和文件名),原因如下:
- 安全性 - 如果您有一个写得很差的应用程序,允许按名称或直接访问下载文件(这很可怕,但它发生了),那么用户,无论是恶意的还是故意的,“猜测”文件的名称要困难得多。
- 唯一性 - 两个不同的人上传同名文件的可能性非常高(即头像.gif,自述文件.txt,video.avi等)。使用唯一标识符可显著降低两个文件同名的可能性。
- 版本控制 -- 使用唯一名称保留文档的多个“版本”要容易得多。它还避免了需要额外的代码来分析文件名以进行更改。一个简单的例子是 document.pdf 到 document(1).pdf,当你不低估用户为事物创建可怕名称的能力时,它会变得更加复杂。
- 长度 (Length) -- 使用已知的文件名长度总是比使用未知的文件名长度要好。我总是可以知道(我的文件路径)+(X个字母)是一定的长度,其中(我的文件路径)+(随机用户文件名)是完全未知的。
- 操作系统 -- 在尝试将极其随机/长的文件名写入驱动器时,上述长度也会产生问题。您必须考虑特殊字符、长度和修剪文件名的问题(用户可能无法收到工作文件,因为扩展名已被修剪)。
- 执行 -- 操作系统很容易执行名为 .exe 或 .php 的文件,或者(插入其他扩展名)。当没有扩展时,这很难。
- URL 编码 -- 确保名称是 URL 安全的。 不是 URL 安全名称,并且在某些系统(服务器端或浏览器端)/某些情况下,当名称应为 d 值时,可能会导致不一致。
Cake Recipe.doc
urlencode
至于存储信息,您通常会在数据库中执行此操作,这与您已经拥有的需求没有什么不同,因为您需要一种方法来引用文件(谁上传了,名称是什么,偶尔存储在哪里,上传时间,有时是大小)。除了文件的用户名之外,您只需添加文件的实际存储名称即可。
OWASP的建议还不错 - 使用文件名和时间戳(而不是日期)将是最唯一的。我更进一步,将微时间与时间戳以及其他一些独特的信息一起包括在内,这样小文件的重复上传就不会在同一时间范围内发生——我还存储了上传日期,这是针对 md5 冲突的额外保险,这在存储许多文件和多年的系统中具有更高的概率。您不太可能在同一天使用文件名和微时间生成两个像 md5s 一样的 md5s。例如:
$filename = date('Ymd') . '_' . md5($uploaded_filename . microtime());
我的 2 美分。
您需要重命名上传的文件是有充分理由的,它是, 如果两个文件上传相同的文件,或同名的文件,则后一个文件将替换前一个文件,这是不利的。
您可以使用散列算法,例如
$extensions = explode(".",$file-name);
$ext = $extensions[count($extensions)-1];
$file-name = md5($file-name .$_SERVER['REMOTE_ADDR']) .'.' .$ext;
然后,您可以保存文件名,哈希文件名,上传者详细信息,日期,时间的详细信息以跟踪文件
评论
Cake Recipe.doc
.php