Skip to content

Commit

Permalink
Ensure the refreshed CSRF cookie retains the original value
Browse files Browse the repository at this point in the history
(cherry picked from commit 2a872fe)
  • Loading branch information
sberyozkin authored and gsmet committed Jan 18, 2024
1 parent d61e59a commit 25ee2c7
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,26 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi
if (requestMethodIsSafe(requestContext)) {
// safe HTTP method, tolerate the absence of a token
if (isCsrfTokenRequired(routing, config)) {
// Set the CSRF cookie with a randomly generated value
byte[] tokenBytes = new byte[config.tokenSize];
secureRandom.nextBytes(tokenBytes);
routing.put(CSRF_TOKEN_BYTES_KEY, tokenBytes);
routing.put(CSRF_TOKEN_KEY, Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes));

if (cookieToken == null) {
generateNewCsrfToken(routing, config);
} else {
String csrfTokenHeaderParam = requestContext.getHeaderString(config.tokenHeaderName);
if (csrfTokenHeaderParam != null) {
LOG.debugf("CSRF token found in the token header");
// Verify the header, make sure the header value, possibly signed, is returned as the next cookie value
verifyCsrfToken(requestContext, routing, config, cookieToken, csrfTokenHeaderParam);
} else if (!config.tokenSignatureKey.isEmpty()) {
// If the signature is required, then we can not use the current cookie value
// as the HTML form token key because it represents a signed value of the previous key
// and it will lead to the double-signing issue if this value is reused as the key.
// It should be fine for simple HTML forms anyway
generateNewCsrfToken(routing, config);
} else {
// Make sure the same cookie value is returned
routing.put(CSRF_TOKEN_KEY, cookieToken);
routing.put(CSRF_TOKEN_BYTES_KEY, Base64.getUrlDecoder().decode(cookieToken));
}
}
routing.put(NEW_COOKIE_REQUIRED, true);
}
} else if (config.verifyToken) {
Expand Down Expand Up @@ -139,6 +153,14 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi
}
}

private void generateNewCsrfToken(RoutingContext routing, CsrfReactiveConfig config) {
// Set the CSRF cookie with a randomly generated value
byte[] tokenBytes = new byte[config.tokenSize];
secureRandom.nextBytes(tokenBytes);
routing.put(CSRF_TOKEN_BYTES_KEY, tokenBytes);
routing.put(CSRF_TOKEN_KEY, Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes));
}

private void verifyCsrfToken(ResteasyReactiveContainerRequestContext requestContext, RoutingContext routing,
CsrfReactiveConfig config, String cookieToken, String csrfToken) {
if (cookieToken == null) {
Expand All @@ -160,6 +182,7 @@ private void verifyCsrfToken(ResteasyReactiveContainerRequestContext requestCont
return;
} else {
routing.put(CSRF_TOKEN_KEY, csrfToken);
routing.put(CSRF_TOKEN_BYTES_KEY, Base64.getUrlDecoder().decode(csrfToken));
routing.put(CSRF_TOKEN_VERIFIED, true);
// reset the cookie
routing.put(NEW_COOKIE_REQUIRED, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.net.URL;
Expand Down Expand Up @@ -56,6 +57,8 @@ public void testCsrfTokenInForm() throws Exception {
assertNotNull(htmlPage.getWebResponse().getResponseHeaderValue("Set-Cookie"));
assertEquals("alice:true:tokenHeaderIsSet=false", textPage.getContent());

Cookie cookie1 = webClient.getCookieManager().getCookie("csrftoken");

// This request which returns String is not CSRF protected
textPage = webClient.getPage("http://localhost:8081/service/hello");
assertEquals("hello", textPage.getContent());
Expand All @@ -67,6 +70,11 @@ public void testCsrfTokenInForm() throws Exception {
assertNotNull(htmlPage.getWebResponse().getResponseHeaderValue("Set-Cookie"));
assertEquals("alice:true:tokenHeaderIsSet=false", textPage.getContent());

Cookie cookie2 = webClient.getCookieManager().getCookie("csrftoken");

assertEquals(cookie1.getValue(), cookie2.getValue());
assertTrue(cookie1.getExpires().before(cookie2.getExpires()));

webClient.getCookieManager().clearCookies();
}
}
Expand Down Expand Up @@ -366,6 +374,12 @@ private void assurePostFormPath(io.vertx.ext.web.client.WebClient vertxWebClient
if (responseBody != null) {
assertEquals(responseBody, result.result().bodyAsString(), path);
}
if (expectedStatus != 400) {
String[] nextCookie = result.result().cookies().get(0).split(";");
String[] cookieNameValue = nextCookie[0].trim().split("=");
assertEquals(csrfCookie.getName(), cookieNameValue[0]);
assertEquals(csrfCookie.getValue(), cookieNameValue[1]);
}
}

private void assurePostJsonPath(io.vertx.ext.web.client.WebClient vertxWebClient, String path,
Expand Down

0 comments on commit 25ee2c7

Please sign in to comment.