【问题标题】:AntiXSS in ASP.Net CoreASP.Net Core 中的 AntiXSS
【发布时间】:2016-10-21 18:18:26
【问题描述】:

Microsoft Web Protection Library (AntiXSS) 已达到使用寿命。该页面指出“在 .NET 4.0 中,框架中包含一个版本的 AntiXSS,可以通过配置启用。在 ASP.NET v5 中,基于白名单的编码器将是唯一的编码器。”

我有一个经典的跨站点脚本场景:一个 ASP.Net Core 解决方案,用户可以使用所见即所得的 html 编辑器编辑文本。结果显示给其他人查看。这意味着如果用户在保存文本时将 JavaScript 注入到他们提交的数据中,则该代码可以在其他人访问该页面时执行。

我希望能够将某些 HTML 代码(安全代码)列入白名单,但去除不良代码。

我该怎么做?我在 ASP.Net Core RC2 中找不到任何方法来帮助我。这个白名单编码器在哪里?我该如何调用它?例如,我需要清理通过 JSON WebAPI 返回的输出。

【问题讨论】:

    标签: xss asp.net-core-1.0


    【解决方案1】:

    dot.net 核心社区对此有一个 wiki。

    您可以在控制器级别(在构造函数中)注入编码器或引用System.Text.Encodings.Web

    更多信息可以在这里看到:

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

    【讨论】:

      【解决方案2】:

      为了执行自动 Xss 检查,旧的 MVC 使用了在 System.Web.CrossSiteScriptingValidation 类中实现的逻辑。然而,这个类在 ASP.NET CORE 1 中不存在。所以,为了重用它,我复制了它的代码:

      System.Web.CrossSiteScriptingValidation 类

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

      然后,为了对所有请求使用上述类,我创建了一个使用 CrossSiteScriptingValidation 类的中间件:

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

      【讨论】:

      • 你拯救了我的一天。
      • 最好使用其他答案中建议的推荐库,而不是复制和粘贴不支持的代码。
      【解决方案3】:

      如果您真的希望净化输入,即只允许特定的 HTML 元素集,那么简单地对内容进行编码并没有多大帮助。你需要一个 HTML sanitizer。

      构建这样的东西绝非易事。您将需要一些方法来解析 HTML 和一组关于允许传递和不允许传递的规则。为了防止未来新的 HTML 标记导致安全问题,我建议采用白名单方法。

      至少有两个开源 HTML 卫生库可以在 .NET Core 上运行,其中一个是我多年前编写的。两者都可以作为 NuGet 包使用:

      他们使用不同的 HTML 解析作为后端。您可能需要稍微调整规则集以匹配您所见即所得编辑器创建的内容。

      【讨论】:

      【解决方案4】:

      您可以在 .NET Standard 中使用 System.Text.Encodings.Web 进行编程编码。它提供 HTML、JavaScript 和 URL 编码器。应该相当于AntiXss,因为使用白名单是documented

      默认情况下,编码器使用限制在基本拉丁 Unicode 范围内的安全列表,并将该范围之外的所有字符编码为其字符代码等效项。

      【讨论】:

        【解决方案5】:

        听起来您需要某种基于白名单的消毒剂。 OWASP AntiSamy.NET 曾经这样做,但我认为它不再被维护了。 如果始终将数据传递到 JSON,您还可以在客户端通过 DOMPurify 运行,然后再将其添加到 DOM。在 JSON 本身中包含恶意 HTML 并不是那么危险(至少只要您正确设置了 content-type 和 X-content-type-options: nosniff 标头)。代码在渲染到 DOM 之前不会触发。

        【讨论】:

        • 除了一些电子邮件(我可以在其中作弊并删除所有 ]+>),我可以通过 DOMPurify 之类的东西运行所有内容。但是不要通过 JSON 运行所有内容,有时它也直接写入页面。将每一个 @Html.Raw(Model.Foobar) 替换为 document.write(DOMPurify(UnBase64(@Html.Raw(Base64(Model.Foobar))));脚本>。 (必须对数据进行 base64 编码,因为其中的任何 标签都将由 HTML 解析器执行,而 HtmlEncode 会禁用您想要保留的标签。+ 一些 utf8 问题。)
        【解决方案6】:

        这是一个很好的问题。我想指出的一件事是,我们永远不应该尝试制造自己的消毒剂。他们很难做到正确。最好使用由知名作者构建和维护的库。

        From OWASP:“OWASP 建议使用以安全为中心的编码库,以确保正确实施这些规则。”

        如果您使用的是 .NET Framework,此库可能仍然适用: https://docs.microsoft.com/en-us/dotnet/api/system.web.security.antixss.antixssencoder?view=netframework-4.8

        对于 .NET Core,上面评论的 System.Text.Encodings 库也可能有所帮助。 https://docs.microsoft.com/en-us/aspnet/core/security/cross-site-scripting?view=aspnetcore-2.2#accessing-encoders-in-code

        【讨论】:

          【解决方案7】:

          有一个AntiXSS.NetStandard.HtmlSanitization 包,它是Microsoft Web Protection Library (AntiXSS) 到.NET Standard 2.0 的非官方端口。

          来源:https://github.com/hangy/AntiXss

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-03-26
            • 2011-03-16
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-01-11
            相关资源
            最近更新 更多