如何安全存储并渲染数据库中的HTML与JS?ASP.NET Core 6 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沙箱渲染」**的组合:
- 保存前用HtmlSanitizer清洗HTML,移除恶意标签/属性
- 渲染时将用户HTML和JS放入带sandbox属性的iframe中隔离执行
- 禁止用户JS直接访问主页面敏感资源,通过
postMessage实现必要的跨环境交互
内容的提问来源于stack exchange,提问作者LP13

