如何在项目中实现安全的图片上传功能(防止伪造图片与木马上传)

如何在项目中实现安全的图片上传功能(防止伪造图片与木马上传)

精选文章moguli202025-05-21 16:28:433A+A-

如何在项目中实现安全的图片上传功能(防止伪造图片与木马上传)

在 Web 项目中,文件上传是一项基础功能,但也是最容易被攻击者利用的入口,尤其是上传“伪装图片”(如 .php.jpg、带木马的图像),如果处理不当,可能导致严重安全问题。

本文将以 Java + Spring Boot 为例,介绍如何构建一个 安全可靠的图片上传接口,有效防止如下攻击:

  • 上传 .php.jsp 等脚本文件伪装成图片
  • 上传带恶意代码的伪装图片(木马)
  • 上传超大文件压垮服务器
  • 路径穿越、文件名注入攻击



安全上传设计目标

安全项

说明

类型校验

限制只上传图片(JPG、PNG、GIF、WEBP)

MIME 校验

校验真实 Content-Type

内容解析校验

通过 ImageIO.read 确保是真图片

文件大小限制

最大限制 5MB

文件名重命名

使用 UUID 防止执行漏洞

路径受控

上传路径不可被用户控制

拒绝危险后缀

.php.jsp.exe.bat


后端代码(Spring Boot)

控制器代码

@RestController
@RequestMapping("/api/upload")
public class FileUploadController {

    private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
    private static final List<String> ALLOWED_EXTENSIONS = List.of("jpg", "jpeg", "png", "gif", "webp");
    private final String uploadDir = "/opt/uploads/avatars/";

    @PostMapping("/avatar")
    public ResponseEntity<?> uploadAvatar(@RequestParam("file") MultipartFile file) {
        String filename = file.getOriginalFilename();
        String ext = FilenameUtils.getExtension(filename).toLowerCase();

        // 1. 扩展名白名单
        if (!ALLOWED_EXTENSIONS.contains(ext)) {
            return ResponseEntity.badRequest().body("仅支持图片类型文件上传");
        }

        // 2. 检查 MIME 类型
        String contentType = file.getContentType();
        if (contentType == null || !contentType.startsWith("image/")) {
            return ResponseEntity.badRequest().body("上传的文件不是图片");
        }

        // 3. 内容校验
        try {
            BufferedImage img = ImageIO.read(file.getInputStream());
            if (img == null) {
                return ResponseEntity.badRequest().body("图片内容无效或已损坏");
            }
        } catch (IOException e) {
            return ResponseEntity.badRequest().body("读取图片失败");
        }

        // 4. 文件大小限制
        if (file.getSize() > MAX_FILE_SIZE) {
            return ResponseEntity.badRequest().body("图片大小不能超过 5MB");
        }

        // 5. 保存文件
        String newName = UUID.randomUUID().toString() + "." + ext;
        try {
            Files.copy(file.getInputStream(), Paths.get(uploadDir + newName), StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            return ResponseEntity.internalServerError().body("保存文件失败");
        }

        return ResponseEntity.ok(Map.of("filename", newName));
    }
}


前端上传组件(React + Ant Design)

import { Upload, message } from 'antd';
import type { UploadProps } from 'antd';
import { InboxOutlined } from '@ant-design/icons';

const props: UploadProps = {
  name: 'file',
  action: '/api/upload/avatar',
  maxCount: 1,
  accept: 'image/*',
  beforeUpload(file) {
    const isImage = file.type.startsWith('image/');
    const isLt5M = file.size / 1024 / 1024 < 5;
    if (!isImage) {
      message.error('只允许上传图片');
      return Upload.LIST_IGNORE;
    }
    if (!isLt5M) {
      message.error('图片大小不能超过5MB');
      return Upload.LIST_IGNORE;
    }
    return true;
  },
  onChange(info) {
    if (info.file.status === 'done') {
      message.success('上传成功');
    } else if (info.file.status === 'error') {
      message.error('上传失败');
    }
  }
};

const AvatarUpload = () => (
  <Upload.Dragger {...props}>
    <p className="ant-upload-drag-icon"><InboxOutlined /></p>
    <p className="ant-upload-text">点击或拖拽上传头像</p>
  </Upload.Dragger>
);

附加安全建议(可选增强)

功能

建议

文件病毒扫描

接入阿里云、腾讯云 COS 安全检测服务

上传后审核机制

上传后不立即展示,管理员审核后启用

图片压缩处理

使用 Thumbnailator 或 TinyPNG API

CDN 静态托管

将图片上传至 OSS/CDN 做前后端隔离

黑名单机制

自动拒绝 .php, .jsp, .exe, .bat 等


总结

构建安全的上传接口,关键在于 多层防护,不能仅靠前端限制:

  • 后端必须验证类型、大小、内容、MIME、路径
  • 严禁直接保存用户提供的原始文件名
  • 推荐结合 CDN、云存储、权限系统实现全链路安全
点击这里复制本文地址 以上内容由莫古技术网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

莫古技术网 © All Rights Reserved.  滇ICP备2024046894号-2