I agree that Cookie XSRF-TOKEN is set by the server and is not meant to be set on the client side, but...
Describe the bug
Set both header X-XSRF-TOKEN and Cookie XSRF-TOKEN to any identical value on client side triggers API endpoint instead of returning 403 Forbidden.
Expected behavior
I would expect that when I set any wrong identical value on client side for both header X-XSRF-TOKEN and Cookie XSRF-TOKEN then API would not be reached and that instead Spring would return 403 Forbidden, because the value is different from the CSRF token stored for current session on the server side.
To Reproduce
Here I can reach the endpoint if I set both header X-XSRF-TOKEN and Cookie XSRF-TOKEN to whatever-value using Postman:
You see on the left of screenshot behind Postman Sending request... that the only value that should trigger the endpoint is ab87abbd-0596-4fcc-93b7-3d31aca6b44a, though whatever-value has also triggered the endpoint on the right of screenshot.
Configuration
I'm testing CSRF with this kind of setting:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.requireCsrfProtectionMatcher(
new AndRequestMatcher(
CsrfFilter.DEFAULT_CSRF_MATCHER,
new RegexRequestMatcher(".*greeting-csrf.*", null)
)
)
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
}
[...]
@RequestMapping(
method = { RequestMethod.GET, RequestMethod.POST },
path = "/greeting-csrf",
consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.TEXT_PLAIN_VALUE }
)
public Greeting greetingCsrf(HttpServletRequest request) throws IOException { ... }
Usually the nominal workflow is that on the first connection I get the value ab87...b44a from Cookie XSRF-TOKEN coming from the server.
Then I set header X-XSRF-TOKEN or GET/POST parameter _csrf to value ab87...b44a, in that case Spring triggers correctly the endpoint. And if I put any other value then Spring returns correctly 403 Forbidden.
But setting Cookie XSRF-TOKEN on the client with any identical value to header X-XSRF-TOKEN (or to parameter _csrf) appears to trigger the endpoint too.
Comment From: rwinch
This is expected behavior. When using a cookie for storing the CSRF token the client can change the value. However, the browsers' same origin policy ensures that an external site cannot change the value which means you are still protected from CSRF attacks.
If you don't want the value to be able to be changed, then you can use the default of storing the CSRF token in session.
Comment From: meeque
Thanks for clarifying this @rwinch! Your answer is obviously true, but even after working on many, many CSRF protection scenarios in very diverse web applications, I had a hard time figuring this out myself.
Could it make sense to explain this in the documentation? Either in JavaDocs of CookieCsrfTokenRepository, or possibly somewhere around here?
The latter also says:
If you do not need the ability to read the cookie with JavaScript directly, it is recommended to omit cookieHttpOnly=false to improve security.
Frankly, in the light of your explanation I cannot see how cookieHttpOnly=true could further increase security? In particular an XSS vulnerability will allow bypassing the CSRF-protection anyhow, regardless of JavaScript access to the cookie. Or am I getting that wrong again?
Comment From: meeque
Just for reference: OWASP describes the approach implemented by CookieCsrfTokenRepository as double submit cookie. See there for further security considerations regarding sub-domains and non-secure cookies.
Comment From: jzheaux
I cannot see how cookieHttpOnly=true could further increase security
If the cookie is HTTPOnly, then JavaScript can't exfiltrate the token's value. While this may be a very minor improvement, in general, security is improved when an application shuts off unnecessary or unused features.