Base solution for your next web application
Open Closed

Backend HTML sanitizing and filtering #12071


User avatar
0
hongbing.wang created

In the original ASP.NET Zero, the HTML sanitizer config: Configuration.Modules.AbpHtmlSanitizer() .KeepChildNodes() .AddSelector<IAccountAppService>(x => nameof(x.IsTenantAvailable)) .AddSelector<IAccountAppService>(x => nameof(x.Register));

We have extended to include all the API methods except a couple. It seems OK. It seems there are no issues without KeepChildNodes(),

Questions:

  1. Does Abp.HtmlSanitizer cover all required for sanitizing XSS? Do we need to do additional input filtering?
  2. ShouldSanitizeContext is not exposed. How is it configured?
  3. Any risks to filter all the API methods?

We have implemented a global input filter SanitizeInputFilter using the same Abp.HtmlSanitizer. Our test results:

  • SanitizeInputFilter: Input: "20240705 <a href=></a>", output: "20240705". It filters out HTML code.
  • options.Filters.AddService(typeof(AbpHtmlSanitizerActionFilter)): Input: "20240705 <a href=></a>", Output: "20240705 <a href=></a>". It doesn't filter out HTML code.

Why SanitizeInputFilter and AbpHtmlSanitizerActionFilter have different filtering behavior? Should "<a href=></a>" be filtered out by AbpHtmlSanitizerActionFilter or just be sanitized? Is the discrepancy caused by the flag ShouldSanitizeContext with AbpHtmlSanitizerActionFilter?


3 Answer(s)
  • User Avatar
    0
    m.aliozkaya created
    Support Team

    Hi @hongbing.wang,

    AbpHtmlSanitizer is the wrapper for this project. https://github.com/mganss/HtmlSanitizer We sanitize at aspnetboilerplate using these methods. You can the ShouldSanitizeContext here https://github.com/aspnetboilerplate/aspnetboilerplate/blob/dev/src/Abp.HtmlSanitizer/HtmlSanitizer/HtmlSanitizerHelper.cs

    Can you share SanitizeInputFilter with us? We can understand more easily what the problem is.

  • User Avatar
    0
    hongbing.wang created

    namespace umsplus.Web.Xss { using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; using System.Collections.Generic; using System.Reflection;

    public class SanitizeInputFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context)
        {
            var htmlSanitizer = context.HttpContext.RequestServices.GetService&lt;IHtmlSanitizer&gt;();
            foreach (var argument in context.ActionArguments.Values)
            {
                SanitizeObjectProperties(argument, htmlSanitizer);
            }
        }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            // No need to do anything here
        }
    
        private void SanitizeObjectProperties(object obj, IHtmlSanitizer sanitizer, HashSet&lt;object&gt; processedObjects = null)
        {
            if (obj == null) return;
    
            // Initialize the HashSet if it's the first call
            if (processedObjects == null)
            {
                processedObjects = new HashSet&lt;object&gt;();
            }
    
            // Check if the object has already been processed to avoid infinite recursion
            if (processedObjects.Contains(obj))
            {
                return;
            }
    
            // Add the current object to the set of processed objects
            processedObjects.Add(obj);
    
            var properties = obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
            foreach (var property in properties)
            {
                // Skip properties that require index parameters (like indexers)
                if (property.GetIndexParameters().Length > 0)
                {
                    continue;
                }
    
                if (property.PropertyType == typeof(string) && property.CanWrite)
                {
                    var value = (string)property.GetValue(obj);
                    if (value != null)
                    {
                        var sanitizedValue = sanitizer.Sanitize(value);
                        property.SetValue(obj, sanitizedValue);
                    }
                }
                else if (!property.PropertyType.IsPrimitive && !property.PropertyType.IsEnum && property.PropertyType != typeof(string))
                {
                    var propertyValue = property.GetValue(obj);
                    if (propertyValue != null)
                    {
                        SanitizeObjectProperties(propertyValue, sanitizer, processedObjects);
                    }
                }
            }
        }
    }
    

    }

  • User Avatar
    0
    m.aliozkaya created
    Support Team

    Hi @hongbing.wang,

    Thank you for sharing with us. I will check it as soon as possible.