You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何安全存储并渲染数据库中的HTML与JS?ASP.NET Core 6 XSS防护问询

ASP.NET Core 6 MVC自定义模板渲染的XSS防护方案

问题背景

我们有一个ASP.NET Core 6 MVC应用,业务需求允许用户创建自定义HTML模板,需将HTML和JS脚本存储至SQL数据库,页面加载时渲染HTML模板并执行JS脚本。

当前实现代码

前端编辑器同步逻辑

<script type="text/javascript">
  $(function () {
     //HTML编辑器配置
     var _htmleditor = ace.edit("editor");
     var _template = $("#HtmlTemplate");
     configureHtmlTemplate();

     function configureHtmlTemplate() {
         //额外配置逻辑省略
        _htmleditor.getSession().on("change", function () {
            _template.val(_htmleditor.getSession().getValue());
        });

        _htmleditor.getSession().setValue(_template.val());
     }

    //JS编辑器配置
    var _jsEditor = ace.edit("jsEditor");
    var _js = $("#Js");
    configureJsTemplate();

    function configureJsTemplate() {    
         //额外配置逻辑省略      
        _jsEditor.getSession().on("change", function () {
            _js.val(_jsEditor.getSession().getValue());
        });
        _jsEditor.getSession().setValue(_js.val());
    }
 })
</script>

服务端保存与渲染逻辑

[HttpPost]
[Route("items/{id}/templates")]
public async Task<ActionResult> SaveTemplate([FromRoute(Name = "id")] int itemID, [FromForm] EditTemplateModel model)
{
    await _templateService.SaveTemplate(new ItemTemplate()
    {
        Id = model.Id,
        HtmlTemplate = model.HtmlTemplate,
        Js = model.Js
    });
    
    return View(model);
}
        
[HttpGet]
[Route("items/{id}/render")]
public async Task<IActionResult> Render([FromRoute(Name = "id")] int itemID)
{
    var template = await _templateService.GetByID(itemID);

    var model = new RenderModel()
    {
        ItemID = itemID,
        Html = template.HtmlTemplate,                
        Js = template.JS
    };

    return View(model);
}

视图渲染逻辑

@model RenderModel

<form method="post" id="renderForm" asp-action="SaveStuff" asp-controller="ItemTemplates" asp-route-id="@Model.ItemID">
    @Html.Raw(Model.Html)           
    <button id="btnTest" class="btn btn-primary mt-3" type="submit">Submit</button>
</form>

<script type="text/javascript">    
    @Html.Raw(Model.Js)
</script>

核心问题

Checkmarx指出该实现存在XSS漏洞,原因是使用了@Html.Raw()直接渲染用户输入的HTML和JS。Razor引擎默认自动编码输出,但业务需要保留HTML格式(比如<b>Foo Bar</b>需显示为粗体),编码后无法满足需求。现需寻找.NET 6中可行的HTML/JS清洗工具或更优防护方案,同时评估「保存前清洗HTML+沙箱隔离JS」方案的可行性。


解决方案

一、HTML清洗(Sanitization)工具

.NET生态中有成熟的HTML清洗库,可在保存到数据库前过滤恶意HTML标签/属性,同时保留合法的HTML结构:

1. HtmlSanitizer库

这是社区广泛使用的轻量工具,支持自定义允许的标签、属性、样式。集成到SaveTemplate方法中:

using Ganss.XSS;

public async Task<ActionResult> SaveTemplate([FromRoute(Name = "id")] int itemID, [FromForm] EditTemplateModel model)
{
    var sanitizer = new HtmlSanitizer();
    // 配置业务允许的标签(按需调整)
    sanitizer.AllowedTags.AddRange(new[] { "b", "i", "div", "input", "label", "span" });
    // 配置允许的属性(按需调整)
    sanitizer.AllowedAttributes.AddRange(new[] { "class", "id", "name", "type", "value" });
    
    // 清洗HTML模板,自动移除<script>、<iframe>、onclick等恶意内容
    var sanitizedHtml = sanitizer.Sanitize(model.HtmlTemplate);

    await _templateService.SaveTemplate(new ItemTemplate()
    {
        Id = model.Id,
        HtmlTemplate = sanitizedHtml,
        Js = model.Js
    });
    
    return View(model);
}

2. ASP.NET Core官方HTML清洗包

安装Microsoft.AspNetCore.Html.Sanitizer NuGet包后,可通过依赖注入使用官方清洗工具:

using Microsoft.AspNetCore.Html.Sanitization;

// 先在Program.cs注册服务
// builder.Services.AddHtmlSanitizer();

public async Task<ActionResult> SaveTemplate([FromRoute(Name = "id")] int itemID, [FromForm] EditTemplateModel model, IHtmlSanitizer sanitizer)
{
    var sanitizedHtml = sanitizer.Sanitize(model.HtmlTemplate);
    // 保存逻辑同上
}

二、JS脚本的安全隔离方案

直接渲染用户自定义JS风险极高,即使清洗HTML,JS仍可能包含恶意逻辑。推荐采用iframe沙箱隔离方案,可行性高且防护效果显著:

沙箱隔离实现(需重构视图)

修改Render.cshtml,将用户HTML和JS放入带sandbox属性的iframe中:

@model RenderModel

<form method="post" id="renderForm" asp-action="SaveStuff" asp-controller="ItemTemplates" asp-route-id="@Model.ItemID">
    <iframe sandbox="allow-same-origin allow-forms allow-scripts" id="templateFrame" style="border:none;width:100%;min-height:500px;"></iframe>
    <button id="btnTest" class="btn btn-primary mt-3" type="submit">Submit</button>
</form>

<script type="text/javascript">
    // 向沙箱iframe注入用户模板内容
    const frame = document.getElementById('templateFrame');
    const frameDoc = frame.contentDocument || frame.contentWindow.document;
    frameDoc.open();
    frameDoc.write(`
        ${@Html.Raw(Model.Html)}
        <script>
            ${@Html.Raw(Model.Js)}
        <\/script>
    `);
    frameDoc.close();
</script>
sandbox属性说明(按需调整)
  • allow-same-origin:允许iframe与主页面同域(需共享数据时保留,否则移除)
  • allow-forms:允许iframe内提交表单
  • allow-scripts:允许执行JS
  • 移除allow-top-navigation可禁止iframe跳转主页面
  • 移除allow-popups可禁止弹出窗口

该方案会隔离用户JS的执行环境,恶意代码无法访问主页面的Cookie、DOM等敏感资源,大幅降低XSS风险。若需主页面与iframe交互,可通过postMessage实现安全通信。

三、综合最优方案

结合业务需求,推荐采用**「前端编辑+后端HTML清洗+iframe沙箱渲染」**的组合:

  1. 保存前用HtmlSanitizer清洗HTML,移除恶意标签/属性
  2. 渲染时将用户HTML和JS放入带sandbox属性的iframe中隔离执行
  3. 禁止用户JS直接访问主页面敏感资源,通过postMessage实现必要的跨环境交互

内容的提问来源于stack exchange,提问作者LP13

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 18:07:15