ASP.NET Core(C#)中如何验证文件内容以防范误导性扩展名?
ASP.NET Core 基于文件内容验证文本文件的实现
针对你需要验证.abc文件实际为文本内容、防止可执行文件重命名上传的需求,可以通过以下几步实现可靠的内容验证:
核心思路
- 排除可执行文件特征:可执行文件(如EXE、DLL、ELF等)开头有固定的字节签名,先检查文件前几个字节直接排除这类文件。
- 验证文本有效性:通过检查内容中可打印字符的比例,或尝试用文本编码解码,判断文件是否为有效文本(可执行文件包含大量非打印/不可解码的字节)。
实现代码
1. 内容验证工具类
创建一个静态工具类封装验证逻辑,避免重复代码:
using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; public static class FileContentValidator { // 常见可执行/非文本文件的开头字节签名 private static readonly HashSet<byte[]> NonTextSignatures = new HashSet<byte[]>(new ByteArrayComparer()) { new byte[] { 0x4D, 0x5A }, // EXE/DLL的MZ头 new byte[] { 0x7F, 0x45, 0x4C, 0x46 }, // ELF格式(Linux可执行文件) new byte[] { 0xCA, 0xFE, 0xBA, 0xBE }, // Java Class文件 new byte[] { 0xFF, 0xD8, 0xFF }, // JPG图片 new byte[] { 0x89, 0x50, 0x4E, 0x47 } // PNG图片 }; // 判断文件开头是否匹配非文本签名 private static bool HasNonTextHeader(byte[] headerBytes) { foreach (var signature in NonTextSignatures) { if (headerBytes.Length >= signature.Length && headerBytes.Take(signature.Length).SequenceEqual(signature)) { return true; } } return false; } // 检查内容中可打印字符比例,判断是否为文本 private static bool IsValidTextContent(byte[] contentBytes) { int printableChars = 0; int totalBytes = contentBytes.Length; foreach (byte b in contentBytes) { // 包含可打印ASCII字符(32-126)+ 常见控制字符(换行、回车、制表符) if ((b >= 32 && b <= 126) || b == 10 || b == 13 || b == 9) { printableChars++; } } // 可打印字符占比超过90%则认定为文本(可根据需求调整阈值) return (printableChars / (double)totalBytes) > 0.9; } // 针对.abc文件的专属验证方法 public static async Task<bool> IsValidAbcTextFile(IFormFile file) { if (file == null || file.Length == 0) return false; using (var stream = file.OpenReadStream()) { // 读取前16字节检查非文本签名 var headerBuffer = new byte[16]; await stream.ReadAsync(headerBuffer, 0, headerBuffer.Length); if (HasNonTextHeader(headerBuffer)) return false; // 读取前4KB内容验证文本有效性(避免加载大文件占用内存) var contentBuffer = new byte[4096]; int bytesRead = await stream.ReadAsync(contentBuffer, 0, contentBuffer.Length); // 空文件直接判定无效 if (bytesRead == 0) return false; return IsValidTextContent(contentBuffer.Take(bytesRead).ToArray()); } } } // 用于HashSet比较字节数组的自定义比较器 public class ByteArrayComparer : IEqualityComparer<byte[]> { public bool Equals(byte[] x, byte[] y) { if (x == null || y == null) return x == y; return x.SequenceEqual(y); } public int GetHashCode(byte[] obj) { if (obj == null) return 0; int hash = 17; foreach (byte b in obj) { hash = hash * 31 + b.GetHashCode(); } return hash; } }
2. 在控制器中使用验证
在文件上传接口中,先验证扩展名(快速过滤),再执行内容验证:
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.IO; using System.Threading.Tasks; [ApiController] [Route("api/files")] public class FileUploadController : ControllerBase { [HttpPost("upload")] public async Task<IActionResult> UploadAbcFile(IFormFile file) { if (file == null || file.Length == 0) return BadRequest("请选择要上传的.abc文件"); // 先检查扩展名(快速过滤非.abc文件) var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant(); if (fileExtension != ".abc") return BadRequest("仅支持.abc格式的文本文件"); // 执行内容验证 bool isContentValid = await FileContentValidator.IsValidAbcTextFile(file); if (!isContentValid) return BadRequest("上传的文件不是有效的文本文件,禁止上传"); // 后续保存文件逻辑(示例) var uploadDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "abc-uploads"); Directory.CreateDirectory(uploadDir); var savePath = Path.Combine(uploadDir, Path.GetFileName(file.FileName)); using (var stream = new FileStream(savePath, FileMode.Create)) { await file.CopyToAsync(stream); } return Ok(new { Message = "文件上传成功", FilePath = savePath }); } }
补充优化建议
- 编码兼容:如果需要支持GB2312等非UTF-8编码的文本,可以在
IsValidTextContent中添加多编码尝试解码的逻辑,避免误判合法文本。 - 阈值调整:可打印字符的比例阈值可根据业务需求调整,如果允许包含较多特殊字符,可适当降低阈值(如80%)。
- 大文件处理:如果需要支持超大文件,可分段读取内容验证,避免一次性加载过多数据到内存。
内容的提问来源于stack exchange,提问作者Md. Mustafizur Rahman
相关产品推荐
相关产品推荐

