Spring官方提供【CSRF攻击】解决方案
步入正文
Cookie
cookie是我们常见用来保存用户态信息,cookie跟随我们的请求自动携带。在同一域名下的请求,cookie总是自动携带。
用户态: 当前登入者的用户信息
以上的特性会导致一个潜在漏洞-CSRF
CSRF
CSRF一般指跨站请求伪造。 跨站请求伪造(英语:Cross-site request forgery),下图举一个例子:
危险漏洞出现在步骤四:此时网站B请求了网站A,又因为cookie的特性(会将一二步骤使用的Cookie)导致请求成功,此时的转账是成功的。
就此出现了CSRF( 跨站请求伪造)漏洞。
解决方案
Spring 提供了两种机制来防止 CSRF 攻击:
- 同步器令牌模式
- 在会话 Cookie 上指定同一网站属性
这两种保护都要求安全方法是幂等的。
1.同步器令牌模式
防范 CSRF 攻击的主要和最全面的方法是使用同步器令牌模式。 此解决方案是为了确保除了我们的会话 cookie 之外,每个 HTTP 请求还需要在 HTTP 请求中存在一个称为 CSRF 令牌的安全随机生成值。
提交 HTTP 请求时,服务器必须查找预期的 CSRF 令牌,并将其与 HTTP 请求中的实际 CSRF 令牌进行比较。 如果值不匹配,则应拒绝 HTTP 请求。
这项工作的关键是实际的CSRF令牌应该在HTTP请求的一部分中,浏览器不会自动包含该部分。 例如,在 HTTP 参数或 HTTP请求头中要求实际的 CSRF 令牌将防止 CSRF 攻击。 在 cookie 中要求实际的 CSRF 令牌是行不通的,因为浏览器会自动将 cookie 包含在 HTTP 请求中。
我们可以放宽期望,为每个更新应用程序状态的 HTTP (PUT,POST,DELETE)请求仅要求实际的 CSRF 令牌。 为此,我们的应用程序必须确保安全的 HTTP 方法是幂等的。 这提高了可用性,因为我们希望允许从外部网站链接到我们的网站。 此外,我们不希望在 HTTP GET 中包含随机令牌,因为这可能会导致令牌泄露。
考虑一下使用同步器令牌模式时我们的示例将如何变化。 假设实际的 CSRF 令牌需要位于名为 的 HTTP 参数中。 我们应用程序的转移表单如下所示:
2. 同网站属性
防止CSRF攻击的一种新兴方法是在cookie上指定SameSite属性。 服务器可以在设置 Cookie 时指定属性,以指示来自外部站点时不应发送 Cookie。
具有该属性的 HTTP 响应标头的示例可能如下所示:
Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax
该属性的有效值为:SameSite
- Strict:指定后,来自同一站点的任何请求都包含 cookie。 否则,Cookie 不会包含在 HTTP 请求中。
- Lax:指定后,当来自同一站点或请求来自顶级导航且方法幂等时,将发送 Cookie。 否则,Cookie 不会包含在 HTTP 请求中。
在我们的会话 Cookie 上设置该属性后,浏览器会继续发送来自银行网站的请求的 Cookie。 但是,浏览器不再发送带有来自邪恶网站的传输请求的cookie。 由于会话不再存在于来自恶意网站的传输请求中,因此应用程序受到保护,免受CSRF攻击。
另一个明显的考虑因素是,为了使属性保护用户,浏览器必须支持该属性。 大多数现代浏览器都支持 SameSite 属性。 但是,仍在使用的旧版浏览器可能不会。
出于这个原因,我们通常建议将该属性用作深度防御,而不是针对 CSRF 攻击的唯一保护。
何时使用 CSRF 保护
何时应使用 CSRF 保护? 我们的建议是对普通用户可以通过浏览器处理的任何请求使用 CSRF 保护。 如果要创建仅由非浏览器客户端使用的服务(Frame等嵌套),则可能需要禁用 CSRF 保护。
CSRF 保护和 JSON
一个常见的问题是“我需要保护JavaScript发出的JSON请求吗? 简短的回答是:视情况而定。 但是,您必须非常小心,因为存在会影响 JSON 请求的 CSRF 漏洞。 例如,恶意用户可以使用以下形式使用 JSON 创建 CSRF:
<form action="https://bank.example.com/transfer" method="post" enctype="text/plain">
<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
<input type="submit"
value="Win Money!"/>
</form>
这将生成以下 JSON 结构
{ "amount": 100,
"routingNumber": "evilsRoutingNumber",
"account": "evilsAccountNumber",
"ignore_me": "=test"
}
如果应用程序未验证header,则会暴露在此攻击中。 根据设置的不同,验证内容类型的Spring MVC应用程序仍可以通过将URL后缀更新为以 结尾来利用,如下所示:Content-Type.json
<form action="https://bank.example.com/transfer.json" method="post" enctype="text/plain">
<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
<input type="submit"
value="Win Money!"/>
</form>
CSRF 和无状态浏览器应用程序
如果我的应用程序是无状态的怎么办? 这并不一定意味着您受到保护。 事实上,如果用户不需要在 Web 浏览器中对给定的请求执行任何操作,他们可能仍然容易受到 CSRF 攻击。
例如,考虑一个应用程序,它使用自定义 cookie,其中包含用于身份验证的所有状态(而不是 JSESSIONID)。 当进行 CSRF 攻击时,自定义 cookie 与请求一起发送的方式与前面示例中发送 JSESSIONID cookie 的方式相同。 此应用程序容易受到 CSRF 攻击。
使用基本身份验证的应用程序也容易受到 CSRF 攻击。 该应用程序容易受到攻击,因为浏览器会自动在任何请求中包含用户名和密码,其方式与上一个示例中发送 JSESSIONID cookie 的方式相同。
CSRF 注意事项
在实施针对 CSRF 攻击的保护时,需要考虑一些特殊注意事项。
登录
为了防止伪造登录请求,应保护登录 HTTP 请求免受 CSRF 攻击。 防止伪造登录请求是必要的,以便恶意用户无法读取受害者的敏感信息。 攻击的执行方式如下:
伪造登录请求:攻击者可能会伪造请求,使用攻击者的凭据将受害者登录到目标网站;这称为登录 CSRF。登录CSRF使各种新颖的攻击成为可能;例如,攻击者稍后可以使用其合法凭据登录站点,并查看私人信息,例如已保存在帐户中的活动历史记录。这种攻击已经针对谷歌[12]和雅虎进行了演示。[注13]
- 恶意用户使用恶意用户的凭据执行 CSRF 登录。 受害者现在被验证为恶意用户。
- 然后,恶意用户诱骗受害者访问受感染的网站并输入敏感信息。
- 该信息与恶意用户的帐户相关联,因此恶意用户可以使用自己的凭据登录并查看受害者的敏感信息。
确保登录 HTTP 请求免受 CSRF 攻击的一个可能的复杂性是,用户可能会遇到会话超时,从而导致请求被拒绝。 会话超时对于不希望需要会话才能登录的用户来说令人惊讶。 有关更多信息,请参阅 CSRF 和会话超时。
注销
为了防止伪造注销请求,应保护注销 HTTP 请求免受 CSRF 攻击。 防止伪造注销请求是必要的,以便恶意用户无法读取受害者的敏感信息。 有关攻击的详细信息,请参阅此博客文章。
确保注销 HTTP 请求免受 CSRF 攻击的一个可能的复杂性是,用户可能会遇到导致请求被拒绝的会话超时。 会话超时对于不希望有会话注销的用户来说令人惊讶。 有关更多信息,请参阅 CSRF 和会话超时。
CSRF 和会话超时
通常,预期的CSRF令牌存储在会话中。 这意味着,一旦会话过期,服务器就找不到预期的 CSRF 令牌并拒绝 HTTP 请求。 有许多选项(每个选项都有权衡)来解决超时问题:
- 缓解超时的最佳方法是使用 JavaScript 在表单提交时请求 CSRF 令牌。 然后,使用CSRF令牌更新表单并提交。
- 另一种选择是使用一些 JavaScript,让用户知道他们的会话即将过期。 用户可以单击按钮以继续并刷新会话。
- 最后,预期的CSRF令牌可以存储在cookie中。 这让预期的 CSRF 令牌比会话更长久。
有人可能会问,为什么预期的CSRF令牌默认情况下不存储在cookie中。 这是因为存在已知的漏洞,其中标头(例如,指定 cookie)可由另一个域设置。 这与Ruby on Rails在标头X-Request-With存在时不再跳过CSRF检查的原因相同。 有关如何执行漏洞利用的详细信息,请参阅此 webappsec.org 线程。 另一个缺点是,通过删除状态(即超时),您将失去在令牌遭到入侵时强制使令牌无效的能力。
分段(文件上传)
保护分段请求(文件上传)免受 CSRF 攻击会导致先有鸡还是先有蛋的问题。 为了防止CSRF攻击的发生,必须读取HTTP请求的正文以获取实际的CSRF令牌。 但是,读取正文意味着文件已上传,这意味着外部站点可以上传文件。
对多部分/表单数据使用 CSRF 保护有两种选择:
- 将CSRF令牌放入正文
- 将 CSRF 令牌放在 URL 中
每个选项都有其权衡取舍。
在将 Spring Security 的 CSRF 保护与分段文件上传集成之前,您应该首先确保可以在没有 CSRF 保护的情况下进行上传。 有关在 Spring 中使用多部分表单的更多信息,请参见 1.1.11。Spring 参考的多部分解析器部分和 MultipartFilter Javadoc。
将CSRF令牌放入正文
第一个选项是在请求正文中包含实际的 CSRF 令牌。 通过将 CSRF 令牌放在正文中,可以在执行授权之前读取正文。 这意味着任何人都可以在您的服务器上放置临时文件。 但是,只有授权用户才能提交由您的应用程序处理的文件。 通常,这是推荐的方法,因为临时文件上载对大多数服务器的影响可以忽略不计。
在 URL 中包含 CSRF 令牌
如果不允许未经授权的用户上传临时文件,另一种方法是在表单的操作属性中包含预期的 CSRF 令牌作为查询参数。 此方法的缺点是查询参数可能会泄漏。 更一般地说,最佳做法是将敏感数据放在正文或标头中,以确保它不会泄露。 您可以在 RFC 2616 第 15.1.3 节 URI 中的敏感信息编码中找到其他信息。
隐藏的HttpMethodFilter
某些应用程序可以使用表单参数来重写 HTTP 方法。 例如,以下表单可以将 HTTP 方法视为 而不是 .deletepost
CSRF 隐藏的 HTTP 方法表单
<form action="/process"
method="post">
<!-- ... -->
<input type="hidden"
name="_method"
value="delete"/>
</form>
重写 HTTP 方法发生在筛选器中。 该过滤器必须放在 Spring Security 的支持之前。 请注意,覆盖只发生在 上,因此这实际上不太可能导致任何实际问题。 但是,最佳做法仍然是确保将其放置在 Spring 安全性的过滤器之前。post
本文
主要来源
https://docs.spring.io/spring-security/reference/features/exploits/csrf.html#csrf-when-stateless
https://docs.spring.io/spring-security/reference/features/exploits/csrf.html#csrf-when-stateless
https://docs.spring.io/spring-security/reference/features/exploits/csrf.html#csrf-when-stateless