Data loss when passing raw HTML from View to Controller in POST request — XSS-safety & information loss

后端 未结 1 949
感动是毒
感动是毒 2020-12-22 07:31

Preamble

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

相关标签:
1条回答
  • 2020-12-22 08:20

    The solution

    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.

    Action

    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");
        }
    

    Custom Model Binder

    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;
            }
        }
    

    [HELP] Missing piece -- understanding the root cause

    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.

    As seen here and here:

    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.

    Sources

    My solution was based on:

    1. This answer. Though request.Unvalidated is no longer supported.
    2. Custom Model Binding.
    3. HtmlSanitizer.
    4. This answer was helpful in pointing me in the right direction.
    0 讨论(0)
提交回复
热议问题