Xor CSRF token (#6798)

#### What type of PR is this?

/kind improvement
/area core
/milestone 2.20.x

#### What this PR does / why we need it:

This PR makes XOR operation for CSRF token and changes the CSRF cookie `HttpOnly` to `true` to forbid JavaScript from accessing the cookie.

See https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-token-request-handler-breach for more details.

#### Special notes for your reviewer:

```bash
http http://localhost:8090/login -ph

HTTP/1.1 200 OK
set-cookie: XSRF-TOKEN=6d5dd83f-f0a7-4d94-a33e-73f213d679ff; Path=/; HTTPOnly
```

```bash
http http://localhost:8090/login -pb | grep _csrf

><input type="hidden" name="_csrf" value="ctubmrEC3dAbxC5H_k_-VnVUtih2BrfjcPfLmVAyaP0a1kAdEb-t_IcwuLM29B11yGLKNRQxm0lFZILOFZX-_GcHWJ974iR5"/>
```

#### Does this PR introduce a user-facing change?

```release-note
None
```
pull/6806/head
John Niang 2024-10-09 17:00:57 +08:00 committed by GitHub
parent 845893944c
commit 5c50779693
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 11 additions and 12 deletions

View File

@ -1,30 +1,29 @@
package run.halo.app.security;
import static org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository.withHttpOnlyFalse;
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.pathMatchers;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository;
import org.springframework.security.web.server.csrf.CsrfWebFilter;
import org.springframework.security.web.server.csrf.ServerCsrfTokenRequestAttributeHandler;
import org.springframework.security.web.server.csrf.XorServerCsrfTokenRequestAttributeHandler;
import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher;
import org.springframework.stereotype.Component;
import run.halo.app.security.authentication.SecurityConfigurer;
@Component
public class CsrfConfigurer implements SecurityConfigurer {
class CsrfConfigurer implements SecurityConfigurer {
@Override
public void configure(ServerHttpSecurity http) {
var csrfMatcher = new AndServerWebExchangeMatcher(
CsrfWebFilter.DEFAULT_CSRF_MATCHER,
new NegatedServerWebExchangeMatcher(pathMatchers("/api/**", "/apis/**", "/system/setup")
));
new NegatedServerWebExchangeMatcher(
pathMatchers("/api/**", "/apis/**", "/system/setup"))
);
http.csrf(csrfSpec -> csrfSpec
.csrfTokenRepository(withHttpOnlyFalse())
// TODO Use XorServerCsrfTokenRequestAttributeHandler instead when console implements
// the algorithm
.csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler())
.csrfTokenRepository(new CookieServerCsrfTokenRepository())
.csrfTokenRequestHandler(new XorServerCsrfTokenRequestAttributeHandler())
.requireCsrfProtectionMatcher(csrfMatcher));
}

View File

@ -1,6 +1,7 @@
package run.halo.app.infra.exception.handlers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
import java.util.Locale;
import org.junit.jupiter.api.AfterEach;
@ -121,9 +122,8 @@ class I18nExceptionTest {
@Test
void shouldGetConflictError() {
webClient.put().uri("/response-entity/conflict-error")
.header("X-XSRF-TOKEN", "fake-token")
.cookie("XSRF-TOKEN", "fake-token")
webClient.mutate().apply(csrf()).build()
.put().uri("/response-entity/conflict-error")
.exchange()
.expectStatus().isEqualTo(HttpStatus.CONFLICT)
.expectBody(ProblemDetail.class)