介绍

跨站点请求伪造 (CSRF) 是一种攻击,当恶意网站、电子邮件、博客、即时消息或程序导致用户的 Web 浏览器在用户经过身份验证时在受信任的站点上执行不需要的操作时,就会发生这种攻击。CSRF 攻击之所以有效,是因为浏览器请求自动包含所有 cookie(包括会话 cookie)。因此,如果用户通过了站点的身份验证,站点将无法区分合法的授权请求和伪造的经过身份验证的请求。当使用正确的授权时,这种攻击就会被阻止,这意味着需要一个挑战-响应机制来验证请求者的身份和权限。

成功的 CSRF 攻击的影响仅限于易受攻击的应用程序暴露的功能和用户的权限。例如,这种攻击可能会导致资金转移、更改密码或使用用户的凭据进行购买。实际上,攻击者使用 CSRF 攻击使目标系统在受害者不知情的情况下通过受害者的浏览器执行功能,至少在未经授权的交易提交之前是这样。

总之,防御CSRF应遵循以下原则:

  • 检查您的框架是否内置 CSRF 保护并使用它
    • 如果框架没有内置的 CSRF 保护,请将CSRF 令牌添加到所有状态更改请求(在网站上引起操作的请求)并在后端验证它们
  • 对于有状态软件,请使用同步器令牌模式
  • 对于无状态软件,请使用双提交 cookie
  • 对于不使用<form>标签的 API 驱动网站,请考虑使用自定义请求标头
  • 实施深度缓解措施部分中的至少一项缓解措施
  • 请记住,任何跨站脚本 (XSS) 都可以用来击败所有 CSRF 缓解技术!
  • 不要使用 GET 请求进行状态更改操作。
    • 如果您出于任何原因这样做,请保护这些资源免受 CSRF 的影响

基于令牌的缓解措施

同步器令牌模式是最流行和推荐的缓解 CSRF 的方法之一。

使用内置或现有的 CSRF 实现进行 CSRF 保护

同步器令牌防御已内置于许多框架中。强烈建议在尝试构建自定义令牌生成系统之前研究您使用的框架是否具有默认实现 CSRF 保护的选项。例如,.NET 具有内置保护,可为 CSRF 易受攻击的资源添加令牌。在使用这些生成令牌来保护 CSRF 易受攻击资源的内置 CSRF 保护之前,您有责任进行正确的配置(例如密钥管理和令牌管理)。

同步器令牌模式

CSRF 令牌应在服务器端生成。它们可以为每个用户会话或每个请求生成一次。每个请求令牌比每个会话令牌更安全,因为攻击者利用被盗令牌的时间范围很短。然而,这可能会导致可用性问题。例如,“后退”按钮浏览器功能通常会受到阻碍,因为前一页可能包含不再有效的令牌。与上一页的交互将导致服务器上发生 CSRF 误报安全事件。在初始生成令牌后的每会话令牌实现中,该值存储在会话中,并用于每个后续请求,直到会话过期。

当客户端发出请求时,服务器端组件必须与用户会话中找到的令牌相比,验证请求中令牌的存在性和有效性。如果在请求中找不到令牌,或者提供的值与用户会话中的值不匹配,则应拒绝该请求。还应考虑其他操作,例如将事件记录为正在进行的潜在 CSRF 攻击。

CSRF 令牌应该是:

  • 每个用户会话都是唯一的。
  • 秘密
  • 不可预测(由安全方法生成的大随机值)。

CSRF 令牌可以防止 CSRF,因为如果没有令牌,攻击者就无法向后端服务器创建有效的请求。

对于同步令牌模式,不应使用 cookie 传输 CSRF 令牌。

CSRF 令牌可以作为响应负载的一部分(例如 HTML 或 JSON 响应)传输到客户端。然后,它可以作为表单提交中的隐藏字段传输回服务器,或者通过 AJAX 请求作为自定义标头值或 JSON 负载的一部分。确保令牌不会在服务器日志或 URL 中泄漏。GET 请求中的 CSRF 令牌可能会在多个位置泄露,例如浏览器历史记录、日志文件、记录 HTTP 请求第一行的网络实用程序以及受保护站点链接到外部站点时的 Referer 标头。

例如:

<form action="/transfer.do" method="post">
<input type="hidden" name="CSRFToken" value="OWY4NmQwODE4ODRjN2Q2NTlhMmZlYWEwYzU1YWQwMTVhM2JmNGYxYjJiMGI4MjJjZDE1ZDZMGYwMGEwOA==">
[...]
</form>

通过 JavaScript 在自定义 HTTP 请求标头中插入 CSRF 令牌被认为比在隐藏字段表单参数中添加令牌更安全,因为具有自定义标头的请求会自动遵守同源策略。

如果在服务器上维护 CSRF 令牌的状态存在问题,您可以使用称为双重提交 Cookie 模式的替代技术。该技术易于实现并且是无状态的。有多种方法可以实现此技术,其中朴素模式是最常用的变体。

Naive Double Submit Cookie是一种可扩展且易于实现的技术,我们在 Cookie 中发送随机值并将其作为请求参数,然后服务器验证 Cookie 值和请求值是否匹配。当用户访问时(甚至在进行身份验证以防止登录 CSRF 之前),站点应生成一个(最好是加密强度较高的)随机值,并将其设置为用户计算机上的 cookie,与会话标识符分开。然后,站点要求每个交易请求都包含此随机值作为隐藏表单值或包含在请求标头中。如果两者在服务器端匹配,则服务器将其视为合法请求,否则将拒绝该请求。

简而言之,攻击者在跨站请求期间无法访问 cookie 值。这可以防止他们在隐藏表单值中包含匹配值或作为请求参数/标头。

Naive Double Submit Cookie 方法是对抗 CSRF 攻击的良好初始步骤,但它仍然容易受到某些攻击。此资源提供了有关某些漏洞的更多信息。因此,建议使用更安全的实现,即签名双重提交 Cookie模式。

签名双重提交 Cookie涉及只有服务器知道的密钥。这可确保攻击者无法创建自己的已知 CSRF 令牌并将其注入受害者的经过身份验证的会话中。可以通过散列或加密来保护令牌,HMAC 算法因其速度快且易于实施而成为流行的选择。

在这两种情况下,建议将 CSRF 令牌与用户当前会话绑定,以进一步增强安全性。

HMAC CSRF 令牌

加密 CSRF cookie 的一个更简单的替代方案是使用 HMAC(基于哈希的消息身份验证代码)使用只有服务器知道的密钥对随机值进行哈希处理,并将该值放入 cookie 中。这类似于加密的 cookie(两者都只需要服务器持有的知识),但比加密和解密 cookie 的计算强度要​​小。

我们建议使用以下步骤生成具有与会话相关的用户值的 HMAC CSRF 令牌:

  • 与会话相关的值,随每个登录会话而变化。该值应该仅对整个用户经过身份验证的会话有效。避免使用用户电子邮件或 ID 等静态值,因为它们不安全 ( 1 | 2 | 3 )。值得注意的是,过于频繁地更新 CSRF 令牌(例如针对每个请求)是一种误解,认为它会增加大量安全性,但实际上会损害用户体验 ( 1 )。例如,您可以选择以下与会话相关的值之一:
    • 服务器端会话 ID(例如PHPASP.NET
    • JWT 中的随机值(例如 UUID)在每次创建 JWT 时都会更改
  • 秘密加密密钥不要与简单实现中的随机值混淆。该值用于生成 HMAC 哈希值。理想情况下,将此密钥存储为环境变量。
  • 用于防碰撞目的的随机值。生成一个随机值(最好是加密随机)以确保同一秒内的连续调用不会产生相同的哈希值 ( 1 )。

下面是一个伪代码示例,演示了上述实现步骤:

// Gather the values
secret = readEnvironmentVariable("CSRF_SECRET") // HMAC secret key
sessionID = session.sessionID // Current authenticated user session
randomValue = cryptographic.randomValue() // Cryptographic random value

// Create the CSRF Token
message = sessionID + "!" + randomValue // HMAC message payload
hmac = hmac("SHA256", secret, message) // Generate the HMAC hash
csrfToken = hmac + "." + message // Combine HMAC hash with message to generate the token. The plain message is required to later authenticate it against its HMAC hash

// Store the CSRF Token in a cookie
response.setCookie("csrf_token=" + csrfToken + "; Secure) // Set Cookie without HttpOnly flag

CSRF 代币中是否应包含时间戳以防止过期?
将时间戳作为指定 CSRF 令牌过期时间的值是一种常见的误解。CSRF 令牌不是访问令牌。它们用于使用会话信息来验证整个会话中请求的真实性。新会话应生成新令牌 ( 1 )。

自定义请求标头

同步器令牌和双重提交 cookie 都用于防止伪造表单数据,但它们的实现可能很棘手,并且会降低可用性。许多现代 Web 应用程序不使用<form>标签。特别适合 AJAX 或 API 端点的用户友好防御是使用自定义请求标头。此方法不需要令牌。

在此模式中,客户端将自定义标头附加到需要 CSRF 保护的请求。标头可以是任意键值对,只要不与现有标头冲突即可。

X-YOURSITE-CSRF-PROTECTION=1

处理请求时,API 会检查此标头是否存在。如果标头不存在,后端将拒绝该请求,因为该请求可能存在伪造。这种方法有几个优点:

  • 不需要更改 UI
  • 没有引入服务器状态来跟踪令牌

如果您<form>在客户端中的任何位置使用标签,您仍然需要使用本文档中描述的替代方法(例如令牌)来保护它们。

这种防御依赖于浏览器的同源策略 (SOP)限制,即只能使用 JavaScript 添加自定义标头,并且只能在其源内。默认情况下,浏览器不允许 JavaScript 使用自定义标头发出跨源请求。只有您从源提供的 JavaScript 才能添加这些标头。

自定义标头和 CORS

默认情况下,跨域请求 (CORS) 不会设置 Cookie。要在 API 上启用 cookie,您需要设置Access-Control-Allow-Credentials=true. 浏览器将拒绝任何包含Access-Control-Allow-Origin=*凭据是否被允许的响应。要允许 CORS 请求,但防止 CSRF,您需要确保服务器仅将您通过标头明确控制的几个选定来源列入白名单Access-Control-Allow-Origin。来自允许域的任何跨源请求都可以设置自定义标头。

例如,您可以将后端配置为允许带有来自http://www.yoursite.com和的 cookie 的 CORS http://mobile.yoursite.com,以便唯一可能的预检响应是:

Access-Control-Allow-Origin=http://mobile.yoursite.com
Access-Control-Allow-Credentials=true

或者

Access-Control-Allow-Origin=http://www.yoursite.com
Access-Control-Allow-Credentials=true

一种不太安全的配置是将后端服务器配置为允许使用正则表达式从站点的所有子域进行 CORS。如果攻击者能够接管子域(在云服务中并不罕见),您的 CORS 配置将允许他们绕过同源策略并使用您的自定义标头伪造请求。

纵深防御技术

SameSite 是一个 cookie 属性(类似于 HTTPOnly、Secure 等),旨在减轻 CSRF 攻击。它在RFC6265bis中定义。此属性帮助浏览器决定是否随跨站点请求一起发送 cookie。该属性的可能值为LaxStrictNone

Strict 值将阻止浏览器在所有跨站点浏览上下文中将 cookie 发送到目标站点,即使在遵循常规链接时也是如此。例如,对于类似 GitHub 的网站,这意味着如果登录用户点击企业讨论论坛或电子邮件上发布的私人 GitHub 项目的链接,GitHub 将不会收到会话 cookie,并且用户将无法访问该项目。然而,银行网站不希望允许从外部站点链接任何交易页面,因此“严格”标志是最合适的。

对于希望在用户从外部链接到达后维持用户登录会话的网站,默认的 Lax 值在安全性和可用性之间提供了合理的平衡。在上述 GitHub 场景中,当跟踪来自外部网站的常规链接时,将允许会话 cookie,同时在容易发生 CSRF 的请求方法(例如 POST)中阻止它。只有在 Lax 模式下允许的跨站点请求才是具有顶级导航并且也是安全的HTTP 方法的请求。

有关这些值的更多详细信息,请查看rfc中的SameSite以下部分

使用此属性的 cookie 示例:

Set-Cookie: JSESSIONID=xxxxx; SameSite=Strict
Set-Cookie: JSESSIONID=xxxxx; SameSite=Lax

现在所有桌面浏览器和几乎所有移动浏览器都支持该SameSite属性。要跟踪实现它的浏览器以及该属性的使用情况,请参阅以下服务。请注意,Chrome 已宣布将从SameSite=LaxChrome 80(将于 2020 年 2 月发布)起将 Cookie 标记为默认设置,Firefox 和 Edge 都计划效仿。此外,Secure标记为 的 cookie 需要该标志SameSite=None

值得注意的是,该属性应作为深度概念的附加层防御来实现。此属性通过支持它的浏览器来保护用户,并且它还包含两种绕过它的方法,如下节所述。此属性不应取代 CSRF 令牌。相反,它应该与该令牌共存,以便以更可靠的方式保护用户。

使用标准标头验证来源

此缓解措施有两个步骤,这两个步骤都依赖于检查 HTTP 请求标头值。

  1. 确定请求的来源(源来源)。可以通过 Origin 或 Referer 标头完成。
  2. 确定请求的来源(目标来源)。

在服务器端,我们验证它们是否匹配。如果他们这样做,我们会接受该请求合法(意味着它是同源请求),如果他们不这样做,我们会丢弃该请求(意味着该请求源自跨域)。这些标头的可靠性来自于这样一个事实:它们不能以编程方式更改,因为它们属于禁止标头列表,这意味着只有浏览器可以设置它们。

识别源来源(通过 Origin/Referer 标头)

检查原始标头

如果 Origin 标头存在,请验证其值是否与目标源匹配。与 Referer 不同,Origin 标头将出现在源自 HTTPS URL 的 HTTP 请求中。

检查引用头

如果 Origin 标头不存在,请验证 Referer 标头中的主机名是否与目标源匹配。这种 CSRF 缓解方法也常用于未经身份验证的请求,例如在建立会话状态之前发出的请求,需要跟踪同步令牌。

在这两种情况下,请确保目标来源检查是强有力的。例如,如果您的网站example.org确保example.org.attacker.com未通过来源检查(即,通过来源后的尾随/进行匹配,以确保您与整个来源进行匹配)。

如果这些标头都不存在,您可以接受或阻止该请求。我们建议阻止。或者,您可能希望记录所有此类实例,监视它们的用例/行为,然后仅在您获得足够的信心后才开始阻止请求。

识别目标来源

您可能认为确定目标来源很容易,但事实往往并非如此。#第一个想法是简单地从请求中的 URL获取目标源(即,其主机名和端口)。然而,应用程序服务器经常位于一个或多个代理后面,并且原始 URL 与应用程序服务器实际接收到的 URL 不同。如果您的应用程序服务器由其用户直接访问,那么在 URL 中使用源就可以了,一切都准备好了。

如果您使用代理,则有多种选择可供考虑。

  • 配置您的应用程序以简单地了解其目标源:这是您的应用程序,因此您可以找到其目标源并在某些服务器配置条目中设置该值。这将是最安全的方法,因为它是在服务器端定义的,因此它是值得信赖的值。但是,如果您的应用程序部署在许多地方(例如开发、测试、QA、生产以及可能的多个生产实例),那么维护可能会出现问题。为每种情况设置正确的值可能很困难,但如果您可以通过一些中央配置来做到这一点并提供实例以从中获取价值,那就太好了!(注意:确保集中配置存储得到安全维护,因为 CSRF 防御的主要部分依赖于它。)
  • 使用 Host 标头值:如果您希望应用程序找到自己的目标,以便不必为每个部署的实例进行配置,我们建议使用 Host 系列标头。Host 标头的目的是包含请求的目标来源。但是,如果您的应用程序服务器位于代理后面,则 Host 标头值很可能会被代理更改为代理后面的 URL 的目标来源,这与原始 URL 不同。此修改后的 Host 标头来源将与原始 Origin 或 Referer 标头中的源来源不匹配。
  • 使用 X-Forwarded-Host 标头值:为了避免代理更改主机标头的问题,还有另一个称为 X-Forwarded-Host 的标头,其用途是包含代理收到的原始主机标头值。大多数代理将在 X-Forwarded-Host 标头中传递原始 Host 标头值。因此,该标头值可能是您需要与 Origin 或 Referer 标头中的源来源进行比较的目标原始值。

当请求中存在原始或引用标头时,此缓解措施可以正常工作。尽管大多数情况下都包含这些标头,但很少有用例不包含它们(其中大多数是出于合法原因保护用户隐私/调整浏览器生态系统)。下面列出了一些用例:

  • Internet Explorer 11 不会在跨受信任区域的站点的 CORS 请求上添加 Origin 标头。Referer 标头将仍然是 UI 来源的唯一指示。请参阅 Stack Overflow 中的以下参考资料(此处此处)
  • 在302 重定向跨源之后的实例中,源不包含在重定向请求中,因为这可能被视为不应发送到其他源的敏感信息。
  • 在某些隐私上下文中,Origin 设置为“null”,例如,请参阅此处的以下内容。
  • 所有跨源请求都包含 Origin 标头,但对于同源请求,在大多数浏览器中,它仅包含在 POST/DELETE/PUT 中。 注意虽然这并不理想,但许多开发人员使用 GET 请求来执行状态更改操作。
  • Referer 标头也不例外。在多种用例中也省略了引用标1、2、3、45众所周知,负载均衡器、代理和嵌入式网络设备在记录时出于隐私原因会剥离引用标头。

通常,一小部分流量属于上述类别(1-2%),没有企业愿意失去这些流量。互联网上使用的一种流行技术是,如果来源/引用者与您配置的域列表“或”空值匹配,则接受请求,使该技术更可用。空值用于覆盖边缘情况上面提到过,其中不发送这些标头)。请注意,攻击者可以利用此技术,但人们更喜欢使用此技术作为深度防御措施,因为部署它所需的工作量很小。

此问题的另一个解决方案是使用Cookie Prefixes带有 CSRF 令牌的 cookie。如果 cookie 有__Host-前缀eg,Set-Cookie: __Host-token=RANDOM; path=/; Secure则 cookie:

  • 无法从另一个子域(覆盖)写入。
  • 必须有 的路径/
  • 必须标记为安全(即不能通过未加密的 HTTP 发送)。

截至 2020 年 7 月,除 Internet Explorer 之外的所有主要浏览器都支持cookie 前缀。

有关 cookie 前缀的更多信息,请参阅Mozilla 开发者网络IETF 草案。

基于用户交互的 CSRF 防御

虽然此处引用的所有技术不需要任何用户交互,但有时让用户参与交易以防止未经授权的操作(通过 CSRF 或其他方式伪造)更容易或更合适。以下是一些技术示例,如果正确实施,这些技术可以充当强大的 CSRF 防御。

  • ~~重新认证~~授权机制(密码或更强)
  • 一次性代币
  • CAPTCHA(更喜欢没有用户交互或视觉模式匹配的较新 CAPTCHA 版本)

虽然这些是非常强大的 CSRF 防御,但它会对用户体验产生重大影响。因此,它们通常仅用于安全关键操作(例如密码更改、转账等),以及本备忘单中讨论的其他防御措施。

登录CSRF

大多数开发人员倾向于忽略登录表单上的 CSRF 漏洞,因为他们认为 CSRF 不适用于登录表单,因为用户在该阶段未经过身份验证,但这种假设并不总是正确。CSRF 漏洞仍然可能发生在用户未经身份验证的登录表单上,但影响和风险有所不同。

例如,如果攻击者使用 CSRF 在购物网站上使用攻击者的帐户假设目标受害者的经过身份验证的身份,然后受害者输入其信用卡信息,则攻击者可能能够使用受害者存储的卡详细信息购买商品。有关登录 CSRF 和其他风险的更多信息,请参阅本文第 3

可以通过创建预会话(用户经过身份验证之前的会话)并在登录表单中包含令牌来缓解登录 CSRF。您可以使用上述任何技术来生成令牌。请记住,一旦用户通过身份验证,预会话就无法转换为真实会话 - 应销毁该会话并创建一个新会话以避免会话固定攻击该技术在跨站点请求伪造的稳健防御第 4.1 节中进行了描述。

客户端CSRF

客户端 CSRF是 CSRF 攻击的一种新变体,攻击者通过操纵程序的输入参数,欺骗客户端 JavaScript 代码,向易受攻击的目标站点发送伪造的 HTTP 请求。当 JavaScript 程序使用攻击者控制的输入(例如 URL)来生成异步 HTTP 请求时,客户端 CSRF 就会产生。

注意: CSRF 的这些变体特别重要,因为它们可以绕过一些常见的反 CSRF 对策,例如基于令牌的缓解措施SameSite cookie。例如,当使用同步器令牌自定义 HTTP 请求标头时,JavaScript 程序会将它们包含在异步请求中。此外,Web 浏览器将在 JavaScript 程序发起的同站点请求上下文中包含 cookie,从而规避SameSite cookie 策略

客户端与经典CSRF:在经典CSRF中,易受攻击的组件是服务器端程序,它无法区分传入的经过身份验证的请求是否是有意执行的,也称为混淆代理问题。在客户端CSRF中,易受攻击的组件是客户端JavaScript程序,它允许攻击者生成任意异步请求,例如通过操纵请求端点和/或其参数。客户端 CSRF 是一个输入验证问题,当被利用时,会重新引入混淆的代理缺陷,即服务器端将无法再次区分请求是否是有意执行的。

有关客户端 CSRF 漏洞的更多信息,请参阅本文的第 2 节和第 5 节 SameSite wikiCSRF 章节以及Facebook Whitehat 程序这篇文章

客户端 CSRF 示例

以下代码片段演示了客户端 CSRF 漏洞的简单示例。

<script type="text/javascript">
    var csrf_token = document.querySelector("meta[name='csrf-token']").getAttribute("content");
    function ajaxLoad(){
        // process the URL hash fragment
        let hash_fragment = window.location.hash.slice(1);  

        // hash fragment should be of the format: /^(get|post);(.*)$/
        // e.g., https://site.com/index/#post;/profile
        if(hash_fragment.length > 0 && hash_fragment.indexOf(';') > 0 ){

            let params = hash_fragment.match(/^(get|post);(.*)$/);
            if(params && params.length){
                let request_method = params[1];   
                let request_endpoint = params[3];

                fetch(request_endpoint, {
                    method: request_method,
                    headers: {
                        'XSRF-TOKEN': csrf_token,
                        // [...]
                    },
                    // [...]
                }).then(response => { /* [...] */ }); 
            }
        }
    }
    // trigger the async request on page load
    window.onload = ajaxLoad();
 </script>

漏洞:在此代码片段中,程序ajaxLoad()在页面加载时调用一个函数,该函数负责加载各种网页元素。该函数读取URL 哈希片段的值(第 4 行),并从中提取两条信息(即请求方法和端点)以生成异步 HTTP 请求(第 11-13 行)。该漏洞出现在第 15-22 行,此时 JavaScript 程序使用 URL 片段获取异步 HTTP 请求(第 15 行)的服务器端端点和请求方法。然而,这两个输入都可以由网络攻击者控制,他们可以选择自己选择的值,并制作包含攻击负载的恶意 URL。

攻击:为了进行利用,攻击者可以与受害者共享恶意 URL(例如鱼叉式网络钓鱼电子邮件)并说服他们点击它,因为此类 URL 属于诚实、信誉良好但易受攻击的网站的来源。或者,他们可以将其用作他们控制的攻击页面的一部分,并滥用浏览器API(例如API window.open())来欺骗目标页面的易受攻击的JavaScript发送HTTP请求,这与经典CSRF的攻击模型非常相似攻击。

有关客户端 CSRF 的更多示例,请参阅Facebook Whitehat 程序这篇文章和 USENIX 安全论文

客户端 CSRF 缓解技术

独立请求:如果异步请求不是通过攻击者可控输入(例如URL窗口名称文档引用者postMessages等)生成的,则可以阻止客户端 CSRF 。

输入验证:根据上下文和功能,不一定总是能够实现输入和请求参数之间的完全隔离。在这些情况下,必须实施输入验证检查。这些检查应严格评估请求参数值的格式和选择,并决定它们是否只能用于非状态更改操作(例如,仅允许 GET 请求和以预定义前缀开头的端点)。

预定义请求数据:另一种缓解技术是在 JavaScript 代码中存储预定义的安全请求数据列表(例如,可以安全重播的端点、请求方法和其他参数的组合)。然后,程序可以使用 URL 片段中的开关参数来决定每个 JavaScript 函数应使用列表中的哪个条目。

Java 参考示例

以下JEE Web 过滤器为本备忘单中描述的一些概念提供了示例参考。它实现了以下无状态缓解措施(OWASP CSRFGuard,涵盖有状态方法)。

  • 使用标准标头验证同源
  • 双重提交cookie
  • SameSite cookie 属性

请注意,它仅充当参考示例,并不完整(例如:当源头和引用者标头检查成功时,它没有用于指导控制流的块,也没有对引用者标头进行端口/主机/协议级别验证)。建议开发人员在此参考示例的基础上构建完整的缓解措施。在检查 CSRF 被认为有效之前,开发人员还应该实现身份验证和授权机制。

完整源代码位于此处,并提供可运行的 POC。

自动包含 CSRF 令牌作为 AJAX 请求标头的 JavaScript 指南

以下指南认为GETHEADOPTIONS方法是安全操作。因此GETHEADOPTIONS方法 AJAX 调用不需要附加 CSRF 令牌标头。但是,如果动词用于执行状态更改操作,则它们还需要 CSRF 令牌标头(尽管这是不好的做法,应该避免)。

POST PUTPATCHDELETE方法是状态更改动词,应该在请求中附加一个 CSRF 令牌。以下指南将演示如何在 JavaScript 库中创建覆盖,以便将 CSRF 令牌自动包含在针对上述状态更改方法的每个 AJAX 请求中。

将 CSRF 令牌值存储在 DOM 中

CSRF 令牌可以包含在<meta>标签中,如下所示。页面中的所有后续调用都可以从此<meta>标记中提取 CSRF 令牌。它还可以存储在 JavaScript 变量中或 DOM 上的任何位置。但是,不建议将其存储在cookie或浏览器本地存储中。

以下代码片段可用于包含 CSRF 令牌作为标签<meta>

<meta name="csrf-token" content="{{ csrf_token() }}">

填充内容属性的确切语法取决于 Web 应用程序的后端编程语言。

覆盖默认值以设置自定义标头

一些 JavaScript 库允许覆盖默认设置,以便将标头自动添加到所有 AJAX 请求中。

XMLHttpRequest(本机 JavaScript)

XMLHttpRequest 的 open() 方法可以被重写,以便在下次调用anti-csrf-token该方法时设置标头。open()下面定义的函数csrfSafeMethod()将过滤掉安全的 HTTP 方法,只为不安全的 HTTP 方法添加标头。

这可以按照以下代码片段所示来完成:

<script type="text/javascript">
    var csrf_token = document.querySelector("meta[name='csrf-token']").getAttribute("content");
    function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS)$/.test(method));
    }
    var o = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(){
        var res = o.apply(this, arguments);
        var err = new Error();
        if (!csrfSafeMethod(arguments[0])) {
            this.setRequestHeader('anti-csrf-token', csrf_token);
        }
        return res;
    };
 </script>

AngularJS

AngularJS 允许为 HTTP 操作设置默认标头。更多文档可以在 AngularJS 的$httpProvider文档中找到。

<script>
    var csrf_token = document.querySelector("meta[name='csrf-token']").getAttribute("content");

    var app = angular.module("app", []);

    app.config(['$httpProvider', function ($httpProvider) {
        $httpProvider.defaults.headers.post["anti-csrf-token"] = csrf_token;
        $httpProvider.defaults.headers.put["anti-csrf-token"] = csrf_token;
        $httpProvider.defaults.headers.patch["anti-csrf-token"] = csrf_token;
        // AngularJS does not create an object for DELETE and TRACE methods by default, and has to be manually created.
        $httpProvider.defaults.headers.delete = {
            "Content-Type" : "application/json;charset=utf-8",
            "anti-csrf-token" : csrf_token
        };
        $httpProvider.defaults.headers.trace = {
            "Content-Type" : "application/json;charset=utf-8",
            "anti-csrf-token" : csrf_token
        };
      }]);
 </script>

此代码片段已经使用 AngularJS 版本 1.7.7 进行了测试。

阿克西奥斯

Axios允许我们为 POST、PUT、DELETE 和 PATCH 操作设置默认标头。

<script type="text/javascript">
    var csrf_token = document.querySelector("meta[name='csrf-token']").getAttribute("content");

    axios.defaults.headers.post['anti-csrf-token'] = csrf_token;
    axios.defaults.headers.put['anti-csrf-token'] = csrf_token;
    axios.defaults.headers.delete['anti-csrf-token'] = csrf_token;
    axios.defaults.headers.patch['anti-csrf-token'] = csrf_token;

    // Axios does not create an object for TRACE method by default, and has to be created manually.
    axios.defaults.headers.trace = {}
    axios.defaults.headers.trace['anti-csrf-token'] = csrf_token
</script>

此代码片段已使用 Axios 0.18.0 版本进行了测试。

jQuery

JQuery 公开了一个 API $.ajaxSetup(),可用于将anti-csrf-token标头添加到 AJAX 请求。$.ajaxSetup()可以在此处找到API 文档。下面定义的函数csrfSafeMethod()将过滤掉安全的 HTTP 方法,只为不安全的 HTTP 方法添加标头。

您可以通过采用以下代码片段来配置 jQuery 自动将令牌添加到所有请求标头中。这为基于 AJAX 的应用程序提供了简单方便的 CSRF 保护:

<script type="text/javascript">
    var csrf_token = $('meta[name="csrf-token"]').attr('content');

    function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS)$/.test(method));
    }

    $.ajaxSetup({
        beforeSend: function(xhr, settings) {
            if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("anti-csrf-token", csrf_token);
            }
        }
    });
</script>

此代码片段已经使用 jQuery 版本 3.3.1 进行了测试。

参考

CSRF

跨站点请求伪造预防备忘单
标签: