To perform an automatic Xss check, the old MVC used the logic implemented in the System.Web.CrossSiteScriptingValidation class. However, this class is missing from ASP.NET CORE 1. Therefore, to reuse it, I copied its code:
System.Web.CrossSiteScriptingValidation class
// <copyright file="CrossSiteScriptingValidation.cs" company="Microsoft"> // Copyright (c) Microsoft Corporation. All rights reserved. // </copyright> 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 the last char, it safe if (n == s.Length - 1) return false; matchIndex = n; switch (s[n]) { case '<': // If the < is followed by a letter or '!', it 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
Than to use the above class for all requests, I created Middleware that use the CrossSiteScriptingValidation class:
AntiXssMiddleware
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<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 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; } }
Christian del bianco
source share