how to sanitize input data in web api using anti xss attack

asp.net web api prevent xss
prevent cross site scripting (xss in asp net core)
prevent xss in web api c#
sanitize html xss
how to fix cross site scripting (xss using microsoft .net web protection library)
bypass html sanitizer
cross site scripting prevention c# mvc
antixss

Below is the snippet of my code

Model class

// Customer.cs

using CommonLayer;

namespace Models
{
    public class Customer
    {
        public int Id { get; set; }

        [MyAntiXss]
        public string Name { get; set; }
    }
}

I want to sanitize the value in the 'Name' field of the Model class as below

// CutstomModelBinder.cs

 using Microsoft.Security.Application;
    using System.ComponentModel;
    using System.Linq;
    using System.Web.Mvc;

    namespace CommonLayer
    {
        public class CutstomModelBinder : DefaultModelBinder
        {
            protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
            {
                if (propertyDescriptor.Attributes.OfType<MyAntiXssAttribute>().Any())
                {
                    ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);
                    string filteredValue = Encoder.HtmlEncode(valueResult.AttemptedValue);
                    propertyDescriptor.SetValue(bindingContext.Model, filteredValue);
                }
                else
                    base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
            }
        }
    }

I changed the 'DefaultBinder' to my 'CutstomModelBinder' as below

// Global.asax.cs

using CommonLayer;
using System.Web.Http;
using System.Web;
using System.Web.Mvc;

namespace WebAPI
{
    public class WebApiApplication : HttpApplication
    {
        protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);
            ModelBinders.Binders.DefaultBinder = new CutstomModelBinder();
        }
    }
}

I wrote a controller class as below

// CustomerController.cs

using Models;
using System.Collections.Generic;
using System.Web.Http;

namespace WebAPI.Controllers
{
    public class CustomerController : ApiController
    {
        public string Post([FromBody]Customer customer)
        {
            //customer.Name = Encoder.HtmlEncode(customer.Name);
            return string.Format("Id = {0}, Name = '{1}'", customer.Id, customer.Name);
        }
    }
}

When I am calling the above controller's class 'Post' method as below, it is giving call to the 'Post' method of the controller's class as expected. But it is not calling the 'BindProperty' method in my 'CutstomModelBinder' class.

// Program.cs

using Models;
using System;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;

namespace Client
{
    public static class Program
    {
        public static void Main(params string[] args)
        {
            bool success = Post();
            Console.WriteLine("success = " + success);
            Console.Read();
        }

        private static HttpClient GetHttpClient()
        {
            HttpClient client = new HttpClient { BaseAddress = new Uri("http://localhost:49295/") };
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            return client;
        }

        private static bool Post()
        {
            Customer customer = new Customer { Id = 1, Name = "<br>Anivesh</br>" };
            HttpContent content = new ObjectContent<Customer>(customer, new JsonMediaTypeFormatter());

            HttpClient client = GetHttpClient();
            HttpResponseMessage response = client.PostAsync("Customer", content).Result;
            client.Dispose();

            if (response.IsSuccessStatusCode)
            {
                string expected = string.Format("Id = {0}, Name = '{1}'", customer.Id, customer.Name);
                string result = response.Content.ReadAsAsync<string>().Result;
                return expected == result;
            }
            else
                return false;
        }
    }
}

Please let me know the correct way of using the 'DataBinders', so that I could sanitize the input data at a common place, before receiving calls in the controllers.

To sanitize input in a generic fashion using Web API, you could create your own ModelBinder as described in my previous answer, however an easier approach would likely be to modify the existing JsonMediaTypeFormatter to include the desired santization logic within the ReadFromStreamAsync method.

One approach you could try is as follows:

First, create a generic Attribute which is used to decorate the properties within your DTO that requires sanitization, ie:

 [AttributeUsage(AttributeTargets.Property)]
 public sealed class SanitizeAttribute : Attribute
 { }

Then create a sub-type of the JsonMediaTypeFormatter which takes care of the sanitization, ie:

public sealed class SanitizingJsonMediaTypeFormatter : JsonMediaTypeFormatter
{
    public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
    {
        Task<object> resultTask = base.ReadFromStreamAsync(type, readStream, content, formatterLogger, cancellationToken);

        var propertiesFlaggedForSanitization = type.GetProperties().Where(e => e.GetCustomAttribute<SanitizeAttribute>() != null).ToList();
        if (propertiesFlaggedForSanitization.Any())
        {
            var result = resultTask.Result;
            foreach (var propertyInfo in propertiesFlaggedForSanitization)
            {
                var raw = (string)propertyInfo.GetValue(result);
                if (!string.IsNullOrEmpty(raw))
                {
                    propertyInfo.SetValue(result, AntiXssEncoder.HtmlEncode(raw, true));
                }
            }
        }
        return resultTask;
    }
}

This implementation simply checks to see if the resulting Type has any properties that are decorated with the Sanitize attribute, and if so, uses the built-in System.Web.Security.AntiXss.AntiXssEncoder (.NET 4.5 and above) to perform the sanitization.

You'll likely will want to optimize this class such that it caches type and property information such that you're not doing heavy weight reflection calls on each deserialization.

The last step in the process is to replace the built-in JSON media type formatter with your own, within the WebAPI start-up code:

var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
config.Formatters.Remove(jsonFormatter);
config.Formatters.Add(new SanitizingJsonMediaTypeFormatter());

Now any DTO that has properties decorated with the Sanitize attribute will be properly encoded before the DTO even hits your controller.

how to sanitize input data in web api using anti xss attack - c#, how to sanitize input data in web api using anti xss attack. Below is the snippet of my code Model class // Customer.cs using CommonLayer; namespace Models� Validation as an XSS prevention technique. Validation can be a useful tool in limiting XSS attacks. For example, a numeric string containing only the characters 0-9 won't trigger an XSS attack. Validation becomes more complicated when accepting HTML in user input. Parsing HTML input is difficult, if not impossible.

.NetCore Web API 2. Sanitize Recursively all Properties (in any depth) of the incoming JSON by using InputFormatter.

[AttributeUsage(AttributeTargets.Property)]
public sealed class SanitizePropertyAttribute : Attribute
{
}

public class SanitizeTextInputFormatter: Microsoft.AspNetCore.Mvc.Formatters.TextInputFormatter
{
    private List<String> ExcludeTypes = new List<string>()
    {
        "System.DateTime",
        "System.Int32",
        "System.Int64",
        "System.Boolean",
        "System.Char",
        "System.Object"
    };

    private string CleanInput(string strIn)
    {
        // Replace invalid characters with empty strings.
        try
        {
            // [<>/] or @"[^\w\.@-]"
            return Regex.Replace(strIn, @"[<>/]", "",
                                 RegexOptions.None, TimeSpan.FromSeconds(1.5));
        }
        // If we timeout when replacing invalid characters, 
        // we should return Empty.
        catch (RegexMatchTimeoutException)
        {
            return String.Empty;
        }
    }

    public SanitizeTextInputFormatter()
    {
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));

        SupportedEncodings.Add(Encoding.UTF8);
        SupportedEncodings.Add(Encoding.Unicode);
    }

    private bool ValidateSanitizeProperty(Type type, PropertyInfo PropertyInfo, List<PropertyInfo> orgTypes)
    {
        var listedProperty = orgTypes.Where(_ => _ == PropertyInfo).FirstOrDefault();
        if (PropertyInfo != null && listedProperty == null) orgTypes.Add(PropertyInfo);

        if (listedProperty != null) return false;

        if (type.FullName == "System.String" && PropertyInfo != null)
        {
            var sanitizePropertyAttribute = PropertyInfo.GetCustomAttribute<SanitizePropertyAttribute>();
            //var sanitizeClassAttribute = PropertyInfo.CustomAttributes.Where(e => e.AttributeType == typeof(SanitizePropertyAttribute)).FirstOrDefault();

            return sanitizePropertyAttribute != null;
        }

        var typeProperties = type.GetProperties().Where(_ => _.PropertyType.IsAnsiClass == true && !ExcludeTypes.Contains(_.PropertyType.FullName)).ToList();

        var doSanitizeProperty = false;
        typeProperties.ForEach(typeProperty =>
        {
            if (doSanitizeProperty == false)
                doSanitizeProperty = ValidateSanitizeProperty(typeProperty.PropertyType, typeProperty, orgTypes);
        });

        return doSanitizeProperty;

    }

    protected override bool CanReadType(Type type)
    {
        var result = ValidateSanitizeProperty(type, null, new List<PropertyInfo>());
        return result;
    }

    private object SanitizeObject(object obj, Type modelType)
    {
        if (obj != null)
        {
            List<PropertyInfo> propertiesFlaggedForSanitization = modelType.GetProperties().Where(e => e.GetCustomAttribute<SanitizePropertyAttribute>() != null).ToList();
            if (propertiesFlaggedForSanitization.Any())
            {
                foreach (var propertyInfo in propertiesFlaggedForSanitization)
                {
                    var raw = (string)propertyInfo.GetValue(obj);
                    if (!string.IsNullOrEmpty(raw))
                    {
                        propertyInfo.SetValue(obj, CleanInput(raw));
                        //propertyInfo.SetValue(obj, AntiXssEncoder.HtmlEncode(raw, true));
                        //propertyInfo.SetValue(obj, AntiXssEncoder.UrlEncode(raw));
                    }
                }
            }
        }

        modelType.GetProperties().ToList().Where(_ => _.PropertyType.IsAnsiClass == true && !ExcludeTypes.Contains(_.PropertyType.FullName)).ToList().ForEach(property =>
        {
            try
            {
                var nObj = property.GetValue(obj);
                if (nObj != null)
                {
                    var sObj = SanitizeObject(nObj, property.PropertyType);
                    property.SetValue(obj, sObj);
                }
            }
            catch(Exception ex)
            {   
            }
        });

        return obj;
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (encoding == null)
        {
            throw new ArgumentNullException(nameof(encoding));
        }

        using (var streamReader = context.ReaderFactory(context.HttpContext.Request.Body, encoding))
        {
            string jsonData = await streamReader.ReadToEndAsync();
            var nObj = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonData, context.ModelType);
            var modelType = context.ModelType;

            try
            {
                var sbj = SanitizeObject(nObj, modelType);

                return await InputFormatterResult.SuccessAsync(sbj);
            }catch (Exception ex)
            {
                return await InputFormatterResult.FailureAsync();
            }
        }
    }
}

And we declare that in public void ConfigureServices(IServiceCollection services) function of the startup.cs class, like this:

services.AddMvcCore(options => { options.InputFormatters.Add(new SanitizeTextInputFormatter()); })

Preventing XSS in ASP.NET Made Easy, Almost all cases of XSS vulnerabilities originate from user input and some would We can see here that an attack's user input was able to be injected, persisted, NET MVC and Web API alike through the Data Annotation library. There are 3 steps to get up and running with AntiXSS library in your ASP. Client projects' HTML page uses jquery ajax calls to fetch data from REST API using json or xml format. My question is to avoid XSS attacks on a web page, where should i perform encoding of user input data (i.e. untrusted data)? Currently i am encoding data into my REST Controller before returning the response.

The DefaultModelBinder is in the System.Web.ModelBinding namespace, which is used by MVC controllers.

For a WebAPI project, you need to implement the System.Web.Http.ModelBinding.IModelBinder interface.

A sample model binder, taken right from the MSDN site follows:

public class GeoPointModelBinder : IModelBinder
{
    // List of known locations.
    private static ConcurrentDictionary<string, GeoPoint> _locations
        = new ConcurrentDictionary<string, GeoPoint>(StringComparer.OrdinalIgnoreCase);

    static GeoPointModelBinder()
    {
        _locations["redmond"] = new GeoPoint() { Latitude = 47.67856, Longitude = -122.131 };
        _locations["paris"] = new GeoPoint() { Latitude = 48.856930, Longitude = 2.3412 };
        _locations["tokyo"] = new GeoPoint() { Latitude = 35.683208, Longitude = 139.80894 };
    }

    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType != typeof(GeoPoint))
        {
            return false;
        }

        ValueProviderResult val = bindingContext.ValueProvider.GetValue(
            bindingContext.ModelName);
        if (val == null)
        {
            return false;
        }

        string key = val.RawValue as string;
        if (key == null)
        {
            bindingContext.ModelState.AddModelError(
                bindingContext.ModelName, "Wrong value type");
            return false;
        }

        GeoPoint result;
        if (_locations.TryGetValue(key, out result) || GeoPoint.TryParse(key, out result))
        {
            bindingContext.Model = result;
            return true;
        }

        bindingContext.ModelState.AddModelError(
            bindingContext.ModelName, "Cannot convert value to Location");
        return false;
    }
}

A full post that backs up this sample can be found here: MSDN Model Binding

Prevent Cross-Site Scripting (XSS) in ASP.NET Core, How to prevent XSS in ASP. Almost all cases of XSS vulnerabilities originate from user input and some would Example scenario of an XSS attack on a victim NET MVC and Web API alike through the Data Annotation library. If attempting to manually sanitize data against a home grown blacklist can� In my last article, I spoke about several common mistakes that show up in web applications. Of these, the one that causes the most trouble is insufficient input validation/sanitization. In this article, I&rsquo;m joined by my colleague Peter (evilops) Ellehauge in looking at input filtering in more depth while picking on a few real examples that we&rsquo;ve seen around the web.

Preventing XSS in ASP.NET Made Easy, Sanitizing input gives people a false sense of security since there are so many to preserve the correct data, but also to protect against SQL injection attacks. 2340939403424 different types of input sanitation are needed, and prevent a lot � Validating input is the process of ensuring an application is rendering the correct data and preventing malicious data from doing harm to the site, database, and users. While whitelisting and input validation are more commonly associated with SQL injection , they can also be used as an additional method of prevention for XSS.

XSS via JSON: Why does a web application not sanitize either its , A stored XSS attack happens when the cross-site scripting payload has results and user inputs where data is reflected back on the website. Use a cross-site scripting sanitizing library such as AntiXSS and use it correctly. For example, a perpetrator can inject a malicious script into a vulnerable API, i.e., one that fails to perform proper filter input, escape output (FIEO), to launch an XSS attack targeting end users’ browsers. Additionally, malicious commands could be inserted into an API message, such as an SQL command that deletes tables from a database.

5 Tips for Preventing Cross-Site Scripting (XSS) Vulnerabilities in , NET Wizard; c# - how to sanitize input data in web api using anti xss attack; xss - Sanitize HTML before storing in the DB or before rendering? (AntiXSS library in� We can easily add data validation rules to models in ASP.NET MVC and Web API alike through the Data Annotation library. This allows us to be expressive in what our expectations are for the user input. For the most part, most data annotations are going to apply a whitelisting approach naturally.

Comments
  • Theres an interessting post about this topic at StackExchange: security.stackexchange.com/questions/108559/…
  • I'm trying your code at the attribute level and I don't know if I'm missing something but instead of returning JSON, it returns XML. Any ideas how to return it back as JSON once it has been sanitized?
  • @Thierry - sounds like you have both the XML and JSON formatters configured. You can either remove the XML formatter if you don't need it (similar to what I did above re: removing the default JSON formatter), or you could Insert the new JSON formatter at position 0, so that it is hit first, (ie: config.formatters.Insert(0, new SanitizingJsonMediaTypeFormatter())
  • It didn't do anything, so I removed the default JsonInputFormatter before adding this one. Result is getting a 415 Unsupported Media Type.
  • So I found out why it's not working, maybe it's good to mention that the SanitizePropertyAttribute is required to be added to properties which you want to sanitize :-) Instead you mention "Sanitize Recursively all Properties", which isn't correct.