You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP网站环境中出现含漏洞的临时文件上传代码,求修补方案

修补无验证文件上传漏洞的有效方案

哥们,你发现的这段代码绝对是个高危安全漏洞——完全没有任何安全限制的文件上传功能,随便哪个攻击者都能上传恶意PHP脚本,直接在你的服务器上执行恶意操作,风险拉满!下面是一套从根源解决问题的完整修补方案,每一步都能堵住不同的安全口子:


1. 严格限制允许上传的文件类型

首先得把上传的文件类型管死,不能让用户传任意文件。我们要同时验证文件扩展名和MIME类型(注意MIME类型可以被篡改,所以扩展名验证是基础,MIME是双重保障)。

示例代码:

// 定义你允许上传的文件扩展名(比如仅图片和PDF)
$allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf'];
// 获取上传文件的扩展名,转成小写避免大小写绕过(比如.PHP和.php)
$fileExtension = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));

// 检查扩展名是否在允许列表里
if (!in_array($fileExtension, $allowedExtensions)) {
    die("错误:仅允许上传jpg、jpeg、png、PDF类型的文件");
}

// 可选但推荐:验证MIME类型
$allowedMimeTypes = ['image/jpeg', 'image/png', 'application/pdf'];
$fileMimeType = mime_content_type($_FILES['file']['tmp_name']);
if (!in_array($fileMimeType, $allowedMimeTypes)) {
    die("错误:文件类型不符合要求,请检查后重试");
}

2. 重命名上传文件,杜绝路径遍历和覆盖风险

原代码直接用用户提供的文件名,这会导致两个大问题:一是攻击者可以直接传shell.php这种恶意文件;二是如果有同名文件会被覆盖,甚至可以通过../../shell.php这种路径遍历写法,把文件写到服务器的敏感目录(比如网站根目录)。

解决办法是生成唯一的随机文件名,比如用时间戳+随机字符串:

// 生成唯一文件名,避免冲突和恶意命名
$newFileName = uniqid('upload_', true) . '.' . $fileExtension;
// 重点:指定一个安全的上传目录——这个目录最好不在Web可访问的根目录下!
// 比如不要放在public_html、www这类目录,防止上传的恶意脚本被浏览器访问执行
$uploadDir = '/var/www/secure_uploads/';
$destination = $uploadDir . $newFileName;

// 执行文件拷贝
if (copy($_FILES['file']['tmp_name'], $destination)) {
    echo "文件上传成功!文件名:" . $newFileName;
} else {
    echo "文件上传失败,请稍后重试";
}

3. 限制上传文件大小,防止DoS攻击

攻击者可能上传超大文件占满你的服务器磁盘,甚至引发拒绝服务攻击,所以必须加大小限制:

// 限制文件最大为2MB(单位是字节,2*1024*1024=2097152)
$maxFileSize = 2 * 1024 * 1024;
if ($_FILES['file']['size'] > $maxFileSize) {
    die("错误:文件大小不能超过2MB,请压缩后上传");
}

4. 配置上传目录的安全权限

  • 优先选择:把上传目录放在Web根目录之外(比如/var/www/secure_uploads/而不是/var/www/html/uploads/),这样即使攻击者上传了恶意脚本,也无法通过浏览器访问执行。
  • 如果必须放在Web目录下:要配置服务器禁止该目录执行PHP脚本:
    • Apache:在上传目录创建.htaccess文件,添加php_flag engine off
    • Nginx:在配置文件中添加针对上传目录的规则:location /uploads/ { deny all; } 或者 location ~ \.php$ { deny all; }

5. 添加CSRF防护,防止跨站上传

原表单没有任何CSRF验证,攻击者可以构造跨站请求,强制用户的浏览器上传恶意文件。我们可以生成一个CSRF令牌,存在会话中,提交时验证:

// 先启动会话
session_start();
// 生成随机的CSRF令牌
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// 在表单里添加隐藏的CSRF令牌字段
echo '<input type="hidden" name="csrf_token" value="' . $_SESSION['csrf_token'] . '">';

// 处理上传请求时验证令牌
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
    die("错误:无效的请求,请刷新页面后重试");
}

完整的安全版本代码

把上面的所有步骤整合起来,最终的安全上传功能代码如下:

<title>安全文件上传</title>
<?php
session_start();
// 生成CSRF令牌
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// 处理上传请求
if ($_POST['_upl'] == "Upload") {
    // 验证CSRF令牌
    if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
        die("错误:无效的请求,请刷新页面后重试");
    }

    // 1. 检查文件大小
    $maxFileSize = 2 * 1024 * 1024;
    if ($_FILES['file']['size'] > $maxFileSize) {
        die("错误:文件大小不能超过2MB,请压缩后上传");
    }

    // 2. 验证文件类型
    $allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf'];
    $fileExtension = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
    if (!in_array($fileExtension, $allowedExtensions)) {
        die("错误:仅允许上传jpg、jpeg、png、PDF类型的文件");
    }

    $allowedMimeTypes = ['image/jpeg', 'image/png', 'application/pdf'];
    $fileMimeType = mime_content_type($_FILES['file']['tmp_name']);
    if (!in_array($fileMimeType, $allowedMimeTypes)) {
        die("错误:文件类型不符合要求,请检查后重试");
    }

    // 3. 生成唯一文件名并指定安全目录
    $newFileName = uniqid('upload_', true) . '.' . $fileExtension;
    // 替换为你服务器上的安全目录,确保不在Web根目录下
    $uploadDir = '/var/www/secure_uploads/';
    // 确保目录存在,不存在则创建(权限设为0755,只有所有者可写)
    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }
    $destination = $uploadDir . $newFileName;

    // 4. 执行文件拷贝
    if (@copy($_FILES['file']['tmp_name'], $destination)) {
        echo "文件上传成功!文件名:" . $newFileName;
    } else {
        echo "文件上传失败,请稍后重试";
    }
}
?>

<form action="" method="post" enctype="multipart/form-data" name="uploader" id="uploader">
    <input type="file" name="file" size="50">
    <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
    <input name="_upl" type="submit" id="_upl" value="Upload">
</form>

内容的提问来源于stack exchange,提问作者Santosh Mishra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:38:35