ASP.NET 上传文件安全检测方案

一、前端初步过滤(防误操作)

复制代码
<!-- HTML部分 -->

  <input type="file" id="fileUpload" accept=".jpg,.png,.pdf,.docx" />

<button onclick="validateFile()">上传</button>

<script>

function

validateFile()

{

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 FF

            return buffer[0] == 0xFF && buffer[1] == 0xD8 && buffer[2] == 0xFF;

        case ".png":

            // PNG魔数: 89 50 4E 47 0D 0A 1A 0A

            return 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 46

            return 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"
相关推荐
陈随易13 分钟前
Postman 被曝自动往 Claude Code 和 Codex 装插件
前端·后端·程序员
云-缩影2 小时前
拆解 AI 图像生成应用的后端架构:ECS、OSS 和大模型 API 怎么配合
java·spring boot·后端
考虑考虑3 小时前
Posrgresql恢复.backup文件
数据库·后端·postgresql
无奈何杨3 小时前
风控决策系统决策流,策略模式,权重、顺序、投票、最坏
后端
Patrick_Wilson5 小时前
Go 为什么需要 defer?从函数怎么收尾说起
后端·go·编程语言
苏三说技术5 小时前
DeepSeek Harness入驻B站,评论区被干成了大型插件集市!
后端
橙序员小站5 小时前
我一句话让 AI 把公众号工作台搭完了:部署实测记录
后端
Patrick_Wilson5 小时前
从文本输出理解 Go 方法与接口
后端·go·编程语言
QCoding6 小时前
企业级Agent Evaluation实战:Agent能跑就能上线?AI Agent到底怎么测?
后端·spring
苍何6 小时前
让 WorkBuddy 操控浏览器,干活有点猛啊1
后端