Microsoft Web Protection Library (AntiXSS) has reached End of Life. The page states \"In .NET 4.0 a version of AntiXSS was included in the framework and could be enabled via
To execute automatic Xss check, the old MVC used the logic implemented in the System.Web.CrossSiteScriptingValidation class. However this class is not present in ASP.NET CORE 1. So, to reuse it I copied its code:
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
public static class CrossSiteScriptingValidation
{
private static readonly char[] StartingChars = { '<', '&' };
#region Public methods
// Only accepts http: and https: protocols, and protocolless urls.
// Used by web parts to validate import and editor input on Url properties.
// Review: is there a way to escape colon that will still be recognized by IE?
// %3a does not work with IE.
public static bool IsDangerousUrl(string s)
{
if (string.IsNullOrEmpty(s))
{
return false;
}
// Trim the string inside this method, since a Url starting with whitespace
// is not necessarily dangerous. This saves the caller from having to pre-trim
// the argument as well.
s = s.Trim();
var len = s.Length;
if ((len > 4) &&
((s[0] == 'h') || (s[0] == 'H')) &&
((s[1] == 't') || (s[1] == 'T')) &&
((s[2] == 't') || (s[2] == 'T')) &&
((s[3] == 'p') || (s[3] == 'P')))
{
if ((s[4] == ':') || ((len > 5) && ((s[4] == 's') || (s[4] == 'S')) && (s[5] == ':')))
{
return false;
}
}
var colonPosition = s.IndexOf(':');
return colonPosition != -1;
}
public static bool IsValidJavascriptId(string id)
{
return (string.IsNullOrEmpty(id) || System.CodeDom.Compiler.CodeGenerator.IsValidLanguageIndependentIdentifier(id));
}
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
}
Than,in order to use the above class for all requests, I created a Middleware that use CrossSiteScriptingValidation class:
public class AntiXssMiddleware
{
private readonly RequestDelegate _next;
private readonly AntiXssMiddlewareOptions _options;
public AntiXssMiddleware(RequestDelegate next, AntiXssMiddlewareOptions options)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
_next = next;
_options = options;
}
public async Task Invoke(HttpContext context)
{
// Check XSS in URL
if (!string.IsNullOrWhiteSpace(context.Request.Path.Value))
{
var url = context.Request.Path.Value;
int matchIndex;
if (CrossSiteScriptingValidation.IsDangerousString(url, out matchIndex))
{
if (_options.ThrowExceptionIfRequestContainsCrossSiteScripting)
{
throw new CrossSiteScriptingException(_options.ErrorMessage);
}
context.Response.Clear();
await context.Response.WriteAsync(_options.ErrorMessage);
return;
}
}
// Check XSS in query string
if (!string.IsNullOrWhiteSpace(context.Request.QueryString.Value))
{
var queryString = WebUtility.UrlDecode(context.Request.QueryString.Value);
int matchIndex;
if (CrossSiteScriptingValidation.IsDangerousString(queryString, out matchIndex))
{
if (_options.ThrowExceptionIfRequestContainsCrossSiteScripting)
{
throw new CrossSiteScriptingException(_options.ErrorMessage);
}
context.Response.Clear();
await context.Response.WriteAsync(_options.ErrorMessage);
return;
}
}
// Check XSS in request content
var originalBody = context.Request.Body;
try
{
var content = await ReadRequestBody(context);
int matchIndex;
if (CrossSiteScriptingValidation.IsDangerousString(content, out matchIndex))
{
if (_options.ThrowExceptionIfRequestContainsCrossSiteScripting)
{
throw new CrossSiteScriptingException(_options.ErrorMessage);
}
context.Response.Clear();
await context.Response.WriteAsync(_options.ErrorMessage);
return;
}
await _next(context);
}
finally
{
context.Request.Body = originalBody;
}
}
private static async Task 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 contentType = context.Request.GetTypedHeaders().ContentType;
if (contentType?.Charset != null) encoding = Encoding.GetEncoding(contentType.Charset);
var requestContent = await new StreamReader(buffer, encoding).ReadToEndAsync();
context.Request.Body.Position = 0;
return requestContent;
}
}