Implement AntiXssMiddleware in .NET Core Web

profile
Hemant ManwaniFirst published: 2020-08-26Last updated: 2025-06-25
anti-xss-middleware-asp-core

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.

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

1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Linq;
5using System.Net;
6using System.Text;
7using System.Text.RegularExpressions;
8using System.Threading.Tasks;
9using Microsoft.AspNetCore.Builder;
10using Microsoft.AspNetCore.Http;
11using Newtonsoft.Json;
12namespace AntiXssMiddleware.Middleware
13{
14public class AntiXssMiddleware
15{
16private readonly RequestDelegate _next;
17private ErrorResponse _error;
18private readonly int _statusCode = (int)HttpStatusCode.BadRequest;
19    public AntiXssMiddleware(RequestDelegate next)
20    {
21        _next = next ?? throw new ArgumentNullException(nameof(next));
22    }
23
24    public async Task Invoke(HttpContext context)
25    {
26        // Check XSS in URL
27            if (!string.IsNullOrWhiteSpace(context.Request.Path.Value))
28            {
29                var url = context.Request.Path.Value;
30
31                if (CrossSiteScriptingValidation.IsDangerousString(url, out _))
32                {
33                    await RespondWithAnError(context).ConfigureAwait(false);
34                    return;
35                }
36            }
37
38            // Check XSS in query string
39            if (!string.IsNullOrWhiteSpace(context.Request.QueryString.Value))
40            {
41                var queryString = WebUtility.UrlDecode(context.Request.QueryString.Value);
42
43                if (CrossSiteScriptingValidation.IsDangerousString(queryString, out _))
44                {
45                    await RespondWithAnError(context).ConfigureAwait(false);
46                    return;
47                }
48            }
49
50            // Check XSS in request content
51            var originalBody = context.Request.Body;
52            try
53            {
54                var content = await ReadRequestBody(context);
55
56                if (CrossSiteScriptingValidation.IsDangerousString(content, out _)) 
57                {
58                        await RespondWithAnError(context).ConfigureAwait(false);
59                        return;
60                }
61                await _next(context).ConfigureAwait(false);
62            }
63            finally
64            {
65                context.Request.Body = originalBody;
66            }
67    }
68
69    private static async Task<string> ReadRequestBody(HttpContext context)
70    {
71        var buffer = new MemoryStream();
72        await context.Request.Body.CopyToAsync(buffer);
73        context.Request.Body = buffer;
74        buffer.Position = 0;
75
76        var encoding = Encoding.UTF8;
77
78        var requestContent = await new StreamReader(buffer, encoding).ReadToEndAsync();
79        context.Request.Body.Position = 0;
80
81        return requestContent;
82    }
83
84    private async Task RespondWithAnError(HttpContext context)
85    {
86        context.Response.Clear();
87        context.Response.Headers.AddHeaders();
88        context.Response.ContentType = "application/json; charset=utf-8";
89        context.Response.StatusCode = _statusCode;
90
91        if (_error == null)
92        {
93            _error = new ErrorResponse
94            {
95                Description = "Error from AntiXssMiddleware",
96                ErrorCode = 500
97            };
98        }
99
100        await context.Response.WriteAsync(_error.ToJSON());
101    }
102}
103
104public static class AntiXssMiddlewareExtension
105{
106    public static IApplicationBuilder UseAntiXssMiddleware(this IApplicationBuilder builder)
107    {
108        return builder.UseMiddleware<AntiXssMiddleware>();
109    }
110}
111
112
113/// <summary>
114/// Imported from System.Web.CrossSiteScriptingValidation Class
115/// </summary>
116public static class CrossSiteScriptingValidation
117{
118    private static readonly char[] StartingChars = { '<', '&' };
119
120    #region Public methods
121
122    public static bool IsDangerousString(string s, out int matchIndex)
123    {
124        //bool inComment = false;
125        matchIndex = 0;
126
127        for (var i = 0; ;)
128        {
129
130            // Look for the start of one of our patterns 
131            var n = s.IndexOfAny(StartingChars, i);
132
133            // If not found, the string is safe
134            if (n < 0) return false;
135
136            // If it's the last char, it's safe 
137            if (n == s.Length - 1) return false;
138
139            matchIndex = n;
140
141            switch (s[n])
142            {
143                case '<':
144                    // If the < is followed by a letter or '!', it's unsafe (looks like a tag or HTML comment)
145                    if (IsAtoZ(s[n + 1]) || s[n + 1] == '!' || s[n + 1] == '/' || s[n + 1] == '?') return true;
146                    break;
147                case '&':
148                    // If the & is followed by a #, it's unsafe (e.g. S) 
149                    if (s[n + 1] == '#') return true;
150                    break;
151
152            }
153
154            // Continue searching
155            i = n + 1;
156        }
157    }
158
159    #endregion
160
161    #region Private methods
162
163    private static bool IsAtoZ(char c)
164    {
165        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
166    }
167
168    #endregion
169
170    public static void AddHeaders(this IHeaderDictionary headers)
171    {
172        if (headers["P3P"].IsNullOrEmpty())
173        {
174            headers.Add("P3P", "CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"");
175        }
176    }
177
178    public static bool IsNullOrEmpty<T>(this IEnumerable<T> source)
179    {
180        return source == null || !source.Any();
181    }
182    public static string ToJSON(this object value)
183    {
184        return JsonConvert.SerializeObject(value);
185    }
186}
187
188public class ErrorResponse
189{
190    public int ErrorCode { get; set; }
191    public string Description { get; set; }
192}
193
194}

In the above file we have created the method for checking the Xss in QueryParam, RequestUri and RequestBody.

Here we have different methods which are as follows:-

ReadRequestBody which is used for reading the RequestBody.

RespondWithAnError which is used for returning the error.

IsDangerousString which is checking if there is any dangerous string like any script in the given string.

Step 7: Edit the Startup.cs file and add below line in Configure method.

1app.UseAntiXssMiddleware();

Step 8 : After editing the Startup.cs file will look like below

1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Threading.Tasks;
5using AntiXssMiddleware.Middleware;
6using Microsoft.AspNetCore.Builder;
7using Microsoft.AspNetCore.Hosting;
8using Microsoft.AspNetCore.HttpsPolicy;
9using Microsoft.AspNetCore.Mvc;
10using Microsoft.Extensions.Configuration;
11using Microsoft.Extensions.DependencyInjection;
12using Microsoft.Extensions.Hosting;
13using Microsoft.Extensions.Logging;
14namespace AntiXssMiddleware
15{
16public class Startup
17{
18public Startup(IConfiguration configuration)
19{
20Configuration = configuration;
21}
22    public IConfiguration Configuration { get; }
23
24    // This method gets called by the runtime. Use this method to add services to the container.
25    public void ConfigureServices(IServiceCollection services)
26    {
27        services.AddControllers();
28    }
29
30    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
31    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
32    {
33        if (env.IsDevelopment())
34        {
35            app.UseDeveloperExceptionPage();
36        }
37
38        app.UseHttpsRedirection();
39        app.UseAntiXssMiddleware();
40        app.UseRouting();
41        app.UseAuthorization();
42        app.UseEndpoints(endpoints =>
43        {
44            endpoints.MapControllers();
45        });
46    }
47}
48
49}

Step 9: Now build and run the solution.

As we run the default API which is https://localhost:44369/weatherforecast we will get the below response.

1[
2    {
3        "date": "2020-08-21T11:58:40.0289718+05:30",
4        "temperatureC": 27,
5        "temperatureF": 80,
6        "summary": "Sweltering"
7    },
8    {
9        "date": "2020-08-22T11:58:40.0289896+05:30",
10        "temperatureC": 21,
11        "temperatureF": 69,
12        "summary": "Cool"
13    },
14    {
15        "date": "2020-08-23T11:58:40.0289899+05:30",
16        "temperatureC": -20,
17        "temperatureF": -3,
18        "summary": "Hot"
19    },
20    {
21        "date": "2020-08-24T11:58:40.0289901+05:30",
22        "temperatureC": 21,
23        "temperatureF": 69,
24        "summary": "Sweltering"
25    },
26    {
27        "date": "2020-08-25T11:58:40.0289902+05:30",
28        "temperatureC": 2,
29        "temperatureF": 35,
30        "summary": "Balmy"
31    }
32]

Now if we inject any script in the above url like https://localhost:44369/weatherforecast<script></script> we will get the response as

1{
2    "ErrorCode": 500,
3    "Description": "Error from AntiXssMiddleware"
4}

Note:

  1. The default port may be different when you run the project. So change the port accordingly.

  2. You can customize the error message according to your need.

Conclusion

In this blog, we learnt about how to implement AntiXssMiddlware in ASP.NET Core Web Application Project. We have implemented the AntiXssMiddleware in API's QueryParam, ReuqestUri and RequestBody. So if any script is injected in QueryParam, RequestUri or RequestBody then it will give the error.

Share On:
Share on TwitterShare on LinkedIn