ASP.NET 上传文件安全检测方案
一、前端初步过滤(防误操作)
<!-- HTML部分 --><input type="file" id="fileUpload" accept=".jpg,.png,.pdf,.docx" /><button onclick="validateFile()">上传</button><script>functionvalidateFile(){const file = document.getElementById('fileUpload').files[0];if (!file) return alert('请选择文件');// 检查文件扩展名const allowedExtensions = /(\.jpg|\.png|\.pdf|\.docx)$/i;if (!allowedExtensions.test(file.name)){ alert('不允许的文件类型'); return false; }// 检查文件大小(示例:限制20MB)if (file.size > 20 * 1024 * 1024){ alert('文件超过20MB'); return false; }// 提交表单document.getElementById('uploadForm').submit();}</script>
二、后端深度验证(核心防御)
// 1. 验证文件扩展名(双重验证,防止绕过前端)
// 2. 验证MIME类型(检查Content-Type头)
// 3. 验证文件内容(魔数检测)
// 4. 扫描文件是否包含恶意代码(示例:检查HTML文件中的脚本标签)
// 5. 保存文件到安全位置(禁用执行权限)
// ASP.NET MVC控制器示例[HttpPost][ValidateAntiForgeryToken]public ActionResult Upload(HttpPostedFileBase file){if (file == null || file.ContentLength == 0)return Json(new { success = false, message = "请选择文件" });try{// 1. 验证文件扩展名(双重验证,防止绕过前端)var allowedExtensions = new[] { ".jpg", ".png", ".pdf", ".docx" };var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant();if (!allowedExtensions.Contains(fileExtension))return Json(new { success = false, message = "文件类型不允许" });// 2. 验证MIME类型(检查Content-Type头)if (!IsValidMimeType(file.ContentType, fileExtension))return Json(new { success = false, message = "文件类型不匹配" });// 3. 验证文件内容(魔数检测)if (!IsValidFileContent(file.InputStream, fileExtension))return Json(new { success = false, message = "文件内容异常" });// 4. 扫描文件是否包含恶意代码(示例:检查HTML文件中的脚本标签)if (fileExtension == ".html" && ContainsMaliciousCode(file.InputStream))return Json(new { success = false, message = "文件包含恶意代码" });// 5. 保存文件到安全位置(禁用执行权限)var fileName = Guid.NewGuid().ToString() + fileExtension;var filePath = Path.Combine(Server.MapPath("~/Uploads/"), fileName);file.SaveAs(filePath);return Json(new { success = true, message = "上传成功" });}catch (Exception ex){// 记录详细日志(包含文件名、大小、时间戳等)Logger.Error($"文件上传失败: {ex.Message}", ex);return Json(new { success = false, message = "上传过程中发生错误" });}}// 验证MIME类型private bool IsValidMimeType(string contentType, string fileExtension){var allowedMimeTypes = new Dictionary<string, string[]>{{ ".jpg", new[] { "image/jpeg", "image/pjpeg" } },{ ".png", new[] { "image/png" } },{ ".pdf", new[] { "application/pdf" } },{ ".docx", new[] { "application/vnd.openxmlformats-officedocument.wordprocessingml.document" } }};if (!allowedMimeTypes.ContainsKey(fileExtension))return false;return allowedMimeTypes[fileExtension].Contains(contentType);}// 验证文件内容(魔数检测)private bool IsValidFileContent(Stream stream, string fileExtension){// 重置流位置stream.Position = 0;// 读取文件前几个字节(魔数)var buffer = new byte[8];stream.Read(buffer, 0, buffer.Length);stream.Position = 0; // 重置流位置供后续使用// 根据文件类型检查魔数switch (fileExtension){case ".jpg":// JPEG魔数: FF D8 FFreturn buffer[0] == 0xFF && buffer[1] == 0xD8 && buffer[2] == 0xFF;case ".png":// PNG魔数: 89 50 4E 47 0D 0A 1A 0Areturn buffer[0] == 0x89 && buffer[1] == 0x50 && buffer[2] == 0x4E && buffer[3] == 0x47 && buffer[4] == 0x0D && buffer[5] == 0x0A && buffer[6] == 0x1A && buffer[7] == 0x0A; case ".pdf":// PDF魔数: 25 50 44 46return buffer[0] == 0x25 && buffer[1] == 0x50 && buffer[2] == 0x44 && buffer[3] == 0x46;default:// 其他类型可以添加更多检查return true;}}// 检查文件是否包含恶意代码(示例:HTML文件)private bool ContainsMaliciousCode(Stream stream){using (var reader = new StreamReader(stream)){var content = reader.ReadToEnd();stream.Position = 0; // 重置流位置// 检测常见的恶意代码模式//在这里增加被攻击的恶意代码var maliciousPatterns = new[] {@"<script\s*[^>]*>",@"vbscript:",@"onerror\s*=",@"<iframe\s*[^>]*>"};return maliciousPatterns.Any(pattern =>Regex.IsMatch(content, pattern, RegexOptions.IgnoreCase));}}@"<?xml", // XML注入@"<!DOCTYPE", // XXE攻击@"<script", // 脚本注入@"<img", // 图片标签注入@"<iframe", // iframe注入@"<object", // object标签@"<embed", // embed标签@"<applet", // applet标签@"<form", // form标签@"<input", // input标签@"<link", // link标签@"<style", // style标签@"<svg", // SVG注入@"<math", // MathML注入@"onerror", // 事件处理器@"onload", // 事件处理器@"javascript:", // JS协议@"data:text/html", // data协议@"base64,", // base64编码@"eval(", // eval函数@"expression(", // CSS表达式@"alert(", // alert函数@"document.cookie", // 访问cookie@"window.location", // 重定向@"window.open", // 弹窗@"fetch(", // fetch API@"XMLHttpRequest", // AJAX@"ActiveXObject", // ActiveX@"<meta", // meta标签@"<body", // body标签@"<head", // head标签@"<title", // title标签@"<audio", // audio标签@"<video", // video标签@"<source", // source标签@"<track", // track标签@"<marquee", // marquee标签@"<blink", // blink标签@"<bgsound", // bgsound标签@"<frame", // frame标签@"<frameset", // frameset标签@"<noscript", // noscript标签@"<plaintext", // plaintext标签@"<xss", // xss标签@"vbscript:", // vbscript协议@"mocha:", // mocha协议@"livescript:", // livescript协议@"<!--", // 注释@"-->", // 注释@"<%=", // ASP/模板注入@"<?php", // PHP代码// @"<%!", // JSP/模板注入@"<%#", // ASP.NET模板注入@"<%$", // ASP.NET模板注入@"<%:", // ASP.NET模板注入@"<%?", // 模板注入@"<%--", // 注释@"<%>", // 模板@"<c:", // JSTL标签@"<jsp:", // JSP标签@"<s:", // Struts标签// @"<%@", // 指令// @"<#", // Freemarker@"#include", // SSI@"system(", // 系统命令@"exec(", // 系统命令@"passthru(", // 系统命令@"shell_exec(", // 系统命令@"popen(", // 系统命令@"proc_open(", // 系统命令@"require", // PHP@"include", // PHP@"require_once", // PHP@"include_once", // PHP@"import", // Python/JS// @"from", // Python@"os.system", // Python@"subprocess", // Python@"rm -rf", // Linux命令@"del ", // Windows命令@"request",@"eval",@"cmd"