We have an ASP.NET Core 6 MVC application that stores html and JS script in a SQL database and then on page load, it renders that html template and execute js script (yes, that's the application requirement to allow user to create different HTML templates).
We use ace editor for the html and js editing.
HtmlTemplate and Js are two hidden fields, ace editor sets the value to these two fields when anything changes.
<script type="text/javascript">
$(function () {
//HTML
var _htmleditor = ace.edit("editor");
var _template = $("#HtmlTemplate");
configureHtmlTemplate();
function configureHtmlTemplate() {
//some more configuration goes here
_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() {
//some more configuration goes here
_jsEditor.getSession().on("change", function () {
_js.val(_jsEditor.getSession().getValue());
});
_jsEditor.getSession().setValue(_js.val());
}
})
Server side SaveTemplate action method saves html and Js to the database and then some other user action invoke Render method:
[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);
}
Render.cshtml:
@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>
Rightfully so, Checkmarks reports this as venerable to XSS attack since we are using @Html.Raw()
From Microsoft Prevent XSS in Asp.NET Core
The Razor engine used in MVC automatically encodes all output sourced from variables, unless you work really hard to prevent it doing so.
But in my application, I have to render stored html in the browser so I am using @Html.Raw()
The general accepted practice is that encoding takes place at the point of output and encoded values should never be stored in a database.
But if I encode the template before outputting, then the following code would not produce expected result. Instead of showing bold text it will render as <b>Foo Bar</b> which is not expected.
@{
var htmlStoredInDB = "<b>Foo Bar</b>";
var untrustedInput = System.Text.Encodings.Web.HtmlEncoder.Default.Encode(htmlStoredInDB);
}
@Html.Raw(untrustedInput)
Whats the solution here? Is there utility available in .NET 6 to sanitize html and js before rendering? or any other better option?
Update 1
I am thinking of two-step approach here(I am still open for any other suggestions)
Sanitize the HTML before saving into the database, to remove any malicious code.
In step 1, the sanitization will only work for HTML template not for JS template. It is difficult to distinguish between bad JS vs good JS. A sandbox technique can isolate the HTML and JS content from main window and minimize the blast radius. Sandboxing may require more work & testing. Our existing template may need to refactor. I Noticed JSFiddle use the sandboxing approach
I have searched the issue via the key words --<asp.net core Cleans HTML to avoid XSS attacks>.
And I found the official doc has
HtmlEncoderandJavaScriptEncoder.Also I found this excellent github repo(HtmlSanitizer).