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"
相关推荐
bobz9654 分钟前
小而精的 HRM 模型
后端
crossoverJie33 分钟前
在多语言的分布式系统中如何传递 Trace 信息
分布式·后端·开源
用户8485081469038 分钟前
SurrealDB 快速上手教程
数据库·后端
用户6147493427741 小时前
JeecgBoot 项目理解与使用心得
后端
ZIQ1 小时前
单机线程池任务防丢设计与实现思路
后端
MaxHua1 小时前
我用 Java 飞算 AI 快速开发了一个音频转文字工具
后端
欧阳码农1 小时前
langgraph开发Deep Research智能体-项目搭建
前端·后端·langchain
BigYe程普1 小时前
出海技术栈集成教程(二):Supabase 登录与数据库配置
前端·后端·全栈
臻实2 小时前
Win10系统Ruby+Devkit3.4.5-1安装
开发语言·后端·ruby
汪子熙2 小时前
使用 Python 解析 X.509 格式的公钥证书
后端