-
-
Notifications
You must be signed in to change notification settings - Fork 9.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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
(cc @tucksaun ) |
5b81f2f
to
601b9ba
Compare
0e8e0da
to
42233a3
Compare
AWASP discourage using this pattern and recommands using a hash/crypted value coupled to the session (or JWT lifetime) instead |
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) |
For additional background: the OWASP page links to https://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
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Security/Csrf/DoubleSubmitCsrfTokenManager.php
Outdated
Show resolved
Hide resolved
src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.php
Outdated
Show resolved
Hide resolved
09d6d78
to
66ae045
Compare
src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
Show resolved
Hide resolved
src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
Outdated
Show resolved
Hide resolved
7c212b9
to
f459912
Compare
Fyi: 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). Setting double_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 "run composer 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
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Security/Csrf/DoubleSubmitCsrfTokenManager.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Security/Csrf/DoubleSubmitCsrfTokenManager.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Security/Csrf/DoubleSubmitCsrfTokenManager.php
Outdated
Show resolved
Hide resolved
* 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 this JavaScript 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
Outdated
Show resolved
Hide resolved
f459912
to
ecc326f
Compare
I 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 discourage naive double-submit. |
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
to
867c03b
Compare
Indeed, good catch, fixed! |
867c03b
to
27d8a31
Compare
@@ -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.
The autocomplete
attribute with value off
on an hidden
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 | MIT After EasyCorp/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 protection Will be leveraged by symfony/symfony#58095 Does no harm being enabled regardless of the Symfony version in use. --- Waiting on: - [x] symfony/symfony#58095 Commits ------- b13f0fb Generate data-controller="csrf-protection" on CSRF fields
#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 at symfony/recipes#1337