文件上传漏洞与防御

JSON 2026-02-28 11:21:05 1548

一、什么是文件上传漏洞

文件上传漏洞是指网站在接收用户上传文件时,后端未对文件类型、文件名、文件内容做严格安全校验,导致攻击者可上传恶意脚本(如 php、jsp、asp、aspx、py 等),并被服务器正常解析执行,最终获取服务器权限的高危漏洞。 常见场景:头像上传、附件上传、图片上传、文档上传、封面 / 素材上传等。

漏洞危害

  • 直接获取网站 WebShell,控制网站
  • 服务器被入侵,进行内网渗透
  • 删库、篡改页面、挂黑页、挖矿、勒索病毒
  • 敏感数据泄露、服务器被当作肉鸡

二、漏洞产生的根本原因

  1. 只依赖前端 JS 校验,后端完全不校验
  2. 只校验文件后缀,不校验文件头与真实内容
  3. 使用黑名单过滤,规则不完整,极易被绕过
  4. 服务器开启危险解析,上传目录可执行脚本
  5. 文件名、路径用户可控,导致路径穿越
  6. 上传目录权限过大,文件可直接被解析运行
  7. 未对文件重命名、未做内容安全检测

三、常见上传绕过方式

1. 前端绕过

前端通过 JS 限制后缀,攻击者抓包修改文件名与类型即可绕过。

2. 文件后缀绕过

  • 大小写绕过:PhP、AspX、Jsp
  • 多后缀绕过:shell.php.jpg
  • 特殊字符截断:shell.php;.jpg、shell.php%00.jpg
  • 解析漏洞绕过:shell.php/.、shell.jpg/.php

3. MIME 类型绕过

抓包修改请求头:  

Content-Type: image/jpeg

4. 文件内容绕过

  • 图片马:图片文件中插入一句话木马
  • 伪造文件头:添加 GIF89a 等图片标识伪装图片

5. 黑名单绕过

黑名单遗漏危险后缀,新版本可解析:php5、php7、phtml、phps、asa、cer、aspx 等。

四、完整安全防御方案(最稳通用方案)

1. 后端强校验(核心)

  • 必须使用白名单,严禁使用黑名单
  • 校验内容:文件后缀 + MIME + 文件头 + 文件内容
  • 禁止上传可执行脚本:php、jsp、asp、aspx、py、sh、exe 等

2. 文件重命名

  • 上传后统一使用随机字符串重命名,如 UUID、uniqid
  • 不使用用户上传的原始文件名,防止路径穿越、文件覆盖

3. 独立存储 & 关闭脚本解析

  • 上传目录与 Web 目录分离
  • 上传目录禁止解析任何脚本
  • 优先使用 OSS、对象存储、CDN 存储用户文件

4. 文件内容检测

  • 读取文件头,验证是否为真实图片 / 文档
  • 图片进行二次渲染 / 重生成,破坏嵌入的恶意代码
  • 限制文件大小、上传频率、单次上传数量

5. 服务器安全配置

  • Nginx/Apache 配置上传目录禁止解析脚本
  • 关闭不必要的解析规则与危险函数
  • Web 服务以最小权限运行
  • 配合 WAF、病毒查杀、上传日志审计

五、一句话防御口诀

前端校验不可信,后端白名单必做。文件头 + 内容双检查,随机改名存别处。上传目录禁执行,服务器配置要严格。

六、实战防御代码示例

1. PHP 安全上传(完整可运行)

<?php
function safeUploadFile() {
    $allowedExts = array('jpg', 'jpeg', 'png', 'gif');
    $allowedMime = array('image/jpeg', 'image/png', 'image/gif');
    $maxSize = 2 * 1024 * 1024;

    if ($_FILES['file']['error'] !== UPLOAD_ERR_OK) {
        return "上传失败:文件上传出错";
    }
    if ($_FILES['file']['size'] > $maxSize) {
        return "上传失败:文件超过2MB";
    }

    $fileInfo = pathinfo($_FILES['file']['name']);
    $fileExt = strtolower($fileInfo['extension']);
    $fileMime = mime_content_type($_FILES['file']['tmp_name']);

    if (!in_array($fileExt, $allowedExts) || !in_array($fileMime, $allowedMime)) {
        return "仅允许上传 jpg/png/gif 图片";
    }

    $fileHeader = file_get_contents($_FILES['file']['tmp_name'], false, null, 0, 4);
    $allowedHeaders = array(
        "\xFF\xD8\xFF\xE0",
        "\x89\x50\x4E\x47",
        "GIF89a"
    );
    if (!in_array($fileHeader, $allowedHeaders)) {
        return "文件不是合法图片";
    }

    $newFileName = uniqid('upload_', true) . '.' . $fileExt;
    $uploadPath = './uploads/';
    if (!is_dir($uploadPath)) {
        mkdir($uploadPath, 0755, true);
    }

    if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadPath . $newFileName)) {
        return "上传成功:{$uploadPath}{$newFileName}";
    } else {
        return "上传失败:文件移动出错";
    }
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
    echo safeUploadFile();
}
?>

<form method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="submit" value="上传">
</form>

  Nginx 禁止上传目录解析脚本  

location /uploads/ {
    location ~ \.php$ {
        deny all;
    }
    location ~* \.(jpg|jpeg|png|gif)$ {
        expires 30d;
    }
}

  2. Java SpringBoot 安全上传  

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.UUID;

@RestController
public class SafeUploadController {
    private static final String[] ALLOWED_EXT = {"jpg", "jpeg", "png", "gif"};
    private static final long MAX_SIZE = 2 * 1024 * 1024;
    private static final String UPLOAD_PATH = "./uploads/";

    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return "上传失败:文件为空";
        }
        if (file.getSize() > MAX_SIZE) {
            return "上传失败:文件超过2MB";
        }

        String originalFilename = file.getOriginalFilename();
        String ext = originalFilename.substring(originalFilename.lastIndexOf(".") + 1).toLowerCase();
        boolean extAllowed = false;
        for (String allowed : ALLOWED_EXT) {
            if (allowed.equals(ext)) {
                extAllowed = true;
                break;
            }
        }
        if (!extAllowed) {
            return "仅允许上传 jpg/png/gif 图片";
        }

        try {
            BufferedImage image = ImageIO.read(file.getInputStream());
            if (image == null || image.getWidth() <= 0) {
                return "文件不是合法图片";
            }
        } catch (IOException e) {
            return "图片校验失败";
        }

        String newFileName = UUID.randomUUID() + "." + ext;
        File uploadDir = new File(UPLOAD_PATH);
        if (!uploadDir.exists()) {
            uploadDir.mkdirs();
        }

        try {
            file.transferTo(new File(UPLOAD_PATH + newFileName));
            return "上传成功:" + UPLOAD_PATH + newFileName;
        } catch (IOException e) {
            return "上传失败:" + e.getMessage();
        }
    }
}

七、核心总结

  1. 文件上传防御的核心是:白名单 + 多层校验 + 随机重命名
  2. 前端校验不可信,所有校验必须在后端实现
  3. 上传目录必须禁止脚本解析,即使上传木马也无法执行
  4. 优先使用独立存储(OSS/CDN),从架构上避免解析风险

版权所属:SO JSON在线解析

原文地址:https://www.sojson.com/blog/588.html

转载时必须以链接形式注明原始出处及本声明。

本文主题:

如果本文对你有帮助,那么请你赞助我,让我更有激情的写下去,帮助更多的人。

关于作者
一个低调而闷骚的男人。
相关文章
Java JSON 组件选型之 FastJson 为什么总有漏洞
网站如何设置404页面、500等错误页面,有什么漏洞?怎么预
阿里云系统漏洞修复合集——高危漏洞提醒【云盾-安骑士】
Linux—文件
如何把自己的Jar包上传到 maven 官方仓库中,Maven上传图文讲解
Java有序读取配置文件,有序读取ini配置文件
Java 文字转图片输出,Java 输出透明背景图片,Java文字转图片爬虫
JSON文件如何转换为txt文件
Plupload 上传详细讲解,Plupload 多实例上传,Plupload多个上传按钮
Linux—文件
最新文章
文件上传漏洞与防御 1548
前端构建工具选型指南:Webpack、Vite、Rollup、esbuild 深度对比 494
物联网时代2026年时序数据库选型指南 507
SaaS行业面临AI挑战:从“无限复用”到“灵活适应” 683
神经网络:从构造到模型训练全链路解析 554
一文吃透 Redis 核心存储结构:ziplist、listpack 与哈希表扩容 / 并发查询 982
Linux sudo提权完整指南:从基础用法到生产级安全配置 281
XSS 和 CSRF 的本质区别及开发防御全解析 390
JVM垃圾回收(GC)全维度解析:从原理到调优实战 420
Linux动静态库与ELF加载全解析:从实操制作到底层原理 539
最热文章
免费天气API,天气JSON API,不限次数获取十五天的天气预报 771514
最新MyEclipse8.5注册码,有效期到2020年 (已经更新) 708851
苹果电脑Mac怎么恢复出厂系统?苹果系统怎么重装系统? 679457
Jackson 时间格式化,时间注解 @JsonFormat 用法、时差问题说明 562378
我为什么要选择RabbitMQ ,RabbitMQ简介,各种MQ选型对比 512346
Elasticsearch教程(四) elasticsearch head 插件安装和使用 484468
Jackson 美化输出JSON,优雅的输出JSON数据,格式化输出JSON数据... ... 301586
Java 信任所有SSL证书,HTTPS请求抛错,忽略证书请求完美解决 247158
Elasticsearch教程(一),全程直播(小白级别) 232831
谈谈斐讯路由器劫持,你用斐讯路由器,你需要知道的事情 228099
支付扫码

所有赞助/开支都讲公开明细,用于网站维护:赞助名单查看

查看我的收藏

正在加载... ...