bool is_uploaded_file ( string $filename )
| 参数 | 描述 |
|---|---|
| filename | 必需。规定要检查的文件临时路径,通常是 $_FILES['file']['tmp_name'] 的值。 |
如果文件是通过 HTTP POST 上传的,返回 TRUE,否则返回 FALSE。
move_uploaded_file() 移动文件。
基本的文件上传验证:
<?php
// 检查是否有文件上传
if (isset($_FILES['userfile']) && $_FILES['userfile']['error'] === UPLOAD_ERR_OK) {
$tmp_name = $_FILES['userfile']['tmp_name'];
$original_name = $_FILES['userfile']['name'];
echo "上传的文件名: $original_name<br>";
echo "临时文件路径: $tmp_name<br><br>";
// 验证是否是有效的上传文件
if (is_uploaded_file($tmp_name)) {
echo "✓ 文件是通过HTTP POST上传的,是有效的上传文件。<br>";
// 现在可以安全地移动文件
$target_path = "uploads/" . basename($original_name);
if (move_uploaded_file($tmp_name, $target_path)) {
echo "✓ 文件已成功移动到: $target_path";
} else {
echo "✗ 移动文件失败。";
}
} else {
echo "✗ 文件不是通过HTTP POST上传的,可能存在安全问题!";
}
} else {
echo "没有文件上传或上传过程中出现错误。";
}
?>
<form action="upload.php" method="post" enctype="multipart/form-data">
<div class="mb-3">
<label for="userfile" class="form-label">选择文件:</label>
<input type="file" class="form-control" id="userfile" name="userfile">
</div>
<button type="submit" class="btn btn-primary">上传文件</button>
</form>
验证多个上传文件:
<?php
if (isset($_FILES['files'])) {
$files = $_FILES['files'];
$upload_results = [];
echo "文件上传处理报告:<br><br>";
// 遍历所有上传的文件
for ($i = 0; $i < count($files['name']); $i++) {
$filename = $files['name'][$i];
$tmp_name = $files['tmp_name'][$i];
$error = $files['error'][$i];
echo "<div class='mb-3 p-2 border rounded'>";
echo "<strong>文件: $filename</strong><br>";
if ($error === UPLOAD_ERR_OK) {
// 验证是否是有效的上传文件
if (is_uploaded_file($tmp_name)) {
echo "✓ 验证通过(有效的上传文件)<br>";
// 安全检查:文件类型和大小
$allowed_types = ['image/jpeg', 'image/png', 'application/pdf'];
$max_size = 2 * 1024 * 1024; // 2MB
$file_size = $files['size'][$i];
$file_type = $files['type'][$i];
// 检查文件大小
if ($file_size > $max_size) {
echo "✗ 文件过大 ($file_size 字节,最大允许 $max_size 字节)<br>";
continue;
}
// 检查文件类型
if (!in_array($file_type, $allowed_types)) {
echo "✗ 不支持的文件类型: $file_type<br>";
continue;
}
// 生成安全文件名
$safe_filename = time() . "_" . preg_replace("/[^a-zA-Z0-9\._-]/", "", $filename);
$target_path = "uploads/" . $safe_filename;
// 移动文件
if (move_uploaded_file($tmp_name, $target_path)) {
echo "✓ 文件已保存为: $safe_filename<br>";
$upload_results[] = [
'original' => $filename,
'saved' => $safe_filename,
'path' => $target_path
];
} else {
echo "✗ 保存文件失败<br>";
}
} else {
echo "✗ 安全验证失败:不是有效的上传文件!<br>";
}
} else {
echo "✗ 上传错误代码: $error<br>";
switch ($error) {
case UPLOAD_ERR_INI_SIZE:
echo "文件大小超过服务器限制";
break;
case UPLOAD_ERR_FORM_SIZE:
echo "文件大小超过表单限制";
break;
case UPLOAD_ERR_PARTIAL:
echo "文件只有部分被上传";
break;
case UPLOAD_ERR_NO_FILE:
echo "没有文件被上传";
break;
case UPLOAD_ERR_NO_TMP_DIR:
echo "找不到临时文件夹";
break;
case UPLOAD_ERR_CANT_WRITE:
echo "写入磁盘失败";
break;
case UPLOAD_ERR_EXTENSION:
echo "PHP扩展停止了文件上传";
break;
}
echo "<br>";
}
echo "</div>";
}
// 显示上传结果摘要
if (!empty($upload_results)) {
echo "<h5>上传成功摘要:</h5>";
echo "<ul>";
foreach ($upload_results as $result) {
echo "<li>{$result['original']} → {$result['saved']}</li>";
}
echo "</ul>";
}
}
?>
<form action="multi_upload.php" method="post" enctype="multipart/form-data">
<div class="mb-3">
<label for="files" class="form-label">选择多个文件:</label>
<input type="file" class="form-control" id="files" name="files[]" multiple>
<div class="form-text">支持的文件类型: JPEG, PNG, PDF (最大2MB)</div>
</div>
<button type="submit" class="btn btn-primary">上传多个文件</button>
</form>
创建一个安全的文件上传处理类:
<?php
class SecureFileUploader {
private $allowed_types = [];
private $max_size = 2097152; // 2MB
private $upload_dir = 'uploads/';
private $errors = [];
public function __construct($upload_dir = 'uploads/') {
$this->upload_dir = rtrim($upload_dir, '/') . '/';
// 确保上传目录存在
if (!is_dir($this->upload_dir)) {
mkdir($this->upload_dir, 0755, true);
}
}
public function setAllowedTypes($types) {
$this->allowed_types = $types;
}
public function setMaxSize($size_in_bytes) {
$this->max_size = $size_in_bytes;
}
public function upload($file_input_name) {
if (!isset($_FILES[$file_input_name])) {
$this->errors[] = "没有文件上传";
return false;
}
$file = $_FILES[$file_input_name];
// 检查上传错误
if ($file['error'] !== UPLOAD_ERR_OK) {
$this->errors[] = "上传错误代码: " . $file['error'];
return false;
}
$tmp_path = $file['tmp_name'];
$original_name = $file['name'];
$file_size = $file['size'];
$file_type = $file['type'];
// 关键验证:检查是否是真正的上传文件
if (!is_uploaded_file($tmp_path)) {
$this->errors[] = "安全验证失败:不是有效的上传文件";
return false;
}
// 验证文件大小
if ($file_size > $this->max_size) {
$this->errors[] = "文件过大 ({$file_size} 字节,最大允许 {$this->max_size} 字节)";
return false;
}
// 验证文件类型
if (!empty($this->allowed_types) && !in_array($file_type, $this->allowed_types)) {
$this->errors[] = "不支持的文件类型: {$file_type}";
return false;
}
// 生成安全文件名
$extension = pathinfo($original_name, PATHINFO_EXTENSION);
$safe_name = uniqid('upload_', true) . '.' . strtolower($extension);
$target_path = $this->upload_dir . $safe_name;
// 移动文件
if (!move_uploaded_file($tmp_path, $target_path)) {
$this->errors[] = "移动文件失败";
return false;
}
// 返回上传文件信息
return [
'original_name' => $original_name,
'saved_name' => $safe_name,
'file_path' => $target_path,
'file_size' => $file_size,
'file_type' => $file_type,
'upload_time' => time()
];
}
public function getErrors() {
return $this->errors;
}
}
// 使用示例
$uploader = new SecureFileUploader('uploads/');
$uploader->setAllowedTypes(['image/jpeg', 'image/png', 'application/pdf']);
$uploader->setMaxSize(5 * 1024 * 1024); // 5MB
$result = $uploader->upload('userfile');
if ($result) {
echo "文件上传成功!<br><br>";
echo "原始文件名: " . $result['original_name'] . "<br>";
echo "保存文件名: " . $result['saved_name'] . "<br>";
echo "文件大小: " . $result['file_size'] . " 字节<br>";
echo "文件类型: " . $result['file_type'] . "<br>";
echo "保存路径: " . $result['file_path'] . "<br>";
} else {
echo "文件上传失败!<br><br>";
echo "错误信息:<br>";
foreach ($uploader->getErrors() as $error) {
echo "- $error<br>";
}
}
?>
展示不使用 is_uploaded_file() 的安全风险:
<?php
// ❌ 危险:不使用 is_uploaded_file() 验证
function unsafe_file_upload($tmp_path, $target_path) {
// 攻击者可以传递任何文件路径,例如 /etc/passwd
if (file_exists($tmp_path)) {
// 直接复制文件 - 这是不安全的!
copy($tmp_path, $target_path);
return true;
}
return false;
}
// ✅ 安全:使用 is_uploaded_file() 验证
function safe_file_upload($tmp_path, $target_path) {
// 验证是否是真正的上传文件
if (is_uploaded_file($tmp_path)) {
// 使用 move_uploaded_file() 移动文件
return move_uploaded_file($tmp_path, $target_path);
}
return false;
}
// 模拟攻击场景
echo "<h5>攻击场景模拟:</h5>";
echo "假设攻击者试图上传 /etc/passwd 文件<br><br>";
$malicious_tmp_path = "/etc/passwd";
$target = "uploads/passwd_copy.txt";
echo "攻击者提供的路径: $malicious_tmp_path<br>";
// 不安全的方式
echo "<br><strong>不安全的方式(不使用is_uploaded_file()):</strong><br>";
if (unsafe_file_upload($malicious_tmp_path, $target)) {
echo "✗ 危险!系统文件被复制到: $target<br>";
echo "攻击者成功访问了敏感文件!";
} else {
echo "✓ 文件复制失败(可能是权限问题)";
}
// 安全的方式
echo "<br><br><strong>安全的方式(使用is_uploaded_file()):</strong><br>";
if (safe_file_upload($malicious_tmp_path, $target)) {
echo "✗ 文件移动成功";
} else {
echo "✓ 安全!is_uploaded_file() 检测到这不是有效的上传文件<br>";
echo "攻击被阻止!";
}
?>
copy()、rename() 或 file_put_contents() 来处理上传文件。必须使用 is_uploaded_file() 验证后,再用 move_uploaded_file() 移动文件。
is_uploaded_file() 是防止文件上传攻击的关键。它确保文件来自HTTP POST请求,而不是来自其他路径。
move_uploaded_file() 移动文件,因为它包含额外的安全检查。
upload_tmp_dir 指定)。脚本结束后,这些文件会被自动删除。
upload_max_filesize、post_max_size、max_execution_time)允许。
将上传的文件移动到新位置
包含所有上传文件的信息
文件上传错误代码
返回文件路径信息
返回路径中的文件名部分
获取图像尺寸和类型信息
is_uploaded_file() 验证文件move_uploaded_file() 移动文件copy() 或 rename() 处理上传文件