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:
- Does Abp.HtmlSanitizer cover all required for sanitizing XSS? Do we need to do additional input filtering?
- ShouldSanitizeContext is not exposed. How is it configured?
- 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)
-
0
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.csCan you share
SanitizeInputFilter
with us? We can understand more easily what the problem is. -
0
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<IHtmlSanitizer>(); 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<object> processedObjects = null) { if (obj == null) return; // Initialize the HashSet if it's the first call if (processedObjects == null) { processedObjects = new HashSet<object>(); } // 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); } } } } }
}
-
0
Hi @hongbing.wang,
Thank you for sharing with us. I will check it as soon as possible.