文件类型如何严格校验

wen 网络安全 27

本文目录导读:

文件类型如何严格校验

  1. 前端校验(用户体验辅助,不可信
  2. 后端校验(核心防线,必须做
  3. 防御绕过技巧(高级校验)
  4. 完整的后端校验流程(建议顺序)
  5. 常见陷阱与解决方案
  6. 推荐工具库

严格校验文件类型是Web开发中重要的安全措施,可以防止恶意文件上传(如绕过前端限制直接发送POST请求),以下是几种严格校验文件类型的方法,建议多层结合使用

前端校验(用户体验辅助,不可信

  • MIME类型检查:通过 File.type 属性
    // 只允许图片
    const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
    if (!allowedTypes.includes(file.type)) {
      alert('不支持的文件类型');
    }
  • 文件头魔数检查:读取文件前几个字节(通过 FileReader
    const reader = new FileReader();
    reader.readAsArrayBuffer(file.slice(0, 8));
    reader.onload = (e) => {
      const bytes = new Uint8Array(e.target.result);
      // 检查 PNG: 89 50 4E 47
      if (bytes[0] === 0x89 && bytes[1] === 0x50) { /* 是 PNG */ }
    };

后端校验(核心防线,必须做

文件签名/魔数(Magic Bytes)校验(最准确)

几乎所有文件格式都有固定头部字节,这是最无法伪造的校验方式。

常见文件签名示例: | 文件类型 | 文件头(Hex) | 文件头(ASCII) | |---------|--------------|-----------------| | JPEG | FF D8 FF E0 | ÿØÿà | | PNG | 89 50 4E 47 | �PNG | | GIF | 47 49 46 38 | GIF8 | | PDF | 25 50 44 46 | %PDF | | ZIP | 50 4B 03 04 | PK | | DOCX | 50 4B 03 04 (同ZIP,需进一步解析) | PK |

Python示例:

import magic  # python-magic 或 file-magic
# 或手动读取前4字节
with open('uploaded_file', 'rb') as f:
    header = f.read(8)
# 手动对比
if header.startswith(b'\xFF\xD8\xFF'):
    print("JPEG")
elif header[0:4] == b'\x89PNG':
    print("PNG")

使用专门的库检测MIME类型(推荐)

当文件签名检测不明确时,可结合MIME检测:

PHP:

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $uploaded_file);
$allowed = ['image/jpeg', 'image/png'];
if (!in_array($mime, $allowed)) {
    // 拒绝
}
finfo_close($finfo);

Python (python-magic):

import magic
mime = magic.from_file('/path/to/file', mime=True)

白名单扩展名 + MIME双重校验

先检查扩展名,再验证MIME,但注意MIME可能被伪造

Node.js (Express):

const ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif'];
const allowedMimes = ['image/jpeg', 'image/png', 'image/gif'];
function validateFile(file) {
    const ext = path.extname(file.originalname).toLowerCase().replace('.', '');
    if (!ALLOWED_EXTENSIONS.includes(ext)) return false;
    if (!allowedMimes.includes(file.mimetype)) return false;
    // 还要结合文件签名检测
    return true;
}

防御绕过技巧(高级校验)

技巧1:复合文档检查

对于Office文件(docx/xlsx等)实际是ZIP压缩包,需解压并检查内部XML结构:

import zipfile
with zipfile.ZipFile('file.docx') as z:
    if 'word/document.xml' in z.namelist():
        print("真实docx文件")

技巧2:文件完整性转换

接收文件后,重新编码/压缩/转换为安全格式,例如上传图片后,用库重新生成:

# Python Pillow
from PIL import Image
img = Image.open('uploaded.png')
img.save('safe_output.png')  # 重新编码,剥离所有元数据和恶意载荷

技巧3:内容安全检查

  • 检查是否包含脚本标签:<script>
  • 对SVG文件严格过滤JS事件(如 onload
  • 图片文件检查是否包含多余数据(exif中的恶意内容)

完整的后端校验流程(建议顺序)

检查文件大小上限
2. 白名单检查文件扩展名
3. 白名单检查MIME type(使用库检测,非用户提供)
4. 【核心】读取文件头魔数,验证文件签名
5. 可选:对文件进行重新编码/压缩
6. 可选:杀毒软件扫描(ClamAV等)
7. 存储时使用安全文件名(UUID重命名)

常见陷阱与解决方案

陷阱 解决方案
仅检查Content-Type(易伪造) 必须检测文件实际内容魔数
信任文件扩展名 禁用双层扩展名攻击 (file.php.jpg)
GIF绕过(图片内嵌PHP代码) 使用GD/Pillow重绘图片,重新编码
类型混淆(Polyglot文件) 文件重新编码,剥离非必要数据
压缩包炸弹(Zip Bomb) 限制解压后大小、检查压缩率

推荐工具库

  • Python: python-magic, pillow, pyclamd
  • PHP: finfo, getimagesize, ImageMagick
  • Node.js: file-type, sharp, clamscan
  • Java: Apache Tika, ImageIO
  • Go: http.DetectContentType, gopacket

最严格的校验方案 = 文件签名检测 + 白名单扩展名 + 重新编码转换 + 存储时安全重命名,绝对不要只依赖前端校验或文件扩展名。

抱歉,评论功能暂时关闭!