My use-case includes a front-end WYSIWYG editor. Taking user input in HTML5/CSS format from the CSHTML Front-End View. Receiving the input in the Backend Controller's action via POST request. And finally doing fancy Database stuff with it.
Sounds pretty easy. Using this beast of an editor, it's very straightforward and customizable.
WYSIWYG editor textarea
nested within a form
to send editor's raw HTML data using POST
<form class="form" asp-controller="CreationController" asp-action="CreateSnowflakeBlogpost" method="post">
<button type="submit" class="btn btn-link">Submit Snowflake Blogpost</button>
<textarea name="snowflakeHtmlContent" id="joditEditor"> </textarea>
</form>
Controller's action receiving POST parameter.
[HttpPost]
public async Task<IActionResult> CreateSnowflakeBlogpost(string snowflakeHtmlContent)
{
// store HTML content in DB and do fancy operations
// redirect to something else
return RedirectToAction("PreviewSnowflakeBlogpost");
}
HTML5/CSS tags are lost along the way when passing the POST data. Upon inspection, they are sent successfully from the View. The Action's parameter though has incorrect data.
Looks like sanitization is in motion here, stripping POST parameters of the HTML tags we want to deliberately keep.
It looks like there are possible solutions for this.
[Request.Unvalidated]
annotation. Deprecated.[AllowHtml]
annotation. Deprecated. See here and here.@Html.Raw(theString)
here but it's for passing unsafe data from Controller to View. Our use-case is the opposite.How do I pass my raw HTML/CSS data from View to Action? satisfying the following conditions:
No data-loss of markup.
Prevent unsafe data that poses XSS risk. As per the guidelines.
I ended up using Custom Model Binding which bypassed this overly-eager sanitization/data loss. As a result preserved the HTML tags I want.
However this introduces XSS risk. To counter-react passing unsafe data, I used HtmlSanitizer to omit unsafe HTML/CSS tags.
Added [ModelBinder(typeof(AllowSanitizedHtmlBinder))]
annotation to parameter
[HttpPost]
public async Task<IActionResult> CreateSnowflakeBlogpost([ModelBinder(typeof(AllowSanitizedHtmlBinder))] string snowflakeHtmlContent)
{
// store HTML content in DB and do fancy operations
// redirect to something else
return RedirectToAction("PreviewSnowflakeBlogpost");
}
This custom model binder is like a relay and prevents any data loss in our POST parameter. HtmlSanitizer
was used here before binding the value to prevent XSS.
// Custom Model Binding
using Microsoft.AspNetCore.Mvc.ModelBinding;
// HTML Sanitizer
using Ganss.XSS;
public class AllowSanitizedHtmlBinder: IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var modelName = bindingContext.ModelName;
// Try to fetch the value of the argument by name
var valueProviderResult =
bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName,
valueProviderResult);
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
// Sanitize HTML from harmful XSS markup
var sanitizer = new HtmlSanitizer();
var sanitizedValue = sanitizer.Sanitize(value);
bindingContext.Result = ModelBindingResult.Success(sanitizedValue);
return Task.CompletedTask;
}
}
With my working solution above, I still have no clue why HTML markup are sanitized and removed by default. Even though everyone is claiming this is not supported and such responsibility is app-specific.
You don't need [AllowHtml] anymore, because nobody denies HTML in ASP.NET Core 2.0
Don't need [AllowHtml] or RequestValidationEnabled because we don't have request validation in this system
Any help in demystifying the root cause would be HUGELY appreciated.
My solution was based on:
request.Unvalidated
is no longer supported.