防止跨站点脚本 (XSS) 在 ASP.NET CorePrevent Cross-Site Scripting (XSS) in ASP.NET Core

本文内容

作者:Rick Anderson

跨站点脚本(XSS)是一个安全漏洞,攻击者可利用此漏洞将客户端脚本(通常为 JavaScript)放入网页中。当其他用户加载受影响的页面时,攻击者的脚本将运行,使攻击者能够盗取 cookie 和会话令牌,通过 DOM 操作更改网页的内容,或者将浏览器重定向到另一页。当应用程序采用用户输入并将其输出到页面而不进行验证、编码或转义时,通常会出现 XSS 漏洞。

针对 XSS 保护应用程序Protecting your application against XSS

在基本级别,XSS 通过引诱你的应用程序将 <script> 标记插入所呈现的页中,或通过将 On* 事件插入到元素中来发挥作用。开发人员应使用以下预防步骤来避免向其应用程序引入 XSS。

  • 请勿将不受信任的数据放入 HTML 输入,除非您按照以下步骤进行操作。不受信任的数据是指攻击者可以控制的任何数据、HTML 窗体输入、查询字符串、HTTP 标头,甚至是源自数据库的数据,即使它们不能破坏您的应用程序,也可能会破坏您的数据库。

  • 在 HTML 元素中放置不受信任的数据之前,请确保它经过 HTML 编码。HTML 编码使用 < 等字符,并将其更改为 < 格式的安全窗体。

  • 将不受信任的数据放入 HTML 属性之前,请确保已对其进行 HTML 编码。HTML 特性编码是 HTML 编码的超集,并对其他字符(如 "and")进行编码。

  • 将不受信任的数据放入 JavaScript 之前,请将数据放置在运行时检索其内容的 HTML 元素中。如果无法做到这一点,请确保数据为 JavaScript 编码。JavaScript 编码会为 JavaScript 使用危险字符,并将其替换为其十六进制,例如 < 将编码为 \u003C

  • 将不受信任的数据置于 URL 查询字符串之前,请确保其 URL 已编码。

使用 Razor 的 HTML 编码HTML Encoding using Razor

MVC 中使用的 Razor 引擎会自动对源自变量的所有输出进行编码,除非您确实很难避免这样做。当你使用 @ 指令时,它将使用 HTML 属性编码规则。HTML 特性编码是 HTML 编码的超集,这意味着您无需担心您应该使用 HTML 编码还是 HTML 特性编码。您必须确保在 HTML 上下文中只使用 @,而不能在尝试将不受信任的输入直接插入 JavaScript 时使用。标记帮助程序还将对在标记参数中使用的输入进行编码。

获取以下 Razor 视图:

  1. @{
  2. var untrustedInput = "<\"123\">";
  3. }
  4. @untrustedInput

此视图输出untrustedInput变量的内容。此变量包含某些在 XSS 攻击中使用的字符,即 <"和 >。检查源会显示编码为的呈现输出:

  1. &lt;&quot;123&quot;&gt;

警告

ASP.NET Core MVC 提供的 HtmlString 类在输出时不会自动编码。请勿将此项与不受信任的输入结合使用,因为这将公开 XSS 漏洞。

使用 Razor 的 JavaScript 编码JavaScript Encoding using Razor

有时可能需要将值插入 JavaScript 中,以便在视图中进行处理。可通过两种方式来执行此操作。插入值的最安全方式是将值放入标记的数据属性中,并在 JavaScript 中检索它。例如:

  1. @{
  2. var untrustedInput = "<\"123\">";
  3. }
  4. <div
  5. id="injectedData"
  6. data-untrustedinput="@untrustedInput" />
  7. <script>
  8. var injectedData = document.getElementById("injectedData");
  9. // All clients
  10. var clientSideUntrustedInputOldStyle =
  11. injectedData.getAttribute("data-untrustedinput");
  12. // HTML 5 clients only
  13. var clientSideUntrustedInputHtml5 =
  14. injectedData.dataset.untrustedinput;
  15. document.write(clientSideUntrustedInputOldStyle);
  16. document.write("<br />")
  17. document.write(clientSideUntrustedInputHtml5);
  18. </script>

这会生成以下 HTML

  1. <div
  2. id="injectedData"
  3. data-untrustedinput="&lt;&quot;123&quot;&gt;" />
  4. <script>
  5. var injectedData = document.getElementById("injectedData");
  6. var clientSideUntrustedInputOldStyle =
  7. injectedData.getAttribute("data-untrustedinput");
  8. var clientSideUntrustedInputHtml5 =
  9. injectedData.dataset.untrustedinput;
  10. document.write(clientSideUntrustedInputOldStyle);
  11. document.write("<br />")
  12. document.write(clientSideUntrustedInputHtml5);
  13. </script>

当它运行时,将呈现以下内容:

  1. <"123">
  2. <"123">

还可以直接调用 JavaScript 编码器:

  1. @using System.Text.Encodings.Web;
  2. @inject JavaScriptEncoder encoder;
  3. @{
  4. var untrustedInput = "<\"123\">";
  5. }
  6. <script>
  7. document.write("@encoder.Encode(untrustedInput)");
  8. </script>

这将在浏览器中呈现,如下所示:

  1. <script>
  2. document.write("\u003C\u0022123\u0022\u003E");
  3. </script>

警告

请勿连接 JavaScript 中不受信任的输入来创建 DOM 元素。应使用 createElement() 并适当地分配属性值(如 node.TextContent=),或使用 element.SetAttribute()/element[attribute]= 否则,你会自行向基于 DOM 的 XSS 公开。

在代码中访问编码器Accessing encoders in code

HTML、JavaScript 和 URL 编码器通过两种方式提供给你的代码,你可以通过依赖关系注入来注入它们,也可以使用 System.Text.Encodings.Web 命名空间中包含的默认编码器。如果使用默认编码器,则应用于字符范围的任何被视为安全的都不会生效-默认编码器可能会使用最安全的编码规则。

若要通过 DI 使用可配置编码器,你的构造函数应适当地采用HtmlEncoderJavaScriptEncoderUrlEncoder参数。例如,

  1. public class HomeController : Controller
  2. {
  3. HtmlEncoder _htmlEncoder;
  4. JavaScriptEncoder _javaScriptEncoder;
  5. UrlEncoder _urlEncoder;
  6. public HomeController(HtmlEncoder htmlEncoder,
  7. JavaScriptEncoder javascriptEncoder,
  8. UrlEncoder urlEncoder)
  9. {
  10. _htmlEncoder = htmlEncoder;
  11. _javaScriptEncoder = javascriptEncoder;
  12. _urlEncoder = urlEncoder;
  13. }
  14. }

编码 URL 参数Encoding URL Parameters

如果要使用不受信任的输入生成 URL 查询字符串作为值,请使用 UrlEncoder 对值进行编码。例如,

  1. var example = "\"Quoted Value with spaces and &\"";
  2. var encodedValue = _urlEncoder.Encode(example);

编码后,Url-encodedvalue 变量将包含 %22Quoted%20Value%20with%20spaces%20and%20%26%22空格、引号、标点符号和其他不安全字符的百分比将编码为其十六进制值,例如,空格字符将变为 %20。

警告

请勿使用不受信任的输入作为 URL 路径的一部分。始终将不受信任的输入传递为查询字符串值。

自定义编码器Customizing the Encoders

默认情况下,编码器使用限制为基本拉丁语 Unicode 范围的安全列表,并将该范围之外的所有字符编码为等效的字符代码。此行为还会影响 Razor TagHelper 和 HtmlHelper 渲染,因为它将使用编码器输出字符串。

这种情况的原因是为了防止未知或将来的浏览器 bug (以前的浏览器 bug 基于非英语字符的处理来触发分析)。如果你的网站大量使用非拉丁字符(如中文、西里尔语或其他),这可能不是你所希望的行为。

ConfigureServices()中,你可以自定义编码器安全列表,以包含在启动过程中适用于你的应用程序的 Unicode 范围。

例如,使用默认配置时,你可以使用 Razor HtmlHelper,如下所示;

  1. <p>This link text is in Chinese: @Html.ActionLink("汉语/漢語", "Index")</p>

当您查看网页的源时,您将看到它的呈现方式如下:中文文本已编码;

<p>This link text is in Chinese: <a href="/">&#x6C49;&#x8BED;/&#x6F22;&#x8A9E;</a></p>

若要放宽编码器被视为安全字符,请将以下行插入到 startup.cs中的 ConfigureServices() 方法中:

services.AddSingleton<HtmlEncoder>(
     HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
                                               UnicodeRanges.CjkUnifiedIdeographs }));

此示例将安全列表扩大为包含 Unicode 范围 CjkUnifiedIdeographs。呈现的输出现在变为

<p>This link text is in Chinese: <a href="/">汉语/漢語</a></p>

安全列表范围指定为 Unicode 代码图表,而不是语言。Unicode 标准包含可用来查找包含字符的图表的代码图表列表。每个编码器、Html、JavaScript 和 Url 都必须单独配置。

备注

安全列表的自定义仅影响通过 DI 的编码器。如果通过 System.Text.Encodings.Web.*Encoder.Default 直接访问编码器,则默认情况下将使用仅限基本拉丁语的唯一安全。

编码应发生在何处?Where should encoding take place?

一般接受的做法是,在输出和编码值中进行编码时,永远不应将其存储在数据库中。使用输出点编码,可以更改数据的使用,例如,从 HTML 到查询字符串值。它还使您能够轻松搜索您的数据,而无需在搜索之前对值进行编码,还可以利用对编码器进行的任何更改或 bug 修复。

验证为 XSS 保护方法Validation as an XSS prevention technique

验证是限制 XSS 攻击的有用工具。例如,仅包含字符0-9 的数字字符串将不会触发 XSS 攻击。在用户输入中接受 HTML 时,验证变得更加复杂。分析 HTML 输入很困难,如果不可能。Markdown 与带嵌入 HTML 的分析器结合使用,是接受丰富输入的一种更安全的选项。请勿仅依赖于验证。在输出之前始终对不受信任的输入进行编码,无论执行了哪些验证或清理。