In this blog, we learn how to implement the AntiXssMiddleware in .NET Core. First, we will understand about the cross-site scripting.

Cross-Site Scripting(XSS)

Cross-site scripting is a security vulnerability and a client-side code injection attack. In this attack, the malicious script is injected into legitimate websites. Cross-site scripting allows an attacker to act like a victim user and to carry out the actions that the user can perform. The attacker can access the user’s data as well.

Implement AntiXssMiddleware in .NET Core

Step 1: Create Asp.NET Core Web Application project in Visual Studio.

Step 2: Select type as API in the next step and create the project. You will find a default controller which is created in the controller folder named as WeatherForecastController.cs

Step 3: Now create a new folder named Middleware in the root directory.

Step 4 : Create a new file AntiXssMiddleware.cs in that Middleware folder.

Step 5: Now add the Newtonsoft.json package into your solution

By doing the above steps you will have below structure in your solution.

Solution Structure

Step 6: Now edit the AntiXssMiddlewars.cs file and paste below code.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;

namespace AntiXssMiddleware.Middleware
{
    public class AntiXssMiddleware
    {
        private readonly RequestDelegate _next;
        private ErrorResponse _error;
        private readonly int _statusCode = (int)HttpStatusCode.BadRequest;

        public AntiXssMiddleware(RequestDelegate next)
        {
            _next = next ?? throw new ArgumentNullException(nameof(next));
        }

        public async Task Invoke(HttpContext context)
        {
            // Check XSS in URL
                if (!string.IsNullOrWhiteSpace(context.Request.Path.Value))
                {
                    var url = context.Request.Path.Value;

                    if (CrossSiteScriptingValidation.IsDangerousString(url, out _))
                    {
                        await RespondWithAnError(context).ConfigureAwait(false);
                        return;
                    }
                }

                // Check XSS in query string
                if (!string.IsNullOrWhiteSpace(context.Request.QueryString.Value))
                {
                    var queryString = WebUtility.UrlDecode(context.Request.QueryString.Value);

                    if (CrossSiteScriptingValidation.IsDangerousString(queryString, out _))
                    {
                        await RespondWithAnError(context).ConfigureAwait(false);
                        return;
                    }
                }

                // Check XSS in request content
                var originalBody = context.Request.Body;
                try
                {
                    var content = await ReadRequestBody(context);

                    if (CrossSiteScriptingValidation.IsDangerousString(content, out _)) 
                    {
                            await RespondWithAnError(context).ConfigureAwait(false);
                            return;
                    }
                    await _next(context).ConfigureAwait(false);
                }
                finally
                {
                    context.Request.Body = originalBody;
                }
        }

        private static async Task<string> ReadRequestBody(HttpContext context)
        {
            var buffer = new MemoryStream();
            await context.Request.Body.CopyToAsync(buffer);
            context.Request.Body = buffer;
            buffer.Position = 0;

            var encoding = Encoding.UTF8;

            var requestContent = await new StreamReader(buffer, encoding).ReadToEndAsync();
            context.Request.Body.Position = 0;

            return requestContent;
        }

        private async Task RespondWithAnError(HttpContext context)
        {
            context.Response.Clear();
            context.Response.Headers.AddHeaders();
            context.Response.ContentType = "application/json; charset=utf-8";
            context.Response.StatusCode = _statusCode;

            if (_error == null)
            {
                _error = new ErrorResponse
                {
                    Description = "Error from AntiXssMiddleware",
                    ErrorCode = 500
                };
            }

            await context.Response.WriteAsync(_error.ToJSON());
        }
    }

    public static class AntiXssMiddlewareExtension
    {
        public static IApplicationBuilder UseAntiXssMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<AntiXssMiddleware>();
        }
    }

    /// <summary>
    /// Imported from System.Web.CrossSiteScriptingValidation Class
    /// </summary>
    public static class CrossSiteScriptingValidation
    {
        private static readonly char[] StartingChars = { '<', '&' };

        #region Public methods

        public static bool IsDangerousString(string s, out int matchIndex)
        {
            //bool inComment = false;
            matchIndex = 0;

            for (var i = 0; ;)
            {

                // Look for the start of one of our patterns 
                var n = s.IndexOfAny(StartingChars, i);

                // If not found, the string is safe
                if (n < 0) return false;

                // If it's the last char, it's safe 
                if (n == s.Length - 1) return false;

                matchIndex = n;

                switch (s[n])
                {
                    case '<':
                        // If the < is followed by a letter or '!', it's unsafe (looks like a tag or HTML comment)
                        if (IsAtoZ(s[n + 1]) || s[n + 1] == '!' || s[n + 1] == '/' || s[n + 1] == '?') return true;
                        break;
                    case '&':
                        // If the & is followed by a #, it's unsafe (e.g. S) 
                        if (s[n + 1] == '#') return true;
                        break;

                }

                // Continue searching
                i = n + 1;
            }
        }

        #endregion

        #region Private methods

        private static bool IsAtoZ(char c)
        {
            return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
        }

        #endregion

        public static void AddHeaders(this IHeaderDictionary headers)
        {
            if (headers["P3P"].IsNullOrEmpty())
            {
                headers.Add("P3P", "CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"");
            }
        }

        public static bool IsNullOrEmpty<T>(this IEnumerable<T> source)
        {
            return source == null || !source.Any();
        }
        public static string ToJSON(this object value)
        {
            return JsonConvert.SerializeObject(value);
        }
    }

    public class ErrorResponse
    {
        public int ErrorCode { get; set; }
        public string Description { get; set; }
    }

}

#.net

Implement AntiXssMiddleware in .NET Core Web
10.10 GEEK