Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork9.6k
[Security] Implement stateless headers/cookies-based CSRF protection#58095
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
(cc@tucksaun ) |
5b81f2f
to601b9ba
Compare0e8e0da
to42233a3
CompareAWASP discourage using this pattern and recommands using a hash/crypted value coupled to the session (or JWT lifetime) instead |
nicolas-grekas commentedAug 27, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
I've read that, and what I'm proposing here is significantly different from what they discourage to be considered seriously IMHO, esp with the new cookie attributes and with the style of double-submit I'm using here (which is not discussed on that page). Aka I'd be interested in an open-minded threat model analysis of what this protects against (and what are the drawbacks) |
nicolas-grekas commentedAug 27, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
For additional background: the OWASP page links tohttps://owasp.org/www-chapter-london/assets/slides/David_Johansson-Double_Defeat_of_Double-Submit_Cookie.pdf to give more insights about vulnerabilities with the "naive double-submit". I read that doc too, and they don't apply to this PR:
Binding CSRF tokens to any server-side state is ruling out statelessness/cacheability of the strategy so I'm not looking for anything hashed/signed/etc. |
src/Symfony/Component/Security/Csrf/DoubleSubmitCsrfTokenManager.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
src/Symfony/Component/Security/Csrf/DoubleSubmitCsrfTokenManager.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
09d6d78
to66ae045
CompareUh oh!
There was an error while loading.Please reload this page.
src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
7c212b9
tof459912
CompareFyi: I'm planning to look at this on Thursday. Need some focus time to properly digest all information in here :) I don't mind if this gets merged before it, there is enough time till feature freeze to tweak it in a follow up PR if necessary. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I feel like it may be the case that all my comments make no sense whatsoever, I had quite the struggle following the logic. Please be patient with me 😉
One of the struggles is the name of the feature. If I'm correct, the stance of Symfony 7.2 would be: verify origin and do not use tokens for CSRF protection.
Then, for cases where origin headers might not be set (e.g. corporate environments), we fallback to CSRF token protection using a modified double-submit pattern.
If that is the case, I think we should not use "double-submit" as the main name of the new feature (it's just the fallback). Settingdouble_submit_token_ids
to enable origin checks for some token IDs sounds wrong. Let's brainstorm a better name if you agree.
In addition, I'm not sure if we should default to all the complexity the double-submit brings if this is just a fallback. Do we want to push JS code to the users that they probably don't even need? Or should we somehow make the recipe optional and have some docs that says "runcomposer install csrf-double-submit-pack
if you can't (always) use origin checks".
Finally, by generating the CSRF token and creating the cookie in the JS code moves a security-critical part of the double-submit flow to the application code. I'm always much more in favor of keeping security-critical parts in the vendor code (3500 contributors have more security knowledge than most dev teams using Symfony). We must make it very clear that a user should not change the cryptographically secure random generator algorithm, otherwise they open themselves to vulnerabilities (as attackers can see the algorithm, they might be able to predict the value).
src/Symfony/Component/Security/Csrf/DoubleSubmitCsrfTokenManager.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
src/Symfony/Component/Security/Csrf/DoubleSubmitCsrfTokenManager.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
src/Symfony/Component/Security/Csrf/DoubleSubmitCsrfTokenManager.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
src/Symfony/Component/Security/Csrf/DoubleSubmitCsrfTokenManager.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
* Then, we validate the request using a cookie and a CsrfToken. If the cookie is found, it should | ||
* contain the same value as the CsrfToken. A JavaScript snippet on the client side is responsible | ||
* for performing this double-submission. The token value should be regenerated on every request | ||
* using a cryptographically secure random generator. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
If the cookie is found, it should contain the same value as the CsrfToken
If I'm reading the implementation correctly, this is not the case. The name of the cookie must be equal to the CsrfToken prefixed with the cookie name, the value can be anything you like.
I can't directly think of a reason why the implementation was chosen to be like this, but we need to update the comment if this is indeed the logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Putting the token in the name of the cookie prevents race conditions.
About the wording, it's approximate, but not incorrect. The cookie is both its name and its value. And either of them can contain the token.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
is there any doc or howto (not talking about the recipe) for thisJavaScript snippet on the client side
?
if not bounded for example with turbo or whatever high level JS lib ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The recipe is the doc on the topic. We can add a link after its merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
@nicolas-grekas for projects that implement manual CSRF protection (e.g. with the csrf_token() twig function to manually generate a token, and some verification in the controllers), it would be great to update the recipe with an example of a Twig template showing how to construct the hidden crsf form field, so that the sample .js in the recipe can work as is (e.g. what initial values to use for the required data-attributes?) - I'm trying to get it to work on a simple login form, but I'm having trouble with the cookie value that seems to be ignored during the validation.
@wouterj this double-submit is not only a fallback. It's also a defense in-depth. What I mean is that if About the naming, "double-submit" suits me, but I also wondered about naming this "same-origin": |
Any other feedback @symfony/mergers ? I'm going to merge as is without any :) |
I agree that same-origin is a better name to me than double-submit because it's the main idea and is the better known term that is part of the http spec. |
src/Symfony/Component/Security/Csrf/DoubleSubmitCsrfTokenManager.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
f459912
toecc326f
CompareI also prefer "same origin CSRF manager" over double-submit 👍 Other than that, this PR is fine with me. But we need to write detailed docs about the pros and cons of all CSRF protection mechanism during stabilization phase :) |
One argument for renaming: it will avoid getting more reports in the future from people telling us that OWASP discourages the double submit pattern, because they miss that they only discouragenaive double-submit. |
nicolas-grekas commentedOct 8, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Renamed to The option is named framework:csrf_protection:stateless_token_ids:[my_stateless_token_id] |
@nicolas-grekas is it same site or same origin ? Those have different meaning. |
Good one, I believe it's indeed same-origin (exact match of scheme+hostname, including subdomain). |
ecc326f
to867c03b
CompareIndeed, good catch, fixed! |
867c03b
to27d8a31
Comparef57a6de
intosymfony:7.2Uh oh!
There was an error while loading.Please reload this page.
@@ -73,6 +76,7 @@ public function finishView(FormView $view, FormInterface $form, array $options): | |||
$csrfForm = $factory->createNamed($options['csrf_field_name'], HiddenType::class, $data, [ | |||
'block_prefix' => 'csrf_token', | |||
'mapped' => false, | |||
'attr' => $this->fieldAttr + ['autocomplete' => 'off'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Theautocomplete
attribute with valueoff
on anhidden
field seems invalid:#59294
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
see#59296
… default CSRF token id apply only to the app; not to bundles (nicolas-grekas)This PR was merged into the 7.2 branch.Discussion----------[Form][FrameworkBundle] Use auto-configuration to make the default CSRF token id apply only to the app; not to bundles| Q | A| ------------- | ---| Branch? | 7.2| Bug fix? | yes| New feature? | no| Deprecations? | no| Issues | -| License | MITAfterEasyCorp/EasyAdminBundle#6724, I realized I made a mistake in#58095:The `framework.form.csrf_protection.token_id` config option should not configure the default CSRF token id for *all* forms. Instead, we want this option to apply only to forms managed by the app. Bundles shouldn't be affected.This is what this PR does: it switches from global config to auto-configured form types only (which means app's form types).Commits-------bf1e312 [Form][FrameworkBundle] Use auto-configuration to make the default CSRF token id apply only to the app; not to bundles
…double-submit CSRF protection (nicolas-grekas)This PR was merged into the 1.x-dev branch.Discussion----------[make:security:form-login] Tweak login forms to enable double-submit CSRF protectionWill be leveraged bysymfony/symfony#58095Does no harm being enabled regardless of the Symfony version in use.---Waiting on:- [x]symfony/symfony#58095Commits-------b13f0fb Generate data-controller="csrf-protection" on CSRF fields
Uh oh!
There was an error while loading.Please reload this page.
#54705 made me think about our CSRF protection and I wrote the attached CSRF token manager to implement stateless headers/cookies-based validation.
By defaults, the existing stateful manager is used.
In order to leverage this new stateless manager, one needs to list the token ids that should be managed this way:
Since it's stateless, end users won't loose their content if they take time to submit a form: even if the session is destroyed while they populate their form, remember-me will reconnect them and the form will be accepted.
Recipe update atsymfony/recipes#1337