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"
相关推荐
秋难降1 分钟前
SQL 索引突然 “罢工”?快来看看为什么
数据库·后端·sql
Access开发易登软件1 小时前
Access开发导出PDF的N种姿势,你get了吗?
后端·低代码·pdf·excel·vba·access·access开发
中国胖子风清扬2 小时前
Rust 序列化技术全解析:从基础到实战
开发语言·c++·spring boot·vscode·后端·中间件·rust
_oP_i2 小时前
WinForms 项目里生成时选择“首选目标平台 32 位导致有些电脑在获取office word对象时获取不到
c#·office
bobz9652 小时前
分析 docker.service 和 docker.socket 这两个服务各自的作用
后端
要记得喝水2 小时前
C#某公司面试题(含题目和解析)--1
开发语言·windows·面试·c#·.net
野犬寒鸦2 小时前
力扣hot100:旋转图像(48)(详细图解以及核心思路剖析)
java·数据结构·后端·算法·leetcode
phiilo3 小时前
golang 设置进程退出时kill所有子进程
后端
花花无缺3 小时前
python自动化-pytest-用例发现规则和要求
后端·python