10.6 Cross Site Request Forgery (CSRF)

10.6.1 CSRF Attacks

在讨论Spring Security 如何保护应用程序免受CSRF攻击之前,我们将解释什么是CSRF攻击。让我们看一个具体的例子,以便更好地理解。

假设您的银行网站提供了一个表单,允许将资金从当前登录的用户转移到另一个银行帐户。例如,HTTP请求可能如下所示:

POST /transfer HTTP/1.1

Host: bank.example.com

Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly

Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876

现在假设你通过了银行网站的身份验证,然后,不注销,访问一个邪恶的网站。邪恶网站包含一个HTML页面,其格式如下:

你喜欢赢钱,所以你点击提交按钮。在此过程中,您无意中将100美元转移给了恶意用户。这是因为,虽然邪恶的网站看不到您的cookie,但与您的银行相关联的cookie仍然随请求一起发送。

最糟糕的是,整个过程可以使用JavaScript实现自动化。这意味着你甚至不需要点击按钮。那么,我们如何保护自己免受这种攻击呢?

10.6.2 Synchronizer Token Pattern

问题是,来自银行网站的HTTP请求和来自邪恶网站的请求完全相同。这意味着无法拒绝来自邪恶网站的请求只允许来自银行网站的请求。为了防止CSRF攻击,我们需要确保邪恶站点无法提供请求中的某些内容。

一种解决方案是使用  Synchronizer Token Pattern。这个解决方案是为了确保除了会话cookie之外,每个请求都需要一个随机生成的令牌作为HTTP参数。提交请求时,服务器必须查找参数的预期值,并将其与请求中的实际值进行比较。如果值不匹配,则请求应失败。

我们可以放宽期望,只需要更新状态的每个HTTP请求的令牌。这可以安全地完成,因为同源策略确保邪恶的站点无法读取响应。此外,我们不希望在HTTP GET中包含随机令牌,因为这可能导致令牌泄漏。

让我们看看我们的示例将如何改变。假设随机生成的令牌存在于名为 _csrf 的HTTP参数中。例如,转账请求如下:

POST /transfer HTTP/1.1

Host: bank.example.com

Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly

Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876&_csrf=

您会注意到我们使用随机值添加了_csrf参数。现在邪恶网站无法猜测CSRF参数的正确值(必须在邪恶网站上明确提供),当服务器将实际令牌与预期令牌进行比较时,传输将失败。

10.6.3 When to use CSRF protection

什么时候应该使用CSRF保护?我们的建议是对正常用户可以通过浏览器处理的任何请求使用CSRF保护。如果您只创建一个非浏览器客户端使用的服务,那么您可能希望禁用CSRF保护。

CSRF protection and JSON

一个常见的问题是,“我需要保护JavaScript发出的JSON请求吗?”简而言之,这要看情况而定。但是,您必须非常小心,因为存在可能影响JSON请求的CSRF漏洞。例如,恶意用户可以使用以下表单使用JSON创建CSRF:

    value="Win Money!"/>

这将生成以下JSON结构

"amount": 100,

"routingNumber": "evilsRoutingNumber",

"account": "evilsAccountNumber",

"ignore_me": "=test"

}

如果应用程序没有验证 Content-Type,那么它将暴露于此漏洞中。根据设置的不同,验证 Content-Type 的SpringMVC应用程序仍然可以通过更新url后缀以“.json”结尾来利用,如下所示:

    value="Win Money!"/>

CSRF and Stateless Browser Applications

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

例如,假设一个应用程序使用一个自定义cookie,该cookie包含它内部的所有状态来进行身份验证,而不是JSessionID。当进行CSRF攻击时,定制cookie将与请求一起发送,发送方式与上一个示例中发送JSessionID cookie的方式相同。

使用 basic 认证的用户也容易受到CSRF攻击,因为浏览器将自动在任何请求中包含用户名密码,其方式与我们上一个示例中发送JSessionID cookie的方式相同。

10.6.4 Using Spring Security CSRF Protection

那么,使用Spring Security保护我们的站点免受CSRF攻击所必需的步骤是什么?使用Spring Security的CSRF保护的步骤概述如下:    

 Use proper HTTP verbs

 Configure CSRF Protection

 Include the CSRF Token

Use proper HTTP verbs 使用正确的HTTP动词

防止CSRF攻击的第一步是确保您的网站使用正确的HTTP动词。具体来说,在Spring Security的CSRF支持可以使用之前,您需要确保您的应用程序正在使用  PATCH、POST、PUT和/或 DELETE 来修改状态。

这不是Spring Security支持的限制,而是正确预防CSRF的一般要求。原因是在HTTP GET中包含私有信息会导致信息泄漏。请参阅  RFC 2616 Section 15.1.3 Encoding Sensitive Information in URI’s ,以获取有关使用POST而不是GET获取敏感信息的一般指导。

Configure CSRF Protection 配置CSRF保护

下一步是在你的应用程序中引入Spring Security 的CSRF保护。一些框架通过使用户的会话无效来处理无效的CSRF令牌,但这会导致自身的问题。相反,默认情况下,Spring Security的CSRF保护将导致HTTP 403访问被拒绝。这可以通过配置 AccessDeniedHandler 以不同方式处理 InvalidCsrfTokenException 来定制。

从Spring Security 4.0开始,CSRF保护默认通过XML配置启用。如果要禁用CSRF保护,可以在下面看到相应的XML配置。

        

        

CSRF保护在使用Java配置时默认启用。如果您想禁用CSRF,可以看下面相应的Java配置。有关CSRF保护的其他自定义配置,请参阅csrf()的javadoc。

@EnableWebSecurity

public class WebSecurityConfig extends

WebSecurityConfigurerAdapter {

    @Override

    protected void configure(HttpSecurity http) throws Exception {

        http

            .csrf().disable();

    }

}

Include the CSRF Token

Form Submissions 表单提交

最后一步是确保在所有 PATCH, POST, PUT 和 DELETE  方法中都包含CSRF令牌。一种方法是使用 _csrf 请求属性获取当前 CsrfToken 。使用JSP执行此操作的示例如下所示:

        

        

一种更简单的方法是使用SpringSecurityJSP标记库中的 the csrfInput tag 标记。

如果您使用的是spring mvc tag或 Thymeleaf 2.1+,并且使用的是 @enableWebSecurity,则会自动为你包含 CsrfToken  (使用 CsrfRequestDataValueProcessor )。

Ajax and JSON Requests

如果你正在使用JSON,那么就不可能在HTTP参数中提交CSRF令牌。相反,您可以在HTTP头中提交令牌。典型的模式是在 中包含CSRF令牌。带有JSP的示例如下所示:

   

   

   

   

你可以使用Spring Security JSP标记库中更简单的  csrfMetaTags tag,而不是手动创建meta标记。

然后可以在所有Ajax请求中包含该令牌。如果您使用的是jquery,则可以通过以下方法完成:

$(function () {

    var token = $("meta[name='_csrf']").attr("content");

    var header = $("meta[name='_csrf_header']").attr("content");

    $(document).ajaxSend(function(e, xhr, options) {

        xhr.setRequestHeader(header, token);

    });

});

作为jquery的替代方案,我们建议使用cujojs的rest.js。js模块为以RESTful方式处理HTTP请求和响应提供了高级支持。核心功能是通过将拦截器链接到客户机来根据需要添加行为,从而将HTTP客户机上下文化的能力。

var client = rest.chain(csrf, {

token: $("meta[name='_csrf']").attr("content"),

name: $("meta[name='_csrf_header']").attr("content")

});

配置的客户端可以与应用程序的任何组件共享,这些组件需要向受CSRF保护的资源发出请求。rest.js和jquery之间的一个显著区别是,只有使用配置的客户端进行的请求才会包含CSRF令牌,而jquery中的所有请求都将包含该令牌。确定接收令牌的请求范围的能力有助于防止CSRF令牌泄漏给第三方。有关rest.js的更多信息,请参阅rest.js参考文档。

CookieCsrfTokenRepository

在某些情况下,用户可能希望在cookie中保留 CsrfToken 。默认情况下,CookieCsrfTokenRepository 将在cookie中写入名为 XSRF-TOKEN 的 token,并从名为 X-XSRF-TOKEN 的头或http参数 _csrf 读取该 token。这些默认值来自AngularJS

可以使用以下方法在XML中配置 CookieCsrfTokenRepository 

   

   

    class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"

    p:cookieHttpOnly="false"/>

示例显式设置 cookieHttpOnly=false。这是允许JavaScript(即AngularJS)读取它所必需的。如果不需要直接使用javascript读取cookie,建议省略  cookieHttpOnly=false 以提高安全性。

10.6.5 CSRF Caveats

在实施CSRF时有一些注意事项。

Timeouts

一个问题是预期的CSRF令牌存储在 HttpSession 中,因此一旦 HttpSession 过期,配置的 AccessDeniedHandler 将收到一个无效的 InvalidCsrfTokenException。如果您使用的是默认的 AccessDeniedHandler,浏览器将得到一个HTTP403并显示一条错误消息。

有人可能会问,为什么默认情况下预期的 CsrfToken  没有存储在cookie中。这是因为存在已知的漏洞,其中头(即指定cookie)可以由另一个域设置。这也是RubyonRails在存在头X-requested-with时不再跳过CSRF检查的原因。有关如何执行该漏洞攻击的详细信息,请参阅此webappsec.org线程。另一个缺点是,通过删除状态(即超时),您将失去在令牌受到威胁时强制终止令牌的能力。

缓解活动用户超时的一个简单方法是使用一些JavaScript让用户知道他们的会话即将到期。用户可以单击按钮继续并刷新会话。

或者,指定一个自定义的accessdeniedHandler允许您以任何方式处理invalidCSRFtokenException。有关如何自定义AccessDeniedHandler 的示例,请参阅所提供的XML和Java配置的链接。

最后,可以将应用程序配置为使用不会过期的 CookicsrftokenRepository。如前所述,这并不像使用会话那样安全,但在许多情况下,这已经足够好了。

Logging In

为了防止伪造登录请求,登录表单也应防止CSRF攻击。由于 CsrfToken 存储在 HttpSession 中,这意味着一旦访问CsrfToken 属性,就会创建 HttpSession 。虽然这在一个RESTful/无状态的体系结构中听起来很糟糕,但事实是状态对于实现真正的安全是必要的。没有状态,一旦token被破坏我们就无能为力。实际上,CSRF令牌的大小非常小,对我们的体系结构的影响应该可以忽略不计。

保护登录表单的常见技术是在表单提交之前使用javascript函数获取有效的CSRF令牌。通过这样做,无需考虑会话超时(在上一节中讨论),因为会话是在表单提交之前创建的(假设没有配置 CookieCsrfTokenRepository),因此用户可以留在登录页面上,并在需要时提交用户名/密码。为了实现这一点,您可以利用Spring Security 提供的CsrfTokenArgumentResolver ,并像这里描述的那样公开一个端点。

Logging Out

启动了CSRF,将更新 LogoutFilter 只能使用 HTTP POST(不启用CSRF时,GET请求也是可以正常退出的,因为其不需要CSRF token)。这可以确保注销需要CSRF令牌,并且恶意用户无法强制注销您的用户。

一种方法是使用表单注销。如果你真的想要一个链接,你可以使用javascript让链接执行一个POST(也就是说,可能在一个隐藏的表单上)。对于禁用了javascript的浏览器,您可以让链接把用户带到一个注销的确认页面(POST提交)。

如果您真的想在注销时使用HTTP GET,可以这样做,但请记住,通常不建议这样做。例如,下面的Java配置将执行注销,URL  /logout 是用任何HTTP方法请求的:

@EnableWebSecurity

public class WebSecurityConfig extends

WebSecurityConfigurerAdapter {

    @Override

    protected void configure(HttpSecurity http) throws Exception {

        http

            .logout()

                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"));

    }

}

Multipart (file upload)

multipart/form-data 使用CSRF保护有两个选项。每个选项都有其权衡。

 Placing MultipartFilter before Spring Security

 Include CSRF token in action

在将Spring Security的CSRF保护与多部分文件上传集成之前,请确保您可以先上传而不需要CSRF保护。有关将多部分表单与Spring一起使用的更多信息,可以在 17.10 Spring’s multipart (file upload) support 章节和 MultipartFilter javadoc 中找到。

Placing MultipartFilter before Spring Security

第一个选项是确保在Spring Security 过滤器之前指定 MultipartFilter 。在Spring安全过滤器之前指定 MultipartFilter  意味着没有调用 MultipartFilter  的授权,这意味着任何人都可以在服务器上放置临时文件。但是,只有授权用户才能提交由您的应用程序处理的文件。一般来说,这是推荐的方法,因为临时文件上载对大多数服务器都会产生不可忽略的影响。

为了确保在使用Java配置的Spring Security 过滤器之前指定 MultipartFilter ,用户可以覆盖beforeSpringSecurityFilterChain ,如下所示:

public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

    @Override

    protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {

        insertFilters(servletContext, new MultipartFilter());

    }

}

为了确保 MultipartFilter 在使用xml配置的spring Security 过滤器之前被指定,用户可以确保  MultipartFilter  元素放在web.xml中的 springSecurityFilterChain 之前,如下所示:

    MultipartFilter

    org.springframework.web.multipart.support.MultipartFilter

    springSecurityFilterChain

    org.springframework.web.filter.DelegatingFilterProxy

    MultipartFilter

    /*

    springSecurityFilterChain

    /*

Include CSRF token in action

如果不允许未经授权的用户上传临时文件,则可以将 MultipartFilter 放在spring Security 过滤器之后,并将 CSRF 作为查询参数包含在表单的action属性中。下面是一个带有JSP的示例

这种方法的缺点是可以泄漏查询参数。一般来说,最好的做法是将敏感数据放在主体或头中,以确保不会泄漏。其他信息可在 RFC 2616 Section 15.1.3 Encoding Sensitive Information in URI’s.中查找。

HiddenHttpMethodFilter

HiddenHttpMethodFilter 应该放在Spring Security过滤器之前。一般来说,这是正确的,但在防止CSRF攻击时,它可能会有额外的影响。

请注意,HiddenHttpMethodFilter 只重写HTTP 的POST方法,因此这实际上不太可能导致任何实际问题。但是,最好还是将其放在Spring Security过滤器之前。

10.6.6 Overriding Defaults

SpringSecurity的目标是提供保护您的用户免受攻击的默认值。这并不意味着您必须接受它的所有默认值。

例如,您可以提供一个自定义的 CsrfTokenRepository 来覆盖 CSRFtoken 的存储方式。为了保证在分布式系统中可以使用,用Redis来存储是比较常见的实现方式。

您还可以指定一个自定义的 RequestMatcher 来确定哪些请求受CSRF保护(也就是说,您可能不关心logout是否安全)。简而言之,如果Spring Security的CSRF保护行为不完全符合您的要求,那么您可以自定义该行为。有关如何使用XML和java配置来进行这些自定义的详细信息,详见the section called “”章节。

你可能感兴趣的:(10.6 Cross Site Request Forgery (CSRF))