Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AntiXSS in ASP.Net Core

Tags:

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 configuration. In ASP.NET v5 a white list based encoder will be the only encoder."

I have a classic cross site scripting scenario: An ASP.Net Core solution where users can edit text using a WYSIWYG html-editor. The result is displayed for others to see. This means that if users inject a JavaScript into the data they submit when saving the text this code could execute when others visits the page.

I want to be able to whitelist certain HTML-codes (safe ones), but strip out bad codes.

How do I do this? I can't find any methods in ASP.Net Core RC2 to help me. Where is this white list encoder? How do I invoke it? For example I would need to clean output being returned via JSON WebAPI.

like image 690
Tedd Hansen Avatar asked Jun 20 '16 13:06

Tedd Hansen


People also ask

What is the use of AntiXSS library?

AntiXSS is an encoding library which uses a safe list approach to encoding. It provides Html, XML, Url, Form, LDAP, CSS, JScript and VBScript encoding methods to allow you to avoid Cross Site Scripting attacks. This library is part of the Microsoft SDL tools.

What is cross-site scripting in asp net?

Cross-Site Scripting (XSS) is a security vulnerability which enables an attacker to place client side scripts (usually JavaScript) into web pages.

What is anti XSS?

At present this mode enables an automatic data encoding strategy designed to reduce XSS exploits arising from the incorrect encoding of data embedded in HTML templates. This mechanism does not encode HTML output that plugins generate outside of Velocity templates.

Which feature of ASP NET helps in mitigating the XSS attack?

Manual Validation We just looked at an out-of-the-box feature that ASP.NET provides for validating user input that can be an extremely helpful mitigation in a defense in-depth strategy.


2 Answers

The dot.net core community has a wiki on this.

You can inject encoders at a controller level (in the constructor) or reference System.Text.Encodings.Web.

More info can be seen here:

https://docs.microsoft.com/en-us/aspnet/core/security/cross-site-scripting

like image 120
Darxtar Avatar answered Sep 18 '22 08:09

Darxtar


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:

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'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:

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;
    }
}
like image 24
Christian Del Bianco Avatar answered Sep 20 '22 08:09

Christian Del Bianco