← 返回文件上传漏洞

🛡️ 实验三:安全上传方案

多重验证策略

安全的文件上传需要多层防护,不能只依赖单一检查

🎮 安全上传演示

✅ 安全上传接口

验证步骤

扩展名白名单检查
双扩展名检测
MIME 类型验证
文件重命名
点击上传查看结果...

📋 安全检查清单

检查项方法目的
扩展名白名单验证只允许安全的扩展名
双扩展名检查所有 . 分隔部分防止 shell.php.jpg
MIME 类型白名单 + 魔数验证防止 MIME 伪造
文件大小限制最大尺寸防止 DoS
文件重命名随机生成文件名防止路径遍历
存储位置非 Web 可访问目录防止直接执行

💻 完整安全代码

const multer = require('multer')
const fileType = require('file-type')
const path = require('path')
const crypto = require('crypto')

const ALLOWED_MIMES = ['image/jpeg', 'image/png', 'image/gif']
const MAX_SIZE = 5 * 1024 * 1024 // 5MB

const storage = multer.diskStorage({
  destination: './uploads/', // 非 public 目录
  filename: (req, file, cb) => {
    // 随机文件名
    const ext = path.extname(file.originalname).toLowerCase()
    const name = crypto.randomBytes(16).toString('hex')
    cb(null, name + ext)
  }
})

const upload = multer({
  storage,
  limits: { fileSize: MAX_SIZE },
  fileFilter: async (req, file, cb) => {
    // 1. 检查扩展名
    const ext = path.extname(file.originalname).toLowerCase()
    if (!['.jpg', '.jpeg', '.png', '.gif'].includes(ext)) {
      return cb(new Error('不允许的扩展名'))
    }
    
    // 2. 检查双扩展名
    if (file.originalname.split('.').length > 2) {
      return cb(new Error('检测到双扩展名'))
    }
    
    // 3. 检查 MIME
    if (!ALLOWED_MIMES.includes(file.mimetype)) {
      return cb(new Error('不允许的 MIME 类型'))
    }
    
    cb(null, true)
  }
})

// 上传后验证文件内容
app.post('/upload', upload.single('file'), async (req, res) => {
  const buffer = fs.readFileSync(req.file.path)
  const type = await fileType.fromBuffer(buffer)
  
  if (!type || !ALLOWED_MIMES.includes(type.mime)) {
    fs.unlinkSync(req.file.path) // 删除文件
    return res.status(400).json({ error: '文件内容与类型不符' })
  }
  
  res.json({ success: true, filename: req.file.filename })
})