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 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.
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.
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