Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure the refreshed CSRF cookie retains the original value #38229

Merged
merged 1 commit into from
Jan 17, 2024

Conversation

sberyozkin
Copy link
Member

@sberyozkin sberyozkin commented Jan 16, 2024

Fixes #38225.

I'm feeling totally wrecked after working on another CSRF PR and it is only a middle of Jan :-)

IMHO this PR should fix all the issues.

Here is what it does when the CSRF cookie is present for safe requests:

  • If the token header is also present then verify it and it will ensure the original header value is fed into the code which will refresh the cookie with the same value but new expiry
  • If the token header is not present then the new cookie will be created if the signature is also required to avoid double signing issue for HTML forms
  • Otherwise the cookie value is reused
  • Tests: for HTML forms it is verified the cookie has the the same value after 2 submissions, with the 1st cookie expiry date being before the 2nd one; for the headers only the cookie values are checked to be the same - we use Vertx WebClient to test // in the path and it has no Cookie objects, so I'm just comparing the values, the dates check in the HTML forms should be enough though

@gsmet I'm not sure you'd like to wait till @FroMage and @ia3andy can confirm it resolves the reported HTMX issue with multiple forms but CC-ing just in case.

Also CC @BarDweller

@quarkus-bot
Copy link

quarkus-bot bot commented Jan 16, 2024

✔️ The latest workflow run for the pull request has completed successfully.

It should be safe to merge provided you have a look at the other checks in the summary.

You can consult the Develocity build scans.

Copy link
Contributor

@ia3andy ia3andy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested on the renotes app with success. Thanks

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()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else if (!config.tokenSignatureKey.isEmpty()) {
} else if (config.tokenSignatureKey.isPresent()) {

@gsmet gsmet merged commit 5cf2f32 into quarkusio:main Jan 17, 2024
19 checks passed
@quarkus-bot quarkus-bot bot added this to the 3.7 - main milestone Jan 17, 2024
@sberyozkin sberyozkin deleted the csrf_cookie_refresh_same_value branch January 17, 2024 10:05
@sberyozkin
Copy link
Member Author

Thanks @ia3andy for a quick check, sorry I missed your suggestion, I'll simplify as you propose during some other fix which will be probably needed soon enough :-)

Copy link
Member

@FroMage FroMage left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so it's too late for a review, too bad.

I think there's something that can potentially go bad here, if a client is stuck with a non-expired cookie or a header and the token is signed with a previous signature: in this case we always generate a bad request, even for safe methods, and so we never renew the token or fix the situation.

If that happens, the only way users can recover is by waiting for the cookie to expire, or manually clean cookies.

I think for safe methods, token issues should auto-recover, no?

// 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand this comment. We have two ways to get a token: cookie or header. Here we're in a situation where we have a signed cookie, and we don't have a header. Why do we have to regenerate a token? Why would the previous signature be invalid?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this would lead to double-signing given that signing is done in the ResponseFilter.

Is there a specific reason why signing cannot be done in generateNewCsrfToken(..) and use a single routing parameter?

@gsmet gsmet modified the milestones: 3.7.0.CR1, 3.6.7 Jan 18, 2024
@FroMage
Copy link
Member

FroMage commented Jan 19, 2024

@sberyozkin any opinion about the comments?

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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this situation really abort the request if token is incorrect? I think it should verify and generate a new cookie if verification fails. I think it would resolve @FroMage 's concern about changed signature key (stuck cookie).

What is the reason for converting a request header to a cookie? Based on my understanding, the important part is preserving the cookie token btw requests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CSRF Token is refreshed on every request
5 participants