Бросать исключение в атаке Xss - PullRequest
0 голосов
/ 24 октября 2018

Это веб-API, который загружает Json (так что Razor нет).
Я использую ASP.NET Core 2.1

1-я версия. Должен упомянуть, что я санирую соответствующие входные данные с помощью HtmlEncoder.Тем не менее, это на всякий случай, если кто-нибудь пройдет мимо моего валидатора, о котором я хочу спросить здесь.

Я хочу написать валидатор, который будет возвращать код ошибки, когда пользователь пытается включить строку html вinput (используя мобильное приложение, которое было бы свойством в полезной нагрузке json).

Я видел несколько наивных предложений по реализации здесь на SO - обычно просто проверяю, чтобы увидеть строку, содержащую '<' или '> '(и, возможно, один или два других символа).

Думаю, мне хотелось бы знать, достаточно ли этого для поставленной задачи.У пользователя нет причин размещать какие-либо html / xml в этом домене.

Многие библиотеки вокруг будут очищать входные данные.Но, похоже, ни у одного из них нет метода, который бы указывал, содержит ли строка потенциально опасные данные.

Как я уже сказал, я уже дезинфицирую (в качестве последней линии защиты).Но в идеале я бы вернул код ошибки, прежде чем он доберется до этого.

Спасибо

1 Ответ

0 голосов
/ 12 декабря 2018

Используйте этот класс из Microsoft ASP.NET Core 1

// <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
}

Затем используйте это промежуточное ПО для управления URL, параметрами запроса и содержимым:

public class XssMiddleware
{
    private readonly RequestDelegate _next;

    public XssMiddleware(RequestDelegate next)
    {
        if (next == null)
        {
            throw new ArgumentNullException(nameof(next));
        }

        _next = next;
    }       

    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))
            {

                throw new CrossSiteScriptingException("YOUR_ERROR_MESSAGE");
            }
        }

        // 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))
            {

                throw new CrossSiteScriptingException("YOUR_ERROR_MESSAGE");

            }
        }

        // Check XSS in request content
        var originalBody = context.Request.Body;
        try
        {                
            var content = await ReadRequestBody(context);

            int matchIndex;
            if (CrossSiteScriptingValidation.IsDangerousString(content, out matchIndex))
            {
                throw new CrossSiteScriptingException("YOUR_ERROR_MESSAGE");
            }

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