content-type 不可信,必须通过文件头(magic bytes)进行 mime 嗅探校验;推荐用 filetypedetective 等库读前 512 字节判断真实类型,并在中间件层拦截不匹配或高危类型,结合业务白名单与后续内容分析实现安全上传。

Content-Type 为什么不能信
浏览器发来的 Content-Type 是客户端自填的,跟文件真实类型毫无关系。用户改个后缀、用 curl 手动构造请求、甚至拖拽一个 .exe 到上传框,multipart/form-data 里照样能写 Content-Type: image/png。C# 后端如果只靠这个判断文件类型,等于把门禁卡交给陌生人自己刷。
必须做 MIME 嗅探(但别用 FileExtensionContentTypeProvider)
FileExtensionContentTypeProvider 只查扩展名,完全不看文件内容,对绕过毫无防御力。真要防伪装,得读文件头(magic bytes)。
- 推荐用
Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream配合System.IO.BinaryReader读前几百字节 - 或轻量级库如
FileTypeDetective(NuGet 可搜),它内置常见格式签名表,比手写 switch 更可靠 - 注意:不要全文件读取再判断——大文件会吃内存,嗅探只需前 512 字节足够
ASP.NET Core 中间件拦截伪造 Content-Type 的实操点
在 Startup.Configure 或 Program.cs 的管道里加一层校验逻辑,比在 Controller 里做更早、更统一。
- 用
context.Request.Form.Files拿到IFormFile实例后,立刻调file.OpenReadStream() - 用
Stream.ReadAsync(buffer, 0, 512)读头部,传给 MIME 检测函数 - 若检测出的 MIME 类型和
file.ContentType不匹配,且不在白名单内(比如允许image/*但实际是application/x-executable),直接context.Response.StatusCode = 400并返回 - 特别注意:IIS 默认限制单个请求体最大 30MB,若需支持大文件上传,还得同步调大
MaxRequestBodySize和requestLimits.maxAllowedContentLength
常见误判场景和绕过手段
不是所有“看起来像图片”的文件头都安全。攻击者会利用格式兼容性做文章。
- .jpg 文件开头加 PHP 标签(
<?php ... ?>)仍能被 GD 库解析——此时 MIME 嗅探会报image/jpeg,但服务器执行时可能触发 RCE - Office 文档(.docx/.xlsx)本质是 ZIP,头部是
PK\x03\x04,但里面可嵌恶意宏或 HTML 脚本 - 某些 PDF 嗅探库会把含 JavaScript 的 PDF 识别为
application/pdf,但它可能触发 XSS —— 这时候光靠 MIME 检测不够,得配合后续的内容沙箱或静态分析
真正难的不是识别类型,而是定义“这个类型+这个内容是否允许上传”。边界永远在业务规则里,不在某个库的返回值里。










