本文目录导读:

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