2
0

exploits.adoc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. = Exploit Protection Migrations
  2. The following steps relate to changes around how to configure CSRF.
  3. == Defer Loading CsrfToken
  4. In Spring Security 5, the default behavior is that the `CsrfToken` will be loaded on every request.
  5. This means that in a typical setup, the `HttpSession` must be read for every request even if it is unnecessary.
  6. [NOTE]
  7. ====
  8. Some examples of where it should be unnecessary to read the session include endpoints marked `permitAll()` such as static assets, static HTML pages, single-page applications hosted under the same domain/server, etc.
  9. ====
  10. In Spring Security 6, the default is that the lookup of the `CsrfToken` will be deferred until it is needed.
  11. [NOTE]
  12. ====
  13. The `CsrfToken` is needed whenever a request is made with an HTTP verb that would change the state of the application.
  14. This is covered in detail in xref:features/exploits/csrf.adoc#csrf-protection-idempotent[Safe Methods Must be Idempotent].
  15. Additionally, it is needed by any request that renders the token to the response, such as a web page with a `<form>` tag that includes a hidden `<input>` for the CSRF token.
  16. ====
  17. To opt into the new Spring Security 6 default, the following configuration can be used.
  18. [[servlet-opt-in-defer-loading-csrf-token]]
  19. .Defer Loading `CsrfToken`
  20. ====
  21. .Java
  22. [source,java,role="primary"]
  23. ----
  24. @Bean
  25. public SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
  26. CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
  27. // set the name of the attribute the CsrfToken will be populated on
  28. requestHandler.setCsrfRequestAttributeName("_csrf");
  29. http
  30. // ...
  31. .csrf((csrf) -> csrf
  32. .csrfTokenRequestHandler(requestHandler)
  33. );
  34. return http.build();
  35. }
  36. ----
  37. .Kotlin
  38. [source,kotlin,role="secondary"]
  39. ----
  40. @Bean
  41. open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
  42. val requestHandler = CsrfTokenRequestAttributeHandler()
  43. // set the name of the attribute the CsrfToken will be populated on
  44. requestHandler.setCsrfRequestAttributeName("_csrf")
  45. http {
  46. csrf {
  47. csrfTokenRequestHandler = requestHandler
  48. }
  49. }
  50. return http.build()
  51. }
  52. ----
  53. .XML
  54. [source,xml,role="secondary"]
  55. ----
  56. <http>
  57. <!-- ... -->
  58. <csrf request-handler-ref="requestHandler"/>
  59. </http>
  60. <b:bean id="requestHandler"
  61. class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler"
  62. p:csrfRequestAttributeName="_csrf"/>
  63. ----
  64. ====
  65. [NOTE]
  66. ====
  67. When the `CsrfToken` is deferred (the default in Spring Security 6), some applications may break due to the fact that they were designed with non-deferred CSRF tokens.
  68. See <<servlet-defer-loading-csrf-token-opt-out,Opt-out Steps>> below for more information.
  69. ====
  70. [[servlet-defer-loading-csrf-token-opt-out]]
  71. === Opt-out Steps
  72. If configuring the `CsrfToken` to be deferred gives you trouble, take a look at these scenarios for optimal opt out behavior:
  73. ==== I am using a Single-Page Application with `CookieCsrfTokenRepository`
  74. If you are using a single-page app (SPA) to connect to a backend protected by Spring Security along with `CookieCsrfTokenRepository.withHttpOnlyFalse()`, you may find that the CSRF token is no longer returned to your application as a cookie on the first request to the server.
  75. In this case, you have several options for restoring the behavior your client-side application expects.
  76. One option is to add a `Filter` that eagerly renders the `CsrfToken` to the response regardless of which request is made first, like so:
  77. .Add a `Filter` to return a cookie on the response
  78. ====
  79. .Java
  80. [source,java,role="primary"]
  81. ----
  82. @Bean
  83. public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  84. CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
  85. CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
  86. // set the name of the attribute the CsrfToken will be populated on
  87. requestHandler.setCsrfRequestAttributeName("_csrf");
  88. http
  89. // ...
  90. .csrf((csrf) -> csrf
  91. .csrfTokenRepository(tokenRepository)
  92. .csrfTokenRequestHandler(requestHandler)
  93. )
  94. .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);
  95. return http.build();
  96. }
  97. private static final class CsrfCookieFilter extends OncePerRequestFilter {
  98. @Override
  99. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
  100. throws ServletException, IOException {
  101. CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
  102. // Render the token value to a cookie by causing the deferred token to be loaded
  103. csrfToken.getToken();
  104. filterChain.doFilter(request, response);
  105. }
  106. }
  107. ----
  108. .Kotlin
  109. [source,kotlin,role="secondary"]
  110. ----
  111. @Bean
  112. open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
  113. val tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
  114. val requestHandler = CsrfTokenRequestAttributeHandler()
  115. // set the name of the attribute the CsrfToken will be populated on
  116. requestHandler.setCsrfRequestAttributeName("_csrf")
  117. http {
  118. csrf {
  119. csrfTokenRepository = tokenRepository
  120. csrfTokenRequestHandler = requestHandler
  121. }
  122. addFilterAfter<BasicAuthenticationFilter>(CsrfCookieFilter())
  123. }
  124. return http.build()
  125. }
  126. class CsrfCookieFilter : OncePerRequestFilter() {
  127. override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
  128. val csrfToken = request.getAttribute(CsrfToken::class.java.name) as CsrfToken
  129. // Render the token value to a cookie by causing the deferred token to be loaded
  130. csrfToken.token
  131. filterChain.doFilter(request, response)
  132. }
  133. }
  134. ----
  135. ====
  136. The option above does not require changes to the single-page application, but does cause the `CsrfToken` to be loaded on every request.
  137. If you do not wish to add a `Filter` to eagerly load tokens on every request, additional options are listed below.
  138. ==== I am using a Single-Page Application with `HttpSessionCsrfTokenRepository`
  139. If you are using sessions, your application will benefit from deferred tokens.
  140. Instead of opting out, another option is to add a new `@RestController` with a `/csrf` endpoint, like so:
  141. .Add a `/csrf` endpoint
  142. ====
  143. .Java
  144. [source,java,role="primary"]
  145. ----
  146. @RestController
  147. public class CsrfController {
  148. @GetMapping("/csrf")
  149. public CsrfToken csrf(CsrfToken csrfToken) {
  150. return csrfToken;
  151. }
  152. }
  153. ----
  154. .Kotlin
  155. [source,kotlin,role="secondary"]
  156. ----
  157. @RestController
  158. class CsrfController {
  159. @GetMapping("/csrf")
  160. fun csrf(csrfToken: CsrfToken): CsrfToken {
  161. return csrfToken
  162. }
  163. }
  164. ----
  165. ====
  166. [NOTE]
  167. ====
  168. You may consider adding `.requestMatchers("/csrf").permitAll()` if the endpoint above is required prior to authenticating with the server.
  169. ====
  170. The `/csrf` endpoint would need to be consumed by the client-side application in order to bootstrap the application for subsequent requests.
  171. [NOTE]
  172. ====
  173. Instructions for calling the `/csrf` endpoint on application launch are specific to your client-side framework and therefore outside the scope of this document.
  174. ====
  175. [NOTE]
  176. ====
  177. While this requires changes to your single-page application, the benefit is that the CSRF token is only loaded once and the token can continue to be deferred.
  178. This approach works particularly well with applications that use `HttpSessionCsrfTokenRepository` and do benefit from deferred tokens by allowing the `HttpSession` not to be read on every request.
  179. ====
  180. If you simply wish to opt out of deferred tokens altogether, that option is listed next.
  181. ==== I need to opt out of deferred tokens for another reason
  182. If deferred tokens break your application for another reason, then you can explicitly opt into the 5.8 defaults using the following configuration:
  183. .Explicit Configure `CsrfToken` with 5.8 Defaults
  184. ====
  185. .Java
  186. [source,java,role="primary"]
  187. ----
  188. @Bean
  189. public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  190. CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
  191. // set the name of the attribute the CsrfToken will be populated on
  192. requestHandler.setCsrfRequestAttributeName(null);
  193. http
  194. // ...
  195. .csrf((csrf) -> csrf
  196. .csrfTokenRequestHandler(requestHandler)
  197. );
  198. return http.build();
  199. }
  200. ----
  201. .Kotlin
  202. [source,kotlin,role="secondary"]
  203. ----
  204. @Bean
  205. open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
  206. val requestHandler = CsrfTokenRequestAttributeHandler()
  207. // set the name of the attribute the CsrfToken will be populated on
  208. requestHandler.setCsrfRequestAttributeName(null)
  209. http {
  210. csrf {
  211. csrfTokenRequestHandler = requestHandler
  212. }
  213. }
  214. return http.build()
  215. }
  216. ----
  217. .XML
  218. [source,xml,role="secondary"]
  219. ----
  220. <http>
  221. <!-- ... -->
  222. <csrf request-handler-ref="requestHandler"/>
  223. </http>
  224. <b:bean id="requestHandler"
  225. class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler">
  226. <b:property name="csrfRequestAttributeName">
  227. <b:null/>
  228. </b:property>
  229. </b:bean>
  230. ----
  231. ====
  232. [NOTE]
  233. ====
  234. By setting the `csrfRequestAttributeName` to `null`, the `CsrfToken` must first be loaded to determine what attribute name to use.
  235. This causes the `CsrfToken` to be loaded on every request.
  236. ====
  237. == Protect against CSRF BREACH
  238. If the steps for <<Defer Loading CsrfToken>> work for you, then you can also opt into Spring Security 6's default support for BREACH protection of the `CsrfToken` using the following configuration:
  239. .`CsrfToken` BREACH Protection
  240. ====
  241. .Java
  242. [source,java,role="primary"]
  243. ----
  244. @Bean
  245. DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
  246. XorCsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler();
  247. // set the name of the attribute the CsrfToken will be populated on
  248. requestHandler.setCsrfRequestAttributeName("_csrf");
  249. http
  250. // ...
  251. .csrf((csrf) -> csrf
  252. .csrfTokenRequestHandler(requestHandler)
  253. );
  254. return http.build();
  255. }
  256. ----
  257. .Kotlin
  258. [source,kotlin,role="secondary"]
  259. ----
  260. @Bean
  261. open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
  262. val requestHandler = XorCsrfTokenRequestAttributeHandler()
  263. // set the name of the attribute the CsrfToken will be populated on
  264. requestHandler.setCsrfRequestAttributeName("_csrf")
  265. http {
  266. csrf {
  267. csrfTokenRequestHandler = requestHandler
  268. }
  269. }
  270. return http.build()
  271. }
  272. ----
  273. .XML
  274. [source,xml,role="secondary"]
  275. ----
  276. <http>
  277. <!-- ... -->
  278. <csrf request-handler-ref="requestHandler"/>
  279. </http>
  280. <b:bean id="requestHandler"
  281. class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler"
  282. p:csrfRequestAttributeName="_csrf"/>
  283. ----
  284. ====
  285. [[servlet-csrf-breach-opt-out]]
  286. === Opt-out Steps
  287. If configuring CSRF BREACH protection gives you trouble, take a look at these scenarios for optimal opt out behavior:
  288. ==== I am using AngularJS or another Javascript framework
  289. If you are using AngularJS and the https://angular.io/api/common/http/HttpClientXsrfModule[HttpClientXsrfModule] (or a similar module in another framework) along with `CookieCsrfTokenRepository.withHttpOnlyFalse()`, you may find that automatic support no longer works.
  290. In this case, you can configure Spring Security to validate the raw `CsrfToken` from the cookie while keeping CSRF BREACH protection of the response using a custom `CsrfTokenRequestHandler` with delegation, like so:
  291. .Configure `CsrfToken` BREACH Protection to validate raw tokens
  292. ====
  293. .Java
  294. [source,java,role="primary"]
  295. ----
  296. @Bean
  297. public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  298. CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
  299. XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler();
  300. // set the name of the attribute the CsrfToken will be populated on
  301. delegate.setCsrfRequestAttributeName("_csrf");
  302. // Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
  303. // default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
  304. CsrfTokenRequestHandler requestHandler = delegate::handle;
  305. http
  306. // ...
  307. .csrf((csrf) -> csrf
  308. .csrfTokenRepository(tokenRepository)
  309. .csrfTokenRequestHandler(requestHandler)
  310. );
  311. return http.build();
  312. }
  313. ----
  314. .Kotlin
  315. [source,kotlin,role="secondary"]
  316. ----
  317. @Bean
  318. open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
  319. val tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
  320. val delegate = XorCsrfTokenRequestAttributeHandler()
  321. // set the name of the attribute the CsrfToken will be populated on
  322. delegate.setCsrfRequestAttributeName("_csrf")
  323. // Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
  324. // default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
  325. val requestHandler = CsrfTokenRequestHandler(delegate::handle)
  326. http {
  327. csrf {
  328. csrfTokenRepository = tokenRepository
  329. csrfTokenRequestHandler = requestHandler
  330. }
  331. }
  332. return http.build()
  333. }
  334. ----
  335. .XML
  336. [source,xml,role="secondary"]
  337. ----
  338. <http>
  339. <!-- ... -->
  340. <csrf token-repository-ref="tokenRepository"
  341. request-handler-ref="requestHandler"/>
  342. </http>
  343. <b:bean id="tokenRepository"
  344. class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
  345. p:cookieHttpOnly="false"/>
  346. ----
  347. ====
  348. This is the RECOMMENDED way to configure Spring Security to work with a client-side application that uses cookie values, because it continues to allow the response to return a randomized value for the CSRF token in case the application returns HTML or other responses that could be vulnerable to BREACH without your knowledge.
  349. [NOTE]
  350. ====
  351. BREACH protection works to protect the token when it is included in a response body that can be GZIP compressed, which generally does not include headers and cookies.
  352. ====
  353. [TIP]
  354. ====
  355. Any token value returned by the server can be used successfully by the client-side application because the underlying (raw) CSRF token does not change.
  356. It is not required for an AngularJS (or similar) application to refresh the CSRF token before/after every request.
  357. ====
  358. If you simply wish to opt out of CSRF BREACH protection altogether, that option is listed next.
  359. ==== I need to opt out of CSRF BREACH protection for another reason
  360. If CSRF BREACH protection does not work for you for another reason, you can opt out using the configuration from the <<servlet-opt-in-defer-loading-csrf-token>> section.
  361. == CSRF BREACH with WebSocket support
  362. If the steps for <<Protect against CSRF BREACH>> work for normal HTTP requests and you are using xref:servlet/integrations/websocket.adoc[WebSocket Security] support, then you can also opt into Spring Security 6's default support for BREACH protection of the `CsrfToken` with xref:servlet/integrations/websocket.adoc#websocket-sameorigin-csrf[Stomp headers].
  363. .WebSocket Security BREACH Protection
  364. ====
  365. .Java
  366. [source,java,role="primary"]
  367. ----
  368. @Bean
  369. ChannelInterceptor csrfChannelInterceptor() {
  370. return new XorCsrfChannelInterceptor();
  371. }
  372. ----
  373. .Kotlin
  374. [source,kotlin,role="secondary"]
  375. ----
  376. @Bean
  377. open fun csrfChannelInterceptor(): ChannelInterceptor {
  378. return XorCsrfChannelInterceptor()
  379. }
  380. ----
  381. .XML
  382. [source,xml,role="secondary"]
  383. ----
  384. <b:bean id="csrfChannelInterceptor"
  385. class="org.springframework.security.messaging.web.csrf.XorCsrfChannelInterceptor"/>
  386. ----
  387. ====
  388. If configuring CSRF BREACH protection for WebSocket Security gives you trouble, you can configure the 5.8 default using the following configuration:
  389. .Configure WebSocket Security with 5.8 default
  390. ====
  391. .Java
  392. [source,java,role="primary"]
  393. ----
  394. @Bean
  395. ChannelInterceptor csrfChannelInterceptor() {
  396. return new CsrfChannelInterceptor();
  397. }
  398. ----
  399. .Kotlin
  400. [source,kotlin,role="secondary"]
  401. ----
  402. @Bean
  403. open fun csrfChannelInterceptor(): ChannelInterceptor {
  404. return CsrfChannelInterceptor()
  405. }
  406. ----
  407. .XML
  408. [source,xml,role="secondary"]
  409. ----
  410. <b:bean id="csrfChannelInterceptor"
  411. class="org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor"/>
  412. ----
  413. ====