请注意,在调用 move_uploaded_file() 之前调用此函数不是必需的,因为它已经执行了完全相同的检查。它没有提供额外的安全性。仅当您尝试将上传的文件用于移动到新位置以外的其他用途时。
参考
https://github.com/php/php-src/blob/master/ext/standard/basic_functions.c#L5796
(PHP 4 >= 4.0.3, PHP 5, PHP 7, PHP 8)
is_uploaded_file — 判断文件是否通过 HTTP POST 上传
如果名为 filename
的文件是通过 HTTP POST 上传的,则返回 true
。这有助于确保恶意用户没有试图欺骗脚本处理其不应该处理的文件——例如,/etc/passwd。
如果对上传的文件执行的任何操作都可能将文件内容泄露给用户,甚至泄露给同一系统上的其他用户,则此类检查尤其重要。
为了正常工作,函数 is_uploaded_file() 需要一个类似于 $_FILES['userfile']['tmp_name'] 的参数,- 客户端机器上上传的文件名 $_FILES['userfile']['name'] 不起作用。
filename
要检查的文件名。
示例 #1 is_uploaded_file() 示例
<?php
if (is_uploaded_file($_FILES['userfile']['tmp_name'])) {
echo "文件 ". $_FILES['userfile']['name'] ." 上传成功。\n";
echo "显示内容\n";
readfile($_FILES['userfile']['tmp_name']);
} else {
echo "可能的文件上传攻击: ";
echo "文件名 '". $_FILES['userfile']['tmp_name'] . "'.";
}
?>
请注意,在调用 move_uploaded_file() 之前调用此函数不是必需的,因为它已经执行了完全相同的检查。它没有提供额外的安全性。仅当您尝试将上传的文件用于移动到新位置以外的其他用途时。
参考
https://github.com/php/php-src/blob/master/ext/standard/basic_functions.c#L5796
扩展 nicoSWD 关于此函数的说明。
任何使用临时文件 $_FILES[]['tmp_name'] 的脚本都应该调用此函数。
在任何脚本被修改为 unlink()、rename() 或以其他方式修改文件且不使用 move_uploaded_file() 的情况下,都不会检查上传。
同样,PHP 中的大多数文件操作都缓存,因此在 move_uploaded_file 之前运行 is_uploaded_file 的性能影响最小,因为它通常会为后者使用缓存的结果。
在任何情况下,安全优势都超过了性能上的微秒差异,并且应该在 $_FILES 数组第一次进入应用程序时普遍使用。虽然可能没有立即出现问题,但代码会不断发展,并且可能会很快改变这一事实。
从 PHP 4.2.0 开始,您可以使用与文件上传相关的错误代码来检查并查看上传失败的原因,而不是自动假设文件上传失败是文件攻击。此错误代码存储在 userfile 数组中(例如:$HTTP_POST_FILES['userfile']['error'])。
这是一个 switch 示例
if (is_uploaded_file($userfile)) {
//在此处包含将临时文件复制到最终位置的代码...
}else{
switch($HTTP_POST_FILES['userfile']['error']){
case 0: //无错误;可能的文件攻击!
echo "您的上传出现问题。";
break;
case 1: //上传的文件超过了 php.ini 中的 upload_max_filesize 指令
echo "您尝试上传的文件太大。";
break;
case 2: //上传的文件超过了 HTML 表单中指定的 MAX_FILE_SIZE 指令
echo "您尝试上传的文件太大。";
break;
case 3: //上传的文件仅部分上传
echo "您尝试上传的文件仅部分上传。";
break;
case 4: //没有上传文件
echo "您必须选择要上传的图片。";
break;
default: //默认错误,以防万一! :)
echo "您的上传出现问题。";
break;
}
此外,通过测试文件上传数组的“name”元素,您可以过滤掉不需要的文件类型(.exe、.zip、.bat 等)。以下是一个可以在测试文件是否上传之前添加的过滤器示例
//拒绝所有 .exe、.com、.bat、.zip、.doc 和 .txt 文件
if(preg_match("/.exe$|.com$|.bat$|.zip$|.doc$|.txt$/i", $HTTP_POST_FILES['userfile']['name'])){
exit("您无法上传此类文件。");
}
//如果文件未被过滤器拒绝,则继续正常
if (is_uploaded_file($userfile)) {
/*其余代码*/
我还没有在任何地方看到过,但 $filename 在 Windows 上*区分大小写*。
这意味着虽然可能已上传了 C:\Windows\TEMP\php123.tmp,但 C:\Windows\Temp\php123.tmp 则没有。
我发现了这一点,因为我正在对文件名使用 realpath(),它“修复”了大小写(我的 Temp 文件夹使用的是标题大小写,而不是大写 - 感谢 Vista)。
无论如何,问题在于 PHP 使用 %TEMP% 来确定上传文件的目标位置,而 %TEMP% 使用路径的全大写版本。将其更改为使用标题大小写并重新启动 Apache 解决了问题。
再次查看了我发布的内容,发现了一些重大和轻微的错误。这就是我在完成咖啡之前就发布内容的后果。这应该可以更好地工作(即应该从一开始就能工作)
<?php
default: //默认错误,以防万一!:)
echo "文件上传出现问题。";
$err_msg = "无法识别的文件 POST 错误: ".$HTTP_POST_FILES['userfile']['error'];
if (!(strpos($err_msg, "\n") === false)) {
$err_lines = explode("\n", $err_msg);
foreach ($err_lines as $msg) {
error_log($msg, 0);
}
} else {
error_log($err_msg, 0);
}
break;
?>
这是我的文件处理代码,希望对大家有所帮助。
首先是一个处理文件上传的类。
<?php
define('UPLOAD_PATH', 'upload/');
define('MAXIMUM_FILESIZE', '10485760'); //10 MB
class FileHandler
{
private $file_types = array('xls', 'xlsx');
private $files = null;
private $filename_sanitized = null;
private $filename_original = null;
public function __construct($files)
{
$this->files = $files;
}
public function setFileTypes($fileTypes = array())
{
$this->file_types = $fileTypes;
return $this;
}
public function setFileNameOriginal($filename)
{
$this->filename_original = $filename;
}
public function fileNameOriginal()
{
return $this->filename_original;
}
public function sanitize($cursor = 0)
{
$this->setFileNameOriginal($this->files['name'][$cursor]);
$safe_filename = preg_replace(
array("/\s+/", "/[^-\.\w]+/"),
array("_", ""),
trim($this->fileNameOriginal()));
$this->filename_sanitized = md5($safe_filename.time()).$safe_filename;
return $this;
}
public function fileSize($cursor = 0)
{
return $this->files['size'][$cursor];
}
public function extensionValid()
{
$fileTypes = implode('|', $this->file_types);
$rEFileTypes = "/^\.($fileTypes){1}$/i";
if(!preg_match($rEFileTypes, strrchr($this->filename_sanitized, '.')))
throw new Exception('未找到合适的的文件类型');
return $this;
}
public function isUploadedFile($cursor)
{
if(!is_uploaded_file($this->files['tmp_name'][$cursor]))
{
throw new Exception("未获得文件上传");
}
}
public function saveUploadedFile($cursor)
{
if(!move_uploaded_file ($this->files['tmp_name'][$cursor],UPLOAD_PATH.$this->filename_sanitized))
throw new Exception("未能保存文件");
}
public function fileNameSanitized()
{
return $this->filename_sanitized;
}
public function uploadFile($cursor = 0)
{
$this->isUploadedFile($cursor);
if ($this->sanitize($cursor)->fileSize($cursor) <= MAXIMUM_FILESIZE)
{
$this->extensionValid()->saveUploadedFile($cursor);
}
else
{
throw new Exception("文件太大。");
}
return $name;
}
}
?>
接下来是使用该类的一部分代码。
<?php
//表单已提交并检测到
if ($form_submited == 1)
{
try
{
//我假设文件输入为:
/*
<input id="<?php echo EXCEL_FILE;?>[]" name="<?php echo EXCEL_FILE;?>[]" type="file"/>
其中 EXCEL_FILE 是常量:
define('EXCEL_FILE', 'excel_file');
*/
$file = new FileHandler($_FILES['excel_file']);
$inputFileName = $file->uploadFile()->fileNameSanitized(); //要读取的文件
...
}
catch(Exception $e)
{
die('加载文件 "'.($file->fileNameOriginal()).'": '.$e->getMessage());
}
}
?>
提供的示例无法按预期工作。
function is_uploaded_file($filename) {
if (!$tmp_file = get_cfg_var('upload_tmp_dir')) {
$tmp_file = dirname(tempnam('', ''));
}
$tmp_file .= '/' . basename($filename);
/* 用户可能在 php.ini 中添加了尾部斜杠... */
return (ereg_replace('/+', '/', $tmp_file) == $filename);
}
它仅适用于小于 4 或 5 kb 的文件,其他文件的大小自动变为 0 字节。所以这里肯定有问题。内置的 is_uploaded_file() 工作良好。
如果您的 $_FILES 和 $_POST 为空,则可能是由于
- php.ini 中设置的 post_max_size 限制
- php.ini 中设置的 upload_max_filesize 限制
不幸的是,第一个限制不会作为错误代码在 $_FILES['error'] 中报告。
要在 Windows 上运行此示例,您需要添加一行代码,将反斜杠替换为斜杠。例如:$filename = str_replace ("\\", "/", $filename);
另外,正如有人提到的,将 $HTTP_POST_FILES 全局化是一个好主意……
<pre>
/* 上传文件的用户空间测试。 */
function is_uploaded_file($filename)
{
global $HTTP_POST_FILES;
if (!$tmp_file = get_cfg_var("upload_tmp_dir")) {
$tmp_file = dirname(tempnam("", ""));
}
$tmp_file .= "/" . basename($filename);
/* 用户可能在 php.ini 中添加了尾部斜杠... */
// Windows 平台修复
$filename = str_replace ("\\", "/", $filename);
return (ereg_replace("/+", "/", $tmp_file) == $filename);
}
</pre>
如果文件没有上传并且 $_FILE 数组为空,并且您的代码看起来没有问题,那么请检查 php.ini 文件,file_uploads 选项应设置为 'On' 以允许文件上传。将其打开并重启 Apache 以使更改生效。
关于 info at metaltoad dot net 的评论
@ 2003年2月19日 04:03
<?php
// ... 等等等等...
preg_match("/.exe$|.com$|.bat$|.zip$|.doc$|.txt$/i", $HTTP_POST_FILES['userfile']['name']))
// ... 等等等等...
?>
这行不通。它会运行,但运行不正确。
您应该为 preg 函数转义 .(点),
并为 PHP 转义 $(美元)符号,或者使用
单引号字符串……
语法应该是(更短更简洁)
<?php
// ... 等等等等...
preg_match('/\\.(exe|com|bat|zip|doc|txt)$/i', $_FILES['userfile']['name']))
// ... 等等等等...
?>
关于 topcat 建议的更改,我对此持保留意见。我不喜欢向用户显示可能提供超出他们应该获得的信息的错误(或表明我没有为该特定错误提供解决方案)。但我想知道何时出现落入默认情况的错误,以便我可以修复我的代码。我通常会将它们写入错误日志,例如对 metaltoad 的帖子的以下修改(考虑了 error_log 处理不佳的多行错误的可能性)
<?php
default: // 一个默认错误,以防万一!:)
echo "上传时出现问题。";
$err_msg = "无法识别的文件 POST 错误:".$HTTP_POST_FILES['userfile']['error'];
if ((strpos($err_msg, "\n") === 0) {
$err_lines = explode("\n", $err_msg);
foreach ($err_lines as $msg) {
error_log($msg, 0);
}
} else {
error_log($err_msg, 0)
}
break;
?>