当前位置: 代码迷 >> 综合 >> SpringSecurity------CSRF跨站请求伪造(三)
  详细解决方案

SpringSecurity------CSRF跨站请求伪造(三)

热度:55   发布时间:2023-12-20 23:15:35.0

SpringSecurity------CSRF跨站请求伪造(三)

    • 一、什么是CSRF攻击?
    • 二、防止CSRF攻击
      • 1、安全方法必须是幂等的
      • 2、同步Token模式(Synchronizer Token Pattern)
      • 3、SameSite Attribute
    • 三、什么时候使用CSRF防护机制
      • 1、CSRF防护和JSON
      • 2、CSRF和无状态浏览器应用程序
    • 四、CSRF注意事项
      • 1、登录和登出
      • 2、CSRF和Session超时
      • 3、Multipart (文件上传)
      • 4、HiddenHttpMethodFilter

一、什么是CSRF攻击?

用一个具体的例子可以明确的说明什么是CSRF跨站请求伪造。假定一个银行网页提供了一个转账表单,登录用户可以执行转账,表单如下:

<form method="post"action="/transfer">
<input type="text"name="amount"/>
<input type="text"name="routingNumber"/>
<input type="text"name="account"/>
<input type="submit"value="Transfer"/>
</form>

相应的转账报文如下:

POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencodedamount=100.00&routingNumber=1234&account=9876

假如现在你登录了自己的银行账户而且没有登出,同时你访问到一个非法网站,非法网站存在如下表单:

<form method="post"action="https://bank.example.com/transfer">
<input type="hidden"name="amount"value="100.00"/>
<input type="hiddenname="routingNumber"value="evilsRoutingNumber"/>
<input type="hidden"name="account"value="evilsAccountNumber"/>
<input type="submit"value="Win Money!"/>
</form>

当你开始执行转账的时候,无意之间就会向非法账户转入100元。虽然非法网站不能获取到你的cookies信息,但是登录银行网站关联的的cookies信息也会通过非法请求传递到银行系统。更为严重的是这些操作可以完全通过JavaScript自动的完成。

二、防止CSRF攻击

通过正常网站和非法网站发送的HTTP请求确切的来说是一样的,这就是为什么会出现CSRF攻击的原因。为了阻止CSRF攻击,我们需要保证在发送HTTP请求时附带上一些非法网站不能提供的信息以便区分是正常请求和非法请求。Spring提供了两种机制来预防CSRF攻击:

  • 同步Token模式(Synchronizer Token Pattern)
  • 在Session Cookie中指定SameSite Attribute

1、安全方法必须是幂等的

要想使以上两种防止CSRF攻击的方式生效,应用程序需要保证GET, HEAD, OPTIONS和TRACE类型的HTTP请求必须是幂等的(不能改变应用程序的状态)。

注意:幂等性是数学中的一个概念,表达的是N次变换与1次变换的结果相同

2、同步Token模式(Synchronizer Token Pattern)

同步Token模式是防止CSRF最有力的办法。在我们发送HTTP请求时,除了使用Session Cookie之外,还需要生成一个安全的随机CSRF Token放入HTTP请求中。当HTTP请求提交之后,服务器会对比预期CSRF Token和请求中的CSRF Token,如果值不匹配,HTTP请求就会被拒绝。我们可以将CSRF Token作为请求参数或是设置到HTTP header中 ,但是将CSRF Token设置到cookie中是不能有效的预防CSRF攻击,因为cookies会被浏览器自动发送。

不是所有的HTTP请求都需要附加传输CSRF Token,只有那些用于更新应用程序的HTTP请求需要使用CSRF Token。这样可以改善我们的网站环境,可以在我们的网站上使用外部链接而不被CSRF防御机制阻拦。不建议在GET请求中使用随机的token,因为GET请求很容易导致tokens泄露。

同步Token模式生效原因。假设在一个HTTP请求中需要使用一个名称为 _csrf的CSRF Token参数,提交表单如下:

<form method="post"action="/transfer">
<input type="hidden"name="_csrf"value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<input type="text"name="amount"/>
<input type="text"name="routingNumber"/>
<input type="hidden"name="account"/>
<input type="submit"value="Transfer"/>
</form>

目前这个表单包含一个隐藏的CSRF Token文本域,外部的网站是无法读取到这个CSRF Token值的,同时同源策略(same origin policy)保证了非法网站不能读取到响应结果。

相应的转账报文如下:

POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencodedamount=100.00&routingNumber=1234&account=9876&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721

现在HTTP请求包含了一个安全的随机参数_csrf,非法网站没法正确提供这个_csrf参数值,所以在服务器比对的时候就能鉴别出正常访问和非法访问了。

3、SameSite Attribute

一种新型的防止CSRF攻击的方法是在Session Cookie中指定SameSite参数。服务器可以设定SameSite属性,保证Session Cookie不能被发送到外部网站。附带SameSite参数的HTTP响应头如下所示:

Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax

注意:Spring Security不直接控制Session Cookie的创建,因此它不提供对SameSite属性的支持。Spring Session在基于servlet的应用程序中提供对SameSite属性的支持。

SameSite的有效值包括:

  • Strict:任何来自同一个网站的请求都会包含cookie,否则cookie将不会被包含在HTTP请求中。

  • Lax:来自同一个网站或请求来自顶级导航(top-level navigations )或请求方法是幂等的,这三种情况下请求都会包含cookie,否则cookie将不会被包含在HTTP请求中。

注意:top-level navigations

请添加图片描述

如果在Session Cookie中设置了SameSite属性,浏览器就不会向来自非法网站的转发请求(transfer request)发送JSESSIONID了。如果非法网站的转发请求中不会被设置session,那么应用就可以阻止CSRF攻击了。当使用SameSite时,有以下一些需要注意的重要事项:

  • 如果将SameSite属性设置成Strict,这确实提供了一种强力的防御措施,但是同时也会给用户带来困扰。细想,一个用户登录一个社交网站,主机地址为https://social.example.com。这个用户从https://email.example.org收到一份电子邮件,在这个电子邮件中包含一个指向社交网站的连接,如果用户点击这个连接,他所期望的是能够正当地跳转到社交网站页面。然而,一旦将SameSite属性设置成Strict,Session Cookie就不会被发送,那么用户就不会被授权成功。

  • 另外一个需要考虑的事情就是,浏览器必须要支持SameSite属性。很多现在的新版浏览器是支持这个属性的,但是有一些旧版的浏览器是不支持的。

所以,使用SameSite作为CSRF攻击的深度防御机制,而不是作为CSRF防御的唯一机制。

三、什么时候使用CSRF防护机制

我们建议在任何需要客户使用浏览器操作的请求都需要使用CSRF防护机制。如果你仅仅是创建一个服务不使用浏览器客户端,那么可能就不需要使用CSRF防护了。

1、CSRF防护和JSON

“我们需不需要保护使用javascript发送的JSON请求呢?”,这需要根据具体情况来确定。你需要注意的是,还是有一些CSRF漏洞会被用于JSON请求的。比如恶意用户可以使用以下的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"
}

如果一个应用程序没有验证Content-Type,那么它就会暴露出这个漏洞。即使Spring MVC应用程序会校验Content-Type,同样可以通过修改URL后缀来利用漏洞,比如将URL改为以.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>

2、CSRF和无状态浏览器应用程序

如果我的应用程序是无状态的呢?这并不一定意味着你受到了保护。事实上,如果用户不需要在web浏览器中对给定的请求执行任何操作,他们仍然可能容易受到CSRF攻击。

例如,考虑使用自定义cookie(其中包含用于身份验证的所有状态,而不是JSESSIONID)的应用程序。当进行CSRF攻击时,自定义cookie将与请求一起发送,发送方式与我们前面的示例中发送JSESSIONID cookie的方式相同。此应用程序容易受到CSRF攻击。

使用基本身份验证(basic authentication)的应用程序也容易受到CSRF攻击。应用程序很容易受到攻击,因为浏览器会在任何请求中自动包含用户名和密码,其方式与上一个示例中发送JSESSIONID cookie的方式相同。

四、CSRF注意事项

当你需要实现CSRF防御时,有以下几个注意事项:

1、登录和登出

为了防止伪造登录(退出)请求,登录(退出)HTTP请求应该被保护免受CSRF攻击。防止伪造登录(退出)请求是必要的,这样恶意用户就无法读取受害者的敏感信息。攻击方式如下:

  • 非法用户使用非法凭证执行CSRF登录,导致正常用户被认证为恶意用户
  • 然后,非法用户欺骗正常用户访问被非法网站并输入敏感信息
  • 这些敏感信息与非法用户的帐户相关联,因此非法用户可以使用自己的凭据登录并查看受害者的敏感信息

要确保登录(退出)HTTP请求免受CSRF攻击,一个可能的复杂情况是,用户可能会经历会话超时,导致请求被拒绝。

2、CSRF和Session超时

通常情况下,预期的CSRF Token存储在会话中。这意味着一旦会话过期,服务器就不会找到预期的CSRF令牌并拒绝HTTP请求。有许多解决超时的选项,每个选项都有利弊

  • 减轻超时的最好方法是在表单提交时使用JavaScript请求CSRF Token,然后使用CSRF Token更新表单并提交
  • 使用JavaScript,让用户知道他们的会话即将到期,用户可以单击按钮继续并刷新会话
  • 预期的CSRF Token可以存储在一个cookie中,这可以使得CSRF Token比会话更持久

3、Multipart (文件上传)

为了防止CSRF攻击的发生,必须读取HTTP Request Body 以获得实际的CSRF Token。然而,读取Request Body意味着文件将被上传,这意味着外部站点可以上传文件。下面有两种选择用于阻止multipart/form-data请求时出现CSRF攻击:

将CSRF Token放在Request Body

将CSRF Token放在Request Body中,在执行鉴权之前读取CSRF Token,只有经过授权的用户才能提交由服务器处理的文件,其他用户只能上传临时文件,临时文件上传对大多数服务器的影响可以忽略不计。

将CSRF Token放在URL中

如果不允许未经授权的用户上传临时文件,那么另一种方法是在表单的action属性中包含CSRF Token作为查询参数。这种方法的缺点是查询参数可能被泄漏。最好的办法是将敏感数据放置在Request Body或Headers中,以确保其不泄漏。

4、HiddenHttpMethodFilter

在某些应用程序中,可以使用表单参数来覆盖HTTP方法。例如,可以使用下面的表单将HTTP方法视为delete而不是post。

<form action="/process"method="post"><!-- ... --><input type="hidden"name="_method"value="delete"/>
</form>

浏览器form表单只支持GET与POST请求,而DELETE、PUT等method并不支持,Spring3.0添加了一个过滤器,可以将这些请求转换为标准的http方法,使得支持GET、POST、PUT与DELETE请求,对应的读取_method值:

package org.springframework.web.filter;import java.io.IOException;
import java.util.Locale;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.WebUtils;public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    public static final String DEFAULT_METHOD_PARAM = "_method";private String methodParam = DEFAULT_METHOD_PARAM;public void setMethodParam(String methodParam) {
    Assert.hasText(methodParam, "'methodParam' must not be empty");this.methodParam = methodParam;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {
    HttpServletRequest requestToUse = request;if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
    String paramValue = request.getParameter(this.methodParam);if (StringUtils.hasLength(paramValue)) {
    requestToUse = new HttpMethodRequestWrapper(request, paramValue);}}filterChain.doFilter(requestToUse, response);}private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
    private final String method;public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
    super(request);this.method = method.toUpperCase(Locale.ENGLISH);}@Overridepublic String getMethod() {
    return this.method;}}
}

重写这个过滤器的HTTP方法,注意重写的只有POST方式的请求,该过滤器要添加在Spring Security过滤器之前。

上一篇:SpringSecurity------密码存储PasswordEncoder接口(二)
下一篇:SpringSecurity------Security相关的HTTP Response Headers(四)

  相关解决方案