AntiXSS in ASP.Net Core

2020-02-06 05:51发布

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.

6条回答
来,给爷笑一个
2楼-- · 2020-02-06 06:07

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

查看更多
戒情不戒烟
3楼-- · 2020-02-06 06:09

You can use System.Text.Encodings.Web for programmatic encoding in .NET Standard. It offers HTML, JavaScript and URL encoders. It should be equivalent to AntiXss because it is documented to use white list:

By default encoders use a safe list limited to the Basic Latin Unicode range and encode all characters outside of that range as their character code equivalents.

查看更多
做个烂人
4楼-- · 2020-02-06 06:10

If you are truly looking to sanitize the input, that is only allowing a certain set of HTML elements, simply encoding the content is not much help. You need a HTML sanitizer.

Building such a thing is no easy task. You'll need some method to parse the HTML and a set of rules on what to allow to pass and what not. In order to prevent future new HTML tags from causing security issues down the road I recommend to take a white listing approach.

There are at least two open source HTML sanitation libraries out there which work on .NET Core, one of which I wrote a bunch of years ago. Both are available as NuGet packages:

They use different HTML parses as back-ends. You may need to tune the rule sets a bit to match what your WYSIWYG editor creates.

查看更多
何必那么认真
5楼-- · 2020-02-06 06:20

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;
    }
}
查看更多
ら.Afraid
6楼-- · 2020-02-06 06:24

Sounds like you need a whitelist based sanitizer of some sort. OWASP AntiSamy.NET used to do that, but I don't think it's maintained anymore. If data is always delivered to JSON, you could also run in through DOMPurify on the client side, before adding it to the DOM. Having malicious HTML in the JSON itself isn't all that dangerous (at least not as long as you set the content-type and X-content-type-options: nosniff headers correctly). The code will not trigger until it's rendered into the DOM.

查看更多
一纸荒年 Trace。
7楼-- · 2020-02-06 06:29

It is a good question. One thing I want to point out is that we should never try to build our own sanitizer. They are very hard to get right. It is much better to use a library that is built and maintained by a reputable author.

From OWASP: "OWASP recommends using a security-focused encoding library to make sure these rules are properly implemented."

If you are using the .NET Framework, this library might still be suitable: https://docs.microsoft.com/en-us/dotnet/api/system.web.security.antixss.antixssencoder?view=netframework-4.8

For .NET Core, System.Text.Encodings library as commented on above may also help. https://docs.microsoft.com/en-us/aspnet/core/security/cross-site-scripting?view=aspnetcore-2.2#accessing-encoders-in-code

查看更多
登录 后发表回答