servlet-exploits-csrf.md 17.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
# Servlet 环境中的跨站点请求伪造

本节讨论 Spring Security对 Servlet 环境的[跨站点请求伪造](../../features/exploits/csrf.html#csrf)支持。

## 使用 Spring 安全CSRF保护

使用 Spring Security的CSRF保护的步骤概述如下:

* [使用适当的HTTP动词](#servlet-csrf-idempotent)

* [配置CSRF保护](#servlet-csrf-configure)

* [包括CSRF令牌](#servlet-csrf-include)

### 使用适当的HTTP动词

防止CSRF攻击的第一步是确保你的网站使用正确的HTTP动词。这在[安全的方法必须是幂等的。](../../features/exploits/csrf.html#csrf-protection-idempotent)中有详细介绍。

### 配置CSRF保护

下一步是在应用程序中配置 Spring Security的CSRF保护。 Spring 默认情况下,Security的CSRF保护是启用的,但你可能需要定制配置。下面是一些常见的定制。

#### 自定义CSRFTokenRepository

Spring 默认情况下,Security使用`HttpSessionCsrfTokenRepository`将预期的CSRF令牌存储在`HttpSession`中。在某些情况下,用户可能希望配置自定义`CsrfTokenRepository`。例如,可能希望将cookie中的`CsrfToken`持久化到[支持基于JavaScript的应用程序](#servlet-csrf-include-ajax-auto)

默认情况下,`CookieCsrfTokenRepository`将写到一个名为`XSRF-TOKEN`的cookie,并从一个名为`X-XSRF-TOKEN`的头部或HTTP参数`_csrf`读取它。这些默认值来自[AngularJS](https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection)

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

例1.使用XML配置在cookie中存储CSRF令牌

```
<http>
	<!-- ... -->
	<csrf token-repository-ref="tokenRepository"/>
</http>
<b:bean id="tokenRepository"
	class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
	p:cookieHttpOnly="false"/>
```

|   |示例显式设置`cookieHttpOnly=false`.<br/>这是允许JavaScript(即Angularjs)读取它所必需的。<br/>如果不需要直接使用JavaScript读取cookie的能力,建议省略`cookieHttpOnly=false`以提高安全性。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

你可以在 Java 配置中使用以下方法配置`CookieCsrfTokenRepository`:

例2.在cookie中存储CSRF令牌

Java

```
@EnableWebSecurity
public class WebSecurityConfig extends
		WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) {
		http
			.csrf(csrf -> csrf
				.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
			);
	}
}
```

Kotlin

```
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {

    override fun configure(http: HttpSecurity) {
       http {
            csrf {
                csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
            }
        }
    }
}
```

|   |示例显式设置`cookieHttpOnly=false`.<br/>这是允许JavaScript(即Angularjs)读取它所必需的。<br/>如果不需要直接使用JavaScript读取cookie的能力,建议省略`cookieHttpOnly=false`(通过使用`new CookieCsrfTokenRepository()`代替)以提高安全性。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 禁用CSRF保护

默认情况下启用了CSRF保护。但是,如果CSRF保护[对你的应用程序来说是有意义的](../../features/exploits/csrf.html#csrf-when),则禁用CSRF保护非常简单。

下面的XML配置将禁用CSRF保护。

例3.禁用CSRF XML配置

```
<http>
	<!-- ... -->
	<csrf disabled="true"/>
</http>
```

下面的 Java 配置将禁用CSRF保护。

例4.禁用CSRF

Java

```
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends
		WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) {
		http
			.csrf(csrf -> csrf.disable());
	}
}
```

Kotlin

```
@Configuration
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {

    override fun configure(http: HttpSecurity) {
       http {
            csrf {
                disable()
            }
        }
    }
}
```

### 包括CSRF令牌

为了使[同步器令牌模式](../../features/exploits/csrf.html#csrf-protection-stp)能够抵御CSRF攻击,我们必须在HTTP请求中包含实际的CSRF令牌。这必须包含在请求的一部分(即表单参数、HTTP头等)中,而该部分不是由浏览器自动包含在HTTP请求中的。

Spring Security的[CsrfFilter](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfFilter.html)[CsrfToken](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfToken.html)公开为名为`HttpServletRequest`的属性。这意味着,任何视图技术都可以访问`CsrfToken`以将预期的令牌公开为[form](#servlet-csrf-include-form-attr)[meta tag](#servlet-csrf-include-ajax-meta-attr)。幸运的是,下面列出的集成使得在[form](#servlet-csrf-include-form)[ajax](#servlet-csrf-include-ajax)请求中包含令牌变得更加容易。

#### 表单URL编码

为了发布HTML表单,CSRF令牌必须作为隐藏输入包含在表单中。例如,呈现的HTML可能看起来像:

例5. CSRF令牌HTML

```
<input type="hidden"
	name="_csrf"
	value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
```

接下来,我们将讨论将CSRF令牌以一种形式包含为隐藏输入的各种方法。

##### CSRF令牌自动包含

Spring Security的CSRF支持通过其[CsrfrequestDataValueProcessor](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.html)提供与 Spring 的[RequestDataValueProcessor](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/support/RequestDataValueProcessor.html)的集成。这意味着,如果你利用[Spring’s form tag library](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-view-jsp-formtaglib)[Thymeleaf](https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor)或与`RequestDataValueProcessor`集成的任何其他视图技术,那么具有不安全的HTTP方法(即POST)的窗体将自动包括实际的CSRF令牌。

##### csrfinput标记

如果你正在使用JSP,那么你可以使用[Spring’s form tag library](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-view-jsp-formtaglib)。但是,如果这不是一个选项,你也可以很容易地将令牌包含在[csrfInput](../integrations/jsp-taglibs.html#taglibs-csrfinput)标记中。

##### CSRFToken请求属性

如果用于在请求中包含实际CSRF令牌的[其他选择](#servlet-csrf-include)不起作用,则可以利用以下事实:`CsrfToken`[is exposed](#servlet-csrf-include)作为`HttpServletRequest`属性,该属性名为`_csrf`

使用JSP执行此操作的示例如下所示:

例6.具有请求属性的表单中的CSRF令牌

```
<c:url var="logoutUrl" value="/logout"/>
<form action="${logoutUrl}"
	method="post">
<input type="submit"
	value="Log out" />
<input type="hidden"
	name="${_csrf.parameterName}"
	value="${_csrf.token}"/>
</form>
```

#### Ajax和JSON请求

如果你正在使用JSON,那么就不可能在HTTP参数中提交CSRF令牌。相反,你可以在HTTP头中提交令牌。

在下面的部分中,我们将讨论在基于JavaScript的应用程序中将CSRF令牌作为HTTP请求头包含在内的各种方法。

##### 自动包含

Spring 安全性可以很容易地[configured](#servlet-csrf-configure-custom-repository)将预期的CSRF令牌存储在cookie中。通过将预期的CSRF存储在Cookie中,像[AngularJS](https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection)这样的JavaScript框架将自动在HTTP请求头中包含实际的CSRF令牌。

##### 元标签

[在cookie中暴露CSRF](#servlet-csrf-include-form-auto)的另一种模式是在`meta`标记中包含CSRF标记。HTML可能看起来是这样的:

例7. CSRF元标记HTML

```
<html>
<head>
	<meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
	<meta name="_csrf_header" content="X-CSRF-TOKEN"/>
	<!-- ... -->
</head>
<!-- ... -->
```

一旦元标记包含CSRF令牌,JavaScript代码将读取元标记并将CSRF令牌作为报头。如果你正在使用jQuery,可以通过以下方式完成此操作:

例8. Ajax发送CSRF令牌

```
$(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);
	});
});
```

###### CSRFMeta标签

如果你正在使用JSP,那么将CSRF令牌写入`meta`标记的一种简单方法是利用[csrfMeta](../integrations/jsp-taglibs.html#taglibs-csrfmeta)标记。

###### CSRFToken请求属性

如果用于在请求中包含实际CSRF令牌的[其他选择](#servlet-csrf-include)不起作用,则可以利用以下事实:`CsrfToken`[is exposed](#servlet-csrf-include)作为`HttpServletRequest`属性,该属性名为`_csrf`。使用JSP执行此操作的示例如下所示:

例9. CSRF元标记JSP

```
<html>
<head>
	<meta name="_csrf" content="${_csrf.token}"/>
	<!-- default header name is X-CSRF-TOKEN -->
	<meta name="_csrf_header" content="${_csrf.headerName}"/>
	<!-- ... -->
</head>
<!-- ... -->
```

## CSRF考虑因素

在实施针对CSRF攻击的保护时,有几个特殊的考虑因素需要考虑。本节讨论与 Servlet 环境相关的那些考虑因素。有关更一般性的讨论,请参见[CSRF考虑因素](../../features/exploits/csrf.html#csrf-considerations)

### 登录

这是重要的[需要CSRF才能登录](../../features/exploits/csrf.html#csrf-considerations-login)请求,以防止伪造日志的企图。 Spring Security的 Servlet 支持是开箱即用的。

### 注销

重要的是[需要CSRF才能注销](../../features/exploits/csrf.html#csrf-considerations-logout)请求,以防止伪造注销尝试。如果启用了CSRF保护(默认), Spring Security的`LogoutFilter`将仅处理HTTP POST。这确保了注销需要CSRF令牌,并且恶意用户不能强制注销你的用户。

最简单的方法是使用表单注销。如果你真的想要一个链接,可以使用JavaScript让该链接执行一个POST(例如,可能在一个隐藏的表单上)。对于禁用了JavaScript的浏览器,你可以选择让链接将用户带到将执行POST的注销确认页面。

如果你真的想使用HTTP GET与注销,你可以这样做,但请记住,这通常是不推荐的。例如,以下 Java 配置将使用任何HTTP方法请求的URL执行注销:

例10.用HTTP GET登出

Java

```
@EnableWebSecurity
public class WebSecurityConfig extends
		WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) {
		http
			.logout(logout -> logout
				.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
			);
	}
}
```

Kotlin

```
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {

    override fun configure(http: HttpSecurity) {
       http {
            logout {
                logoutRequestMatcher = AntPathRequestMatcher("/logout")
            }
        }
    }
}
```

### CSRF和会话暂停

默认情况下, Spring Security将CSRF令牌存储在`HttpSession`中。这可能导致会话过期的情况,这意味着没有预期的CSRF令牌来验证。

我们已经讨论了[一般解决方案](../../features/exploits/csrf.html#csrf-considerations-login)到会话的超时。本节讨论CSRF超时的细节,因为它与 Servlet 支持有关。

将预期的CSRF令牌的存储更改为cookie中的存储是很简单的。有关详细信息,请参阅[自定义CSRFTokenRepository](#servlet-csrf-configure-custom-repository)部分。

如果一个令牌确实过期了,你可能希望通过指定一个自定义`AccessDeniedHandler`来定制它的处理方式。自定义`AccessDeniedHandler`可以以任何方式处理`InvalidCsrfTokenException`。对于如何自定义`AccessDeniedHandler`的示例,请参阅为[xml](../appendix/namespace/http.html#nsa-access-denied-handler)[Java configuration](https://github.com/spring-projects/spring-security/tree/5.6.2/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java#L64)提供的链接。

### 

我们有[已经讨论过了](../../features/exploits/csrf.html#csrf-considerations-multipart)如何保护多部分请求(文件上传)不受CSRF攻击导致[鸡和蛋](https://en.wikipedia.org/wiki/Chicken_or_the_egg)问题。本节讨论如何在 Servlet 应用程序中实现将CSRF令牌放置在[body](#servlet-csrf-considerations-multipart-body)[url](#servlet-csrf-considerations-multipart-url)中。

|   |关于使用具有 Spring 的多部分表单的更多信息,可以在 Spring 引用的[1.1.11. 多部分旋转变压器](https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/web.html#mvc-multipart)部分和[MultipartFilter Javadoc](https://docs.spring.io/spring/docs/5.2.x/javadoc-api/org/springframework/web/multipart/support/MultipartFilter.html)中找到。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 将CSRF标记放入体内

我们有[已经讨论过了](../../features/exploits/csrf.html#csrf-considerations-multipart-body)在主体中放置CSRF标记的权衡。在本节中,我们将讨论如何配置 Spring 安全性,以便从主体读取CSRF。

为了从主体中读取CSRF令牌,在 Spring 安全过滤器之前指定了`MultipartFilter`。在 Spring 安全过滤器之前指定`MultipartFilter`意味着没有调用`MultipartFilter`的授权,这意味着任何人都可以在你的服务器上放置临时文件。但是,只有经过授权的用户才能提交由你的应用程序处理的文件。通常,这是推荐的方法,因为临时文件上传对大多数服务器的影响可以忽略不计。

为了确保`MultipartFilter`是在 Spring 安全过滤器 Java 配置之前指定的,用户可以在SpringSecurityFilterchain之前覆盖,如下所示:

例11.初始化器MultipartFilter

Java

```
public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

	@Override
	protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
		insertFilters(servletContext, new MultipartFilter());
	}
}
```

Kotlin

```
class SecurityApplicationInitializer : AbstractSecurityWebApplicationInitializer() {
    override fun beforeSpringSecurityFilterChain(servletContext: ServletContext?) {
        insertFilters(servletContext, MultipartFilter())
    }
}
```

为了确保在使用XML配置的 Spring 安全过滤器之前指定`MultipartFilter`,用户可以确保将`MultipartFilter`元素的\<filter-mapping\>元素放置在web.xml中的SpringSecurityFilterchain之前,如下所示:

示例12.web.xml-multipartfilter

```
<filter>
	<filter-name>MultipartFilter</filter-name>
	<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
	<filter-name>MultipartFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>
```

#### 在URL中包含CSRF令牌

如果允许未经授权的用户上传临时文件是不可接受的,则另一种选择是将`MultipartFilter`置于 Spring 安全过滤器之后,并将CSRF作为查询参数包含在表单的动作属性中。由于`CsrfToken`被公开为`HttpServletRequest`[请求属性](#servlet-csrf-include),因此我们可以使用它来创建带有CSRF令牌的`action`。下面显示了一个带有JSP的示例。

例13. CSRF令牌正在运行

```
<form method="post"
	action="./upload?${_csrf.parameterName}=${_csrf.token}"
	enctype="multipart/form-data">
```

### HiddenHttpMethodFilter

我们有[已经讨论过了](../../features/exploits/csrf.html#csrf-considerations-multipart-body)在主体中放置CSRF标记的权衡。

在 Spring 的 Servlet 支持中,覆盖HTTP方法是使用[HiddenHttpMethodFilter](https://docs.spring.io/spring-framework/docs/5.2.x/javadoc-api/org/springframework/web/filter/reactive/HiddenHttpMethodFilter.html)完成的。更多信息可以在参考文档的[HTTP方法转换](https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/web.html#mvc-rest-method-conversion)部分中找到。

[保护免受剥削](index.html)[安全HTTP响应标头](headers.html)