每个 Java 软件架构师都应该知道的 20 件事
什么是跨站点 请求伪造(CSRF)?
基本思想
攻击者编写代码来创建 HTTP 请求,如果该请求在已登录用户的浏览器中运行,则会执行一些危险操作,例如转账或删除数据。然后,攻击者会找到一种方法(通常是通过电子邮件)将恶意代码放入受害者的浏览器中。例如,攻击者可能会编写代码来更改购物网站上的送货地址。攻击者会向数十万用户发送钓鱼电子邮件。如果打开电子邮件的任何用户恰好登录了购物网站,CSRF 攻击可能会将他们的货物重定向到攻击者选择的地址。
攻击之所以能成功,是因为受害者已经登录,因此浏览器已经拥有经过身份验证的会话 cookie。当攻击者的电子邮件执行其请求时,浏览器会自动将其拥有的购物网站的所有 cookie(包括会话 cookie)附加到请求中。该代码在用户合法会话的上下文中运行,并执行用户不想要的操作。
为什么 CSRF 很糟糕
严重的跨站请求伪造 (CSRF) 漏洞可能会造成毁灭性后果,例如欺诈性金融交易和账户接管。Netflix 、YouTube和银行网络应用程序ING Direct等主要网站都发现了 CSRF 漏洞。Facebook 曾为发现的严重 CSRF 漏洞支付了25,000美元的漏洞赏金。
家用路由器内置的配置网页可能容易受到 CSRF 攻击。例如, 2008 年针对 ADSL 路由器的攻击更改了路由器对一家领先银行的入口,导致从该路由器到银行的任何后续流量都经过攻击者的服务器。国家漏洞数据库的搜索显示,即使在 2021 年,一些路由器制造商仍然没有吸取教训。
2017 年,OWASP 将 CSRF 从十大 Web 漏洞列表中删除。那时,大多数主流开发平台都已将 CSRF 防御功能纳入其框架中。尽管如此,直到 2020 年,HackerOne 仍将 CSRF 列为漏洞赏金计划中渗透测试人员最有利可图的漏洞之一。
攻击条件
只要存在以下情况,就有可能发生 CSRF 攻击:
- 可以通过 HTTP 执行状态改变请求。
- 状态改变请求的语法是可预测的。
- 浏览器会自动在请求中包含会话信息。
大多数网络表单都是为了改变某个地方某种事物的状态而构建的,因此大多数交互式网站都满足第一个条件。
状态更改请求的语法必须是可预测的,这样攻击者才能伪造它。CSRF 攻击者会盲目地将代码推送到用户的浏览器中,而无法看到用户正在做什么。通常,恶意请求会发布表单,攻击者必须知道要发布哪些名称/值对,服务器才能接受它而不会出现错误。
第三个条件与会话有关。系统的会话处理机制必须自动将用户的身份验证令牌包含在每次请求中,否则将存在漏洞。令牌可能是证书或 HTTP 基本身份验证凭据,但它们通常是会话 cookie。浏览器始终会将其拥有的目标域的所有 cookie(包括会话 cookie)附加到任何请求中。
攻击传递机制
一旦确定了满足所有三个条件的易受攻击的系统,攻击者就必须找到一种方法将恶意代码传送到用户的浏览器。如果用户在浏览器中阅读电子邮件,则将攻击代码直接隐藏在电子邮件中可能会奏效。或者电子邮件可能包含指向攻击者拥有的网站的链接,攻击者将代码隐藏在恶意服务器的页面中。无论哪种情况,如果用户打开电子邮件(第一种情况)或点击电子邮件中的链接(第二种情况),攻击代码就会到达用户的浏览器 – 只有当用户当前在目标网站上打开了有效会话时,攻击代码才可能成功。
POST 攻击
攻击很容易通过 POST 请求执行。假设攻击者观察到一个网站,该网站允许用户使用以下(简化的)形式向他人汇款:HTML
<form method="post" action="https://bankapp.com/transfer">
How much do you want to send?
<input name="amount" />
To whom do you want to send it?
<input name="receiver" />
<input type="submit" value="Send" />
</form>
攻击者在 BankApp 网站上以用户名 @evil 开设账户。攻击者撰写了两封单独的钓鱼邮件。第一封邮件假装来自 BankApp,内容是“警告!您的 BankApp 账户中检测到潜在欺诈行为。”第二封邮件内容是“您可能赢了 1,000 美元。”第一封邮件的目的是鼓励用户登录 BankApp。第二封邮件包含以下标记:HTML
<form method="post" action="https://bankapp.com/transfer">
<input name="amount" type="hidden" value="1000" />
<input name="recipient" type="hidden" value="@evil" />
</form>
<script>document.forms[0].submit();</script>
用户甚至无需点击任何内容。第二封电子邮件提交表单并在用户浏览器中呈现后立即执行攻击。攻击者使用这两封电子邮件向 100,000 名用户发送垃圾邮件。其中一些用户可能拥有 BankApp 帐户。其中一些用户会打开电子邮件,攻击者从每封电子邮件中获得 1000 美元。
服务器通过 HTTP 公开的任何状态更改都可能受到伪造请求的影响。CSRF 攻击可以更改密码、发布社交媒体帖子、删除帐户、配置路由器以及许多其他危险或破坏性的操作。
其他 HTTP 动词
BankApp 示例假设您必须通过 POST 表单来转账。并非所有 Web 端点都以这种方式编写。如果 BankApp 网站允许使用 GET 请求转账,那么攻击者的电子邮件可能只会这样做:HTML
<IMG src="http://bankapp.com/transfer?amount=1000&recipient=@evil">
当用户在钓鱼邮件中看到损坏的图像标签时,1000 美元就已经没了。
其他可改变状态的 HTTP 动词也可能被利用。只有 PATCH、POST、PUT 和 DELETE应允许更改状态,但如果 GET、HEAD 和 OPTIONS 也能进行更改,则可能存在漏洞。
登录CSRF
不要忽略登录页面上的 CSRF 警告。在登录时,即使用户尚未通过身份验证,CSRF 攻击仍然可能是一种威胁,尽管方法和风险与经过身份验证的 CSRF 不同。在登录 CSRF 攻击中,攻击者伪造请求,使用攻击者的登录凭据将用户登录到站点。受害者可能会认为自己已经登录了自己的帐户,从而留下攻击者可以使用的信息。针对 Google 和 Yahoo 的登录 CSRF 攻击可能会将受害者的搜索历史记录暴露给攻击者。针对 PayPal 等网站的登录攻击可能会诱使用户提供信用卡信息,然后攻击者可以登录并使用这些信息。
主要防御
CSRF 攻击依赖于能够预测浏览器请求的内容,从而伪造浏览器请求。针对 CSRF 的最强大防御方法是使每个请求的内容不可预测。基于令牌的防御依赖于随机生成的值来做到这一点。
同步器令牌
为了保护自己,服务器会向每个表单添加一个隐藏字段,为每个会话(或每个请求)分配一个不同的随机值。服务器会确认每个后续请求都具有此字段,并且其值与为会话分配的值相匹配。这称为同步器令牌模式。
加密令牌
同步器令牌要求服务器在服务器端存储随机会话值。如果您不想在服务器上保留状态,则可以使用只有服务器上才知道的私钥加密令牌值。加密值应该是时间戳。通过确认每个请求都包含隐藏字段、您可以解密字段值、解密值是时间戳以及时间戳是最近的(令牌未过期)来验证后续请求。
双重提交 Cookie
加密令牌是一种无状态的 CSRF 防御措施,而双重提交的 cookie 是另一种。在这种防御措施中,随机令牌值保存在客户端的 cookie 中。每个后续请求都必须在隐藏字段中包含相同的值。在每次请求中,服务器都会确认隐藏字段是否存在,并且其值与 cookie 中的值匹配。
为了确保这种防御的可靠性,您必须使用 HTTPS(无论如何都推荐使用)并确保您的子域也是安全的。
AJAX 中的反 CSRF 令牌
AJAX 调用发生在基于 Cookie 的浏览器会话上下文中,也容易受到 CSRF 攻击。AJAX 调用通常不会发布可以放置隐藏字段的表单。相反,它会在自定义 AJAX 标头中将 CSRF 令牌返回给服务器。JavaScript
var xhr = new XMLHttpRequest();
xhr.setRequestHeader(tokenname, tokenvalue);
将tokenname
和替换tokenvalue
为生成 AJAX 请求的页面中隐藏的随机令牌字段的名称和值。
使用自定义 HTTP 标头是一种防御措施,因为只有 JavaScript 可以创建自定义标头,并且浏览器的单源策略 (SOP) 会阻止跨站点 JavaScript 调用。
基于框架的防御
许多编程框架(例如Spring、Django、.NET和AngularJS )都带有基于令牌的 CSRF 防御的内置实现,这些实现可以生成加密性强的随机令牌值并在每次请求时对其进行验证。其他一些框架也有可用的附加组件:例如,适用于 Java 的OWASP CSRFGuard和适用于 PHP 的CSRFProtector 项目。正确实施基于令牌的防御可能很棘手,因此如果有人已经为您制定了可靠的实现,请不要自行构建。
次要防御
基于令牌的防御效果最好,但纵深防御方法需要考虑额外措施来阻止可能的 CSRF 攻击。
SameSite Cookie 属性
SameSite是一种 Cookie 属性,类似于Secure、HTTPOnly和Expires。SameSite 属性让网站设计者决定浏览器何时应在跨站点请求中包含 Cookie。SameSite 的值可能是Strict或Lax,任何一个值都会阻止至少一些 CSRF 请求。这是一种有用的防御措施,但仅靠它还不够,因为并非所有浏览器都支持它(大多数浏览器都支持),而且它可以被绕过。
原产地验证
要确认请求是否有效,请检查 HTTP 标头以确认来源和目标值是否匹配。要确定请求的来源,请检查 Origin 或 Referer 标头。将其与请求的目的地进行比较:目标来源。这些值无法伪造。只有浏览器才允许设置它们。
这种防御措施的优点是,甚至在身份验证之前就可以识别跨站点请求。但是,一小部分网络流量可能会出于正当理由忽略请求来源,并且如果您的应用程序位于代理后面,则确定目标来源并非易事。
重新认证
有时(尤其是对于高风险交易)直接让用户参与是缓解 CSRF 和其他伪造请求的有效方法。要求用户提供密码或一次性令牌,或者通过 CAPTCHA,可以提供有效的保护。
实用建议
这些提示将帮助您避免防御 CSRF 时常见的问题。
防御被打破
如果未正确实施,基于令牌的防御措施可能会被攻破。为了确保随机令牌值无法预测,应使用加密性强的随机数生成器来创建令牌值。此外,请谨慎使用将生成的值与请求中收到的值进行匹配的逻辑。验证逻辑中的错误导致GlassDoor 遭受 CSRF 攻击。在 GlassDoor 实施的某些情况下,检查失败会引发异常,但该异常仅被记录下来,代码会继续执行,就像验证成功一样。
此外,跨站点脚本漏洞 (XSS)可以击败任何 CSRF 保护。如果攻击者可以从用户的浏览器中检索令牌,即使是完美实施的基于令牌的 CSRF 防御也几乎无法提供保护。可靠的 XSS 防御是 CSRF 防御的先决条件。
虚假辩护
这些措施有时被视为缓解措施,但却不起作用:
多步骤交易
如果攻击者仍然可以预测事务的每个步骤,那么将改变服务器状态的操作分成多个 HTTP 请求是没有帮助的。
秘密cookie
创建攻击者无法预测的秘密 cookie 毫无用处,因为浏览器总是在每次请求时发回所有相关 cookie。攻击者无需猜测秘密 cookie。
HTTPS
单纯加密网络流量并不能阻止 CSRF 攻击。然而,对于其他一些缓解措施(例如双重提交的 cookie)而言,这是必要的步骤,并且在所有情况下都可取,以使其他措施更加可靠。
URL 重写
您可以在页面加载时修改 URL,从而消除会话 cookie,但这样做会在 URL 上暴露会话 ID,从而增加攻击者捕获会话 ID 的风险。
CSRF 与 XSS
跨站点脚本 (XSS) 漏洞与跨站点请求伪造 (CSRF) 漏洞具有一些共同的特征。两者都旨在在受害者的合法网络会话环境中运行恶意代码。然而,XSS 旨在将恶意代码直接注入易受攻击的页面,而 CSRF 通常依赖于社交工程(例如网络钓鱼电子邮件)将恶意代码放入受害者浏览器中的不相关页面。XSS 依赖于目标网站中的缺陷,这些缺陷允许注入恶意代码,服务器将把这些恶意代码作为其自身网页的一部分传递给受害者。
差异很大。恶意 XSS 脚本可以直接访问目标站点中的页面。事实上,XSS 漏洞可以向攻击者暴露页面上的所有内容,包括任何反 CSRF 令牌值。即使是完美的防御措施也几乎无法为易受 XSS 攻击的站点提供 CSRF 保护。
测试 CSRF
漏洞测试是任何安全程序的基本组成部分。扫描工具能够发现许多 CSRF(和 XSS)漏洞。鉴于此类漏洞的潜在严重性,任何拥有 Web 应用程序的人都应系统地测试它们 – 最好将自动扫描作为 CI/CD 管道的一部分。漏洞测试需要遵守最佳实践和标准,例如 NIST SP 800-53 和 ISO 27001。高风险应用程序还应在其安全程序中包括手动渗透测试。