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:
-
The default port may be different when you run the project. So change the port accordingly.
-
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.

