ASP.NET MVC 和网页中的 XSRF/CSRF 防护

作者 :Rick Anderson

跨站点请求伪造(也称为 XSRF 或 CSRF)是一种针对 Web 托管型应用程序的攻击,恶意网站凭此可以影响客户端浏览器与受该浏览器信任的网站之间的交互。 这些攻击出现的原因可能是 Web 浏览器针对每一个对网站的请求自动发送身份验证令牌。 典型示例是身份验证 cookie,如 ASP.NET 的表单身份验证票证。 然而,使用任何持久身份验证(如 Windows Authentication、Basic 等)的网站也可能成为受攻击目标。

XSRF 攻击不同于网络钓鱼攻击。 网络钓鱼攻击需要与受害者进行交互。 在网络钓鱼攻击中,恶意网站将仿冒目标网站,受到欺骗的受害者会向攻击者提供敏感信息。 在 XSRF 攻击中,通常不必与受害者进行交互。 相反,浏览器自动向目标网站发送所有相关 cookie 为攻击者提供了可乘之机。

有关详细信息,请参阅 Open Web Application Security Project (OWASP) XSRF

攻击剖析

若要演练 XSRF 攻击,请考虑一个想要执行一些网上银行交易的用户。 此用户首先访问 WoodgroveBank.com 并登录,此时响应标头将包含她的身份验证 Cookie:

HTTP/1.1 200 OK
Date: Mon, 18 Jun 2012 21:22:33 GMT
X-AspNet-Version: 4.0.30319
Set-Cookie: .ASPXAUTH={authentication-token}; path=/; secure; HttpOnly;
{ Cache-Control, Content-Type, Location, Server and other keys/values not listed. }

由于身份验证 Cookie 是会话 Cookie,因此浏览器将在浏览器进程退出时自动清除它。 但是,在该时间之前,浏览器会自动将 cookie 包含在 WoodgroveBank.com 的每个请求中。 用户现在想要将 1000 美元转入另一个帐户,因此她在银行网站上填写了一个表单,浏览器向服务器发出此请求:

POST /DoTransfer HTTP/1.1
Host: WoodgroveBank.com
Content-Type: application/x-www-form-urlencoded
Cookie: .ASPXAUTH={authentication-token}
toAcct=12345&amount=1,000.00

由于此操作 (它启动货币交易) 具有副作用,因此银行站点已选择要求 HTTP POST 才能启动此操作。 服务器从请求中读取身份验证令牌,查找当前用户的帐号,验证是否存在足够的资金,然后将事务启动到目标帐户。

她的网上银行业务完成,用户离开银行网站,访问 Web 上的其他位置。 其中一个网站(fabrikam.com)在 iframe> 中<嵌入的页面上包含以下标记:

<form id="theForm" action="https://WoodgroveBank.com/DoTransfer" method="post">
    <input type="hidden" name="toAcct" value="67890" />
    <input type="hidden" name="amount" value="250.00" />
</form>
<script type="text/javascript">
    document.getElementById('theForm').submit();
</script>

这会导致浏览器发出此请求:

POST /DoTransfer HTTP/1.1
Host: WoodgroveBank.com
Content-Type: application/x-www-form-urlencoded
Cookie: .ASPXAUTH={authentication-token}
toAcct=67890&amount=250.00

攻击者正在利用这样一个事实,即用户可能仍具有目标网站的有效身份验证令牌,并且她正在使用一小段 Javascript 来使浏览器自动向目标站点发出 HTTP POST。 如果身份验证令牌仍然有效,银行站点将发起 250 美元的转入攻击者选择的帐户。

无效的缓解措施

有趣的是,在上述方案中,WoodgroveBank.com 是通过 SSL 访问的,并且具有仅限 SSL 的身份验证 Cookie 这一事实不足以阻止攻击。 攻击者能够在其 <form> 元素中 (https) 指定 URI 方案,只要这些 Cookie 与预期目标的 URI 方案一致,浏览器就会继续向目标站点发送未过期的 Cookie。

人们可能会争辩说,用户根本不应该访问不受信任的站点,因为仅访问受信任的站点有助于保持在线安全。 这有一些道理,但不幸的是,这个建议并不总是切实际的。 也许用户“信任”本地新闻网站 ConsolidatedMessenger.com 并改为访问该网站,但该网站存在 XSS 漏洞,允许攻击者注入 fabrikam.com 上运行的相同代码片段。

可以验证传入请求是否具有引用域 的引用器标头 。 这将停止无意中从第三方域提交的请求。 但是,有些人出于隐私原因禁用其浏览器的 Referer 标头,如果受害者安装了某些不安全的软件,攻击者有时可能会欺骗该标头。 验证 Referer 标头 不被视为防止 XSRF 攻击的安全方法。

Web Stack 运行时 XSRF 缓解措施

ASP.NET Web Stack 运行时使用 同步器令牌模式 的变体来防御 XSRF 攻击。 同步器令牌模式的一般形式是将两个反 XSRF 令牌提交到服务器,每个 HTTP POST (除了身份验证令牌) :一个令牌作为 Cookie,另一个作为表单值。 攻击者无法确定 ASP.NET 运行时生成的令牌值或不可预测值。 提交令牌时,仅当两个令牌都传递比较检查时,服务器才会允许请求继续。

XSRF 请求验证 会话令牌 存储为 HTTP Cookie,当前在其有效负载中包含以下信息:

  • 由随机 128 位标识符组成的安全令牌。
    下图显示了使用 Internet Explorer F12 开发人员工具显示的 XSRF 请求验证会话令牌: (请注意,这是当前实现,甚至可能会发生更改。)

显示“我的 A S P 点 NET M V C 应用程序索引”页的屏幕截图。“网络”选项卡已打开。

字段令牌存储为 ,<input type="hidden" />并在其有效负载中包含以下信息:

反 XSRF 令牌的有效负载经过加密和签名,因此在使用工具检查令牌时无法查看用户名。 当 Web 应用程序面向 ASP.NET 4.0 时,加密服务由 MachineKey.Encode 例程提供。 当 Web 应用程序面向 ASP.NET 4.5 或更高版本时,加密服务由 MachineKey.Protect 例程提供,从而提供更好的性能、可扩展性和安全性。 有关更多详细信息,请参阅以下博客文章:

生成令牌

若要生成反 XSRF 令牌,请从 MVC 视图或 @AntiForgery.GetHtml() Razor 页面调用 @Html.AntiForgeryToken 方法。 然后,运行时将执行以下步骤:

  1. 如果当前 HTTP 请求已包含反 XSRF 会话令牌 (反 XSRF cookie __RequestVerificationToken) ,则会从中提取安全令牌。 如果 HTTP 请求不包含反 XSRF 会话令牌,或者如果提取安全令牌失败,则将生成新的随机反 XSRF 令牌。
  2. 使用上述步骤 (1) 中的安全令牌和当前登录用户的标识生成反 XSRF 字段令牌。 (有关确定用户标识的详细信息,请参阅下面的 具有特殊支持的方案 部分。) 此外,如果配置 了 IAntiForgeryAdditionalDataProvider ,运行时将调用其 GetAdditionalData 方法,并在字段令牌中包含返回的字符串。 (有关详细信息,请参阅 配置和扩展性 部分。)
  3. 如果在步骤 (1) 中生成了新的反 XSRF 令牌,则会创建一个新的会话令牌来包含该令牌,并将添加到出站 HTTP Cookie 集合中。 步骤 (2) 中的字段标记将包装在 元素中<input type="hidden" />,此 HTML 标记将是 或 AntiForgery.GetHtml()Html.AntiForgeryToken()返回值。

验证令牌

为了验证传入的反 XSRF 令牌,开发人员在 MVC 操作或控制器上包括 ValidateAntiForgeryToken 属性,或者她从她的 Razor 页面调用 @AntiForgery.Validate() 。 运行时将执行以下步骤:

  1. 读取传入会话令牌和字段令牌,并从每个令牌中提取反 XSRF 令牌。 在生成例程中,每个步骤的反 XSRF 令牌必须相同 (2 个) 。
  2. 如果当前用户已经过身份验证,则她的用户名与存储在字段令牌中的用户名进行比较。 用户名必须匹配。
  3. 如果配置 了 IAntiForgeryAdditionalDataProvider ,运行时将调用其 ValidateAdditionalData 方法。 方法必须返回布尔值 true

如果验证成功,则允许请求继续。 如果验证失败,框架将引发 HttpAntiForgeryException

故障条件

从 ASP.NET Web Stack 运行时 v2 开始,验证期间引发的任何 HttpAntiForgeryException 都将包含有关错误的详细信息。 当前定义的故障条件包括:

  • 请求中不存在会话令牌或表单令牌。
  • 会话令牌或表单令牌不可读。 最可能的原因是,运行不匹配版本的 ASP.NET Web Stack Runtime 的场,或者Web.config中的 machineKey> 元素因计算机而异的场<。 可以使用 Fiddler 等工具通过篡改任何一个反 XSRF 令牌来强制实施此异常。
  • 会话令牌和字段令牌已交换。
  • 会话令牌和字段令牌包含不匹配的安全令牌。
  • 字段令牌中嵌入的用户名与当前登录用户的用户名不匹配。
  • IAntiForgeryAdditionalDataProvider.ValidateAdditionalData 方法返回 false

反 XSRF 工具也可能在令牌生成或验证期间执行其他检查,在这些检查期间失败可能会导致引发异常。 有关详细信息,请参阅 WIF/ACS/基于声明的身份验证和配置和扩展性部分。

具有特殊支持的方案

匿名身份验证

反 XSRF 系统包含对匿名用户的特殊支持,其中“anonymous”定义为 IIdentity.IsAuthenticated 属性返回 false 的用户。 方案包括在对用户进行身份验证之前 (登录页提供 XSRF 保护,) 和自定义身份验证方案,其中应用程序使用 IIdentity 以外的机制来标识用户。

若要支持这些方案,请记住会话令牌和字段令牌由安全令牌联接,安全令牌是随机生成的 128 位不透明标识符。 此安全令牌用于跟踪单个用户在浏览站点时的会话,以便有效地提供匿名标识符。 空字符串用于代替上述生成和验证例程的用户名。

WIF/ACS/基于声明的身份验证

通常,内置于 .NET Framework 的 IIdentity 类具有 属性,IIdentity.Name 属性足以唯一标识特定应用程序中的特定用户。 例如, FormsIdentity.Name 返回存储在成员资格数据库中的用户名, (该用户名对于所有应用程序都是唯一的,具体取决于该数据库) , WindowsIdentity.Name 返回用户的域限定标识,依此。 这些系统不仅提供身份验证;它们还会 标识 应用程序的用户。

另一方面,基于声明的身份验证不一定需要标识特定用户。 相反, ClaimsPrincipalClaimsIdentity 类型与一组 Claim 实例相关联,其中单个声明可能为“已超过 18 岁”或“是管理员”,而其他任何内容。 由于不一定标识用户,因此运行时无法使用 ClaimsIdentity.Name 属性作为此特定用户的唯一标识符。 团队已看到实际示例 ,其中 ClaimsIdentity.Name 返回 null,返回友好 (显示) 名称,否则返回不适合用作用户的唯一标识符的字符串。

许多使用基于声明的身份验证的部署都使用 Azure 访问控制 服务 (ACS) 。 ACS 允许开发人员配置单个 标识提供者 (,例如 ADFS、Microsoft 帐户提供程序、OpenID 提供程序(如 Yahoo!)等) ,标识提供者返回 名称标识符。 这些名称标识符可能包含个人身份信息 (PII) (如电子邮件地址),也可以像个人标识符 (PPID) 一样匿名。 不管怎样,元组 (标识提供者、名称标识符) 都充分用作特定用户在浏览站点时的适当跟踪令牌,因此 ASP.NET Web Stack 运行时可以在生成和验证反 XSRF 字段令牌时使用元组代替用户名。 标识提供者和名称标识符的特定 URI 为 :

  • https://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider
  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier

有关详细信息, (请参阅此 ACS 文档页 。)

生成或验证令牌时,ASP.NET Web Stack 运行时将在运行时尝试绑定到类型:

  • Microsoft.IdentityModel.Claims.IClaimsIdentity, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 (适用于 WIF SDK.)
  • System.Security.Claims.ClaimsIdentity 适用于 .NET 4.5) 的 (。

如果存在这些类型,并且当前用户的 IIIIdentity 实现或子类其中一种类型,则反 XSRF 工具将在生成和验证令牌时使用 (标识提供者、名称标识符) 元组来代替用户名。 如果不存在此类元组,请求将失败,并显示一个错误,向开发人员描述如何配置反 XSRF 系统以了解正在使用的特定基于声明的身份验证机制。 有关详细信息,请参阅 配置和扩展性 部分。

OAuth/OpenID 身份验证

最后,反 XSRF 工具对使用 OAuth 或 OpenID 身份验证的应用程序提供特殊支持。 此支持基于启发式支持:如果当前 IIdentity.Name 以 http:// 或 https:// 开头,则用户名比较将使用序号比较器而不是默认的 OrdinalIgnoreCase 比较器来完成。

配置和扩展性

有时,开发人员可能需要更严格地控制反 XSRF 生成和验证行为。 例如,MVC 和网页帮助程序自动将 HTTP Cookie 添加到响应的默认行为可能是不可取的,开发人员可能希望将令牌保存在其他位置。 有两个 API 可帮助执行此操作:

AntiForgery.GetTokens(string oldCookieToken, out string newCookieToken, out string formToken);
AntiForgery.Validate(string cookieToken, string formToken);

GetTokens 方法将现有的 XSRF 请求验证会话令牌 (作为输入,该令牌可能为 null) ,并生成新的 XSRF 请求验证会话令牌和字段令牌作为输出。 标记只是不透明的字符串,没有修饰;例如,formToken 值不会包装在输入>标记中<。 newCookieToken 值可以为 null;如果发生这种情况,则表示旧的CookieToken 值仍然有效,并且无需设置新的响应 Cookie。 GetTokens 的调用方负责保留任何必要的响应 Cookie 或生成任何必要的标记;GetTokens 方法本身不会改变响应作为副作用。 Validate 方法采用传入的会话和字段令牌,并对其运行上述验证逻辑。

AntiForgeryConfig

开发人员可以从Application_Start配置反 XSRF 系统。 配置采用编程方式。 下面描述了静态 AntiForgeryConfig 类型的属性。 大多数使用声明的用户需要设置 UniqueClaimTypeIdentifier 属性。

属性 说明
AdditionalDataProvider IAntiForgeryAdditionalDataProvider,在令牌生成期间提供其他数据,并在令牌验证期间使用其他数据。 默认值为 null。 有关详细信息,请参阅 IAntiForgeryAdditionalDataProvider 部分。
CookieName 一个字符串,提供用于存储反 XSRF 会话令牌的 HTTP Cookie 的名称。 如果未设置此值,将根据应用程序的已部署虚拟路径自动生成名称。 默认值为 null
RequireSsl 一个布尔值,指示是否需要通过 SSL 保护的通道提交反 XSRF 令牌。 如果此值为 true,则任何自动生成的 Cookie 都将设置“安全”标志,如果从未通过 SSL 提交的请求中调用,则反 XSRF API 将引发。 默认值是 false秒。
SuppressIdentityHeuristicChecks 一个布尔值,指示反 XSRF 系统是否应停用对基于声明的标识的支持。 如果此值为 true,系统将假定 IIdentity.Name 适合用作每个用户的唯一标识符,并且不会尝试使用特殊情况 IClaimsIdentityClClaimsIdentity ,如 基于 WIF/ACS/声明的身份验证 部分中所述。 默认值为 false
UniqueClaimTypeIdentifier 一个字符串,指示哪个声明类型适合用作唯一的每个用户标识符。 如果设置了此值,并且当前 IIdentity 是基于声明的,则系统将尝试提取 UniqueClaimTypeIdentifier 指定的类型的声明,并在生成字段令牌时使用相应的值来代替用户的用户名。 如果未找到声明类型,系统将使请求失败。 默认值为 null,指示系统应使用 (标识提供者、名称标识符) 元组(如前面所述)来代替用户的用户名。

IAntiForgeryAdditionalDataProvider

IAntiForgeryAdditionalDataProvider 类型允许开发人员通过在每个令牌中往返附加数据来扩展反 XSRF 系统的行为。 每次生成字段令牌时都会调用 GetAdditionalData 方法,并将返回值嵌入到生成的令牌中。 实现者可以返回时间戳、nonce 或她希望从此方法获得的任何其他值。

同样,每次验证字段令牌时都会调用 ValidateAdditionalData 方法,并将令牌中嵌入的“附加数据”字符串传递给该方法。 验证例程可以通过根据创建令牌) 、nonce 检查例程或任何其他所需逻辑的时间检查当前时间来实现超时 (。

设计决策和安全注意事项

仅当尝试保护匿名/未经身份验证的用户免受 XSRF 攻击时,技术上才需要链接会话令牌和字段令牌的安全令牌。 对用户进行身份验证时,身份验证令牌本身 (以 cookie 的形式提交,) 可用作同步器令牌对的一半。 但是,有一些有效的方案可以保护未经身份验证的用户点击的登录页,并且通过始终生成和验证安全令牌,简化了反 XSRF 逻辑,即使对于经过身份验证的用户也是如此。 它还会在字段令牌被攻击者入侵时提供一些额外的保护,因为设置或猜测会话令牌将是攻击者克服的另一个障碍。

在单个域中托管多个应用程序时,开发人员应谨慎使用。 例如,即使 example1.cloudapp.netexample2.cloudapp.net 是不同的主机,* .cloudapp.net 域下的所有主机之间也存在隐式信任关系。 这种隐式信任关系 允许潜在的不受信任的主机影响彼此的 Cookie , (控制 AJAX 请求的同源策略不一定适用于 HTTP cookie) 。 ASP.NET Web Stack 运行时提供了一些缓解措施,其中用户名嵌入到字段令牌中,因此即使恶意子域能够覆盖会话令牌,也无法为用户生成有效的字段令牌。 但是,在此类环境中托管时,内置反 XSRF 例程仍无法防御会话劫持或登录 XSRF。

反 XSRF 例程当前无法防御 点击劫持。 希望保护自己免受点击劫持的应用程序可以通过发送 X-Frame-Options: SAMEORIGIN 标头和每个响应来轻松执行此操作。 所有最新浏览器都支持此标头。 有关详细信息,请参阅 IE 博客SDL 博客OWASP。 ASP.NET Web Stack 运行时可能会在将来的某个版本中使 MVC 和网页反 XSRF 帮助程序自动设置此标头,以便应用程序自动受到保护,免受此攻击。

Web 开发人员应继续确保其网站不会受到 XSS 攻击。 XSS 攻击非常强大,成功利用也会打破 ASP.NET Web Stack 运行时针对 XSRF 攻击的防御。

确认

@LeviBroderick,他在大部分信息中编写了 ASP.NET 安全代码。