From 0e4097c26f766c5b4ab1a1a646d6cd202ef96240 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 13 Dec 2023 18:18:55 +0000 Subject: [PATCH] Do not use CSRF cookie as the next token value --- .../CsrfRequestResponseReactiveFilter.java | 13 ++++---- .../java/io/quarkus/it/csrf/TestResource.java | 30 +++++++++++++++++++ .../src/main/resources/application.properties | 2 +- .../templates/csrfTokenFirstForm.html | 17 +++++++++++ .../templates/csrfTokenSecondForm.html | 17 +++++++++++ .../io/quarkus/it/csrf/CsrfReactiveTest.java | 30 +++++++++++++++++++ 6 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenFirstForm.html create mode 100644 integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenSecondForm.html diff --git a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java index 694b690fe797f..b75764772a912 100644 --- a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java +++ b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java @@ -66,8 +66,6 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi String cookieToken = getCookieToken(routing, config); if (cookieToken != null) { - routing.put(CSRF_TOKEN_KEY, cookieToken); - try { int cookieTokenSize = Base64.getUrlDecoder().decode(cookieToken).length; // HMAC SHA256 output is 32 bytes long @@ -98,10 +96,10 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi // unsafe HTTP method, token is required // Check the header first - String csrfTokenInHeader = requestContext.getHeaderString(config.tokenHeaderName); - if (csrfTokenInHeader != null) { + String csrfTokenHeaderParam = requestContext.getHeaderString(config.tokenHeaderName); + if (csrfTokenHeaderParam != null) { LOG.debugf("CSRF token found in the token header"); - verifyCsrfToken(requestContext, routing, config, cookieToken, csrfTokenInHeader); + verifyCsrfToken(requestContext, routing, config, cookieToken, csrfTokenHeaderParam); return; } @@ -128,9 +126,9 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi ResteasyReactiveRequestContext rrContext = (ResteasyReactiveRequestContext) requestContext .getServerRequestContext(); - String csrfToken = (String) rrContext.getFormParameter(config.formFieldName, true, false); + String csrfTokenFormParam = (String) rrContext.getFormParameter(config.formFieldName, true, false); LOG.debugf("CSRF token found in the form parameter"); - verifyCsrfToken(requestContext, routing, config, cookieToken, csrfToken); + verifyCsrfToken(requestContext, routing, config, cookieToken, csrfTokenFormParam); return; } else if (cookieToken == null) { @@ -159,6 +157,7 @@ private void verifyCsrfToken(ResteasyReactiveContainerRequestContext requestCont requestContext.abortWith(badClientRequest()); return; } else { + routing.put(CSRF_TOKEN_KEY, csrfToken); routing.put(CSRF_TOKEN_VERIFIED, true); return; } diff --git a/integration-tests/csrf-reactive/src/main/java/io/quarkus/it/csrf/TestResource.java b/integration-tests/csrf-reactive/src/main/java/io/quarkus/it/csrf/TestResource.java index 0f66abf3ed4a1..d3c7b18c47306 100644 --- a/integration-tests/csrf-reactive/src/main/java/io/quarkus/it/csrf/TestResource.java +++ b/integration-tests/csrf-reactive/src/main/java/io/quarkus/it/csrf/TestResource.java @@ -29,6 +29,12 @@ public class TestResource { @Inject Template csrfTokenForm; + @Inject + Template csrfTokenFirstForm; + + @Inject + Template csrfTokenSecondForm; + @Inject Template csrfTokenHeader; @@ -49,6 +55,14 @@ public TemplateInstance getCsrfTokenForm() { return csrfTokenForm.instance(); } + @GET + @Path("/csrfTokenFirstForm") + @Produces(MediaType.TEXT_HTML) + @Authenticated + public TemplateInstance getCsrfTokenFirstForm() { + return csrfTokenFirstForm.instance(); + } + @GET @Path("/csrfTokenWithFormRead") @Produces(MediaType.TEXT_HTML) @@ -71,6 +85,22 @@ public String postCsrfTokenForm(@FormParam("name") String name, @HeaderParam("X- return name + ":" + routingContext.get("csrf_token_verified", false) + ":tokenHeaderIsSet=" + (csrfHeader != null); } + @POST + @Path("/csrfTokenFirstForm") + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @Produces(MediaType.TEXT_HTML) + public TemplateInstance postCsrfTokenFirstForm() { + return csrfTokenSecondForm.instance(); + } + + @POST + @Path("/csrfTokenSecondForm") + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @Produces(MediaType.TEXT_PLAIN) + public String postCsrfTokenSecondForm(@FormParam("name") String name, @HeaderParam("X-CSRF-TOKEN") String csrfHeader) { + return name + ":" + routingContext.get("csrf_token_verified", false) + ":tokenHeaderIsSet=" + (csrfHeader != null); + } + @POST @Path("/csrfTokenWithFormRead") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) diff --git a/integration-tests/csrf-reactive/src/main/resources/application.properties b/integration-tests/csrf-reactive/src/main/resources/application.properties index 23f5d88b8c1f0..9a778c50dda03 100644 --- a/integration-tests/csrf-reactive/src/main/resources/application.properties +++ b/integration-tests/csrf-reactive/src/main/resources/application.properties @@ -1,5 +1,5 @@ quarkus.csrf-reactive.cookie-name=csrftoken -quarkus.csrf-reactive.create-token-path=/service/csrfTokenForm,/service/csrfTokenWithFormRead,/service/csrfTokenMultipart,/service/csrfTokenWithHeader +quarkus.csrf-reactive.create-token-path=/service/csrfTokenForm,/service/csrfTokenFirstForm,/service/csrfTokenSecondForm,/service/csrfTokenWithFormRead,/service/csrfTokenMultipart,/service/csrfTokenWithHeader quarkus.csrf-reactive.token-signature-key=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow quarkus.http.auth.basic=true diff --git a/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenFirstForm.html b/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenFirstForm.html new file mode 100644 index 0000000000000..71dadecdf41e2 --- /dev/null +++ b/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenFirstForm.html @@ -0,0 +1,17 @@ + + + + +CSRF Token First Form Test + + +

CSRF Test

+ +
+ + +

Your Name:

+

+
+ + diff --git a/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenSecondForm.html b/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenSecondForm.html new file mode 100644 index 0000000000000..7c28b45f662a6 --- /dev/null +++ b/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenSecondForm.html @@ -0,0 +1,17 @@ + + + + +CSRF Token Second Form Test + + +

CSRF Test

+ +
+ + +

Your Name:

+

+
+ + diff --git a/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java b/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java index e6d50f0fc39b7..770d36441e300 100644 --- a/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java +++ b/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java @@ -61,6 +61,36 @@ public void testCsrfTokenInForm() throws Exception { } } + @Test + public void testCsrfTokenTwoForms() throws Exception { + try (final WebClient webClient = createWebClient()) { + webClient.addRequestHeader("Authorization", basicAuth("alice", "alice")); + HtmlPage htmlPage = webClient.getPage("http://localhost:8081/service/csrfTokenFirstForm"); + + assertEquals("CSRF Token First Form Test", htmlPage.getTitleText()); + + HtmlForm loginForm = htmlPage.getForms().get(0); + + loginForm.getInputByName("name").setValueAttribute("alice"); + + assertNotNull(webClient.getCookieManager().getCookie("csrftoken")); + + htmlPage = loginForm.getInputByName("submit").click(); + + assertEquals("CSRF Token Second Form Test", htmlPage.getTitleText()); + + loginForm = htmlPage.getForms().get(0); + + loginForm.getInputByName("name").setValueAttribute("alice"); + + TextPage textPage = loginForm.getInputByName("submit").click(); + assertNotNull(webClient.getCookieManager().getCookie("csrftoken")); + assertEquals("alice:true:tokenHeaderIsSet=false", textPage.getContent()); + + webClient.getCookieManager().clearCookies(); + } + } + @Test public void testCsrfTokenWithFormRead() throws Exception { try (final WebClient webClient = createWebClient()) {