Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit42233a3

Browse files
[Security] Implement double-submit CSRF protection
1 parentf654df3 commit42233a3

File tree

6 files changed

+211
-1
lines changed

6 files changed

+211
-1
lines changed

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableI
237237
->children()
238238
->booleanNode('enabled')->defaultNull()->end()// defaults to framework.csrf_protection.enabled
239239
->scalarNode('field_name')->defaultValue('_token')->end()
240+
->scalarNode('header_name')->defaultValue('x-csrf-token')->end()
241+
->booleanNode('accept_as_fallback')->defaultFalse()->end()
240242
->end()
241243
->end()
242244
->end()

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@
149149
useSymfony\Component\Security\Core\AuthenticationEvents;
150150
useSymfony\Component\Security\Core\Exception\AuthenticationException;
151151
useSymfony\Component\Security\Csrf\CsrfTokenManagerInterface;
152+
useSymfony\Component\Security\Csrf\DoubleSubmitCsrfTokenManager;
152153
useSymfony\Component\Semaphore\PersistingStoreInterfaceasSemaphoreStoreInterface;
153154
useSymfony\Component\Semaphore\Semaphore;
154155
useSymfony\Component\Semaphore\SemaphoreFactory;
@@ -763,6 +764,12 @@ private function registerFormConfiguration(array $config, ContainerBuilder $cont
763764

764765
$container->setParameter('form.type_extension.csrf.enabled',true);
765766
$container->setParameter('form.type_extension.csrf.field_name',$config['form']['csrf_protection']['field_name']);
767+
$container->setParameter('form.type_extension.csrf.header_name',$config['form']['csrf_protection']['header_name']);
768+
$container->setParameter('form.type_extension.csrf.accept_as_fallback',$config['form']['csrf_protection']['accept_as_fallback']);
769+
770+
if (!$config['form']['csrf_protection']['header_name'] || !class_exists(DoubleSubmitCsrfTokenManager::class)) {
771+
$container->setAlias('form.type_extension.csrf.token_manager','security.csrf.token_manager');
772+
}
766773
}else {
767774
$container->setParameter('form.type_extension.csrf.enabled',false);
768775
}

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,28 @@
1212
namespaceSymfony\Component\DependencyInjection\Loader\Configurator;
1313

1414
useSymfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension;
15+
useSymfony\Component\Security\Csrf\DoubleSubmitCsrfTokenManager;
1516

1617
returnstaticfunction (ContainerConfigurator$container) {
1718
$container->services()
1819
->set('form.type_extension.csrf', FormTypeCsrfExtension::class)
1920
->args([
20-
service('security.csrf.token_manager'),
21+
service('form.type_extension.csrf.token_manager'),
2122
param('form.type_extension.csrf.enabled'),
2223
param('form.type_extension.csrf.field_name'),
2324
service('translator')->nullOnInvalid(),
2425
param('validator.translation_domain'),
2526
service('form.server_params'),
2627
])
2728
->tag('form.type_extension')
29+
30+
->set('form.type_extension.csrf.token_manager', DoubleSubmitCsrfTokenManager::class)
31+
->args([
32+
service('request_stack'),
33+
service('logger')->nullOnInvalid(),
34+
param('form.type_extension.csrf.header_name'),
35+
param('form.type_extension.csrf.accept_as_fallback'),
36+
])
37+
->tag('monolog.logger', ['channel' =>'request'])
2838
;
2939
};

‎src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public function finishView(FormView $view, FormInterface $form, array $options):
7373
$csrfForm =$factory->createNamed($options['csrf_field_name'], HiddenType::class,$data, [
7474
'block_prefix' =>'csrf_token',
7575
'mapped' =>false,
76+
'attr' => ['data--csrf-protection' =>true],
7677
]);
7778

7879
$view->children[$options['csrf_field_name']] =$csrfForm->createView($view);

‎src/Symfony/Component/Security/Csrf/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.2
5+
---
6+
7+
* Add`DoubleSubmitCsrfTokenManager`
8+
49
6.0
510
---
611

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespaceSymfony\Component\Security\Csrf;
13+
14+
usePsr\Log\LoggerInterface;
15+
useSymfony\Component\HttpFoundation\Request;
16+
useSymfony\Component\HttpFoundation\RequestStack;
17+
useSymfony\Component\HttpFoundation\Response;
18+
useSymfony\Component\HttpFoundation\Session\Session;
19+
useSymfony\Component\HttpKernel\Event\ResponseEvent;
20+
21+
/**
22+
* This CSRF token manager uses a combination of cookie and headers to validate non-persistent tokens.
23+
*
24+
* Double-Submit Validation: A JavaScript snippet on the client side is responsible for performing the
25+
* double-submission. If the double-submit information is missing, we fall back to using the Origin or
26+
* Referer headers.
27+
*
28+
* Fallback Scenarios: If neither double-submit nor Origin/Referer headers are available, it typically
29+
* indicates that JavaScript is disabled on the client side (unless the JavaScript snippet was not
30+
* properly implemented), or that the Origin header was not sent.
31+
*
32+
* By default, requests lacking both double-submit and origin information are deemed insecure.
33+
*
34+
* Security Consistency: When a session is found, a behavioral check is added to ensure that the
35+
* validation method does not downgrade from double-submit to origin checks, or from origin checks to
36+
* the accept fallback. This prevents attackers from exploiting potentially less secure validation
37+
* methods once a more secure method has been confirmed as functional.
38+
*
39+
* @author Nicolas Grekas <p@tchwork.com>
40+
*/
41+
finalclass DoubleSubmitCsrfTokenManagerimplements CsrfTokenManagerInterface
42+
{
43+
publicconstHEADER_NAME ='x-csrf-token';
44+
45+
publicfunction__construct(
46+
privateRequestStack$requestStack,
47+
private ?LoggerInterface$logger =null,
48+
privatestring$headerName =self::HEADER_NAME,
49+
privatebool$acceptAsFallback =false,
50+
) {
51+
}
52+
53+
publicfunctiongetToken(string$tokenId):CsrfToken
54+
{
55+
returnnewCsrfToken($tokenId,$this->headerName);
56+
}
57+
58+
publicfunctionrefreshToken(string$tokenId):CsrfToken
59+
{
60+
returnnewCsrfToken($tokenId,$this->headerName);
61+
}
62+
63+
publicfunctionremoveToken(string$tokenId): ?string
64+
{
65+
returnnull;
66+
}
67+
68+
publicfunctionisTokenValid(CsrfToken$token):bool
69+
{
70+
// This token is not for us
71+
if ($token->getValue() !==$this->headerName) {
72+
$this->logger?->debug('CSRF validation failed: Unknown CSRF token.');
73+
74+
returnfalse;
75+
}
76+
77+
if (!$request =$this->requestStack->getCurrentRequest()) {
78+
$this->logger?->debug('CSRF validation failed: No request found.');
79+
80+
returnfalse;
81+
}
82+
83+
if (false ===$isValidOrigin =$this->isValidOrigin($request)) {
84+
$this->logger?->debug('CSRF validation failed: Origin doesn\'t match.');
85+
86+
returnfalse;
87+
}
88+
89+
if ($this->isValidDoubleSubmit($request)) {
90+
// Mark the request as validated using double-submit info
91+
$request->attributes->set($this->headerName,'double-submit');
92+
$this->logger?->debug('CSRF validation accepted using double-submit info.');
93+
94+
returntrue;
95+
}
96+
97+
// Opportunistically lookup at the session for a previous CSRF validation strategy
98+
$session =$request->hasPreviousSession() ?$request->getSession() :null;
99+
$usageIndexValue =$sessioninstanceof Session ?$usageIndexReference = &$session->getUsageIndex() :0;
100+
$usageIndexReference = \PHP_INT_MIN;
101+
$csrfProtection =$session?->get($this->headerName);
102+
$usageIndexReference =$usageIndexValue;
103+
104+
// If a previous request was validated using double-submit info, stick to it
105+
if ('double-submit' ===$csrfProtection) {
106+
$this->logger?->debug('CSRF validation failed: double-submit info was used in a previous request but didn\'t pass this time.');
107+
108+
returnfalse;
109+
}
110+
111+
// If a previous request was validated using origin info, stick to it
112+
if ('origin' ===$csrfProtection &&null ===$isValidOrigin) {
113+
$this->logger?->debug('CSRF validation failed: origin info was used in a previous request but didn\'t pass this time.');
114+
115+
returnfalse;
116+
}
117+
118+
if (true ===$isValidOrigin) {
119+
// Mark the request as validated using origin info
120+
$request->attributes->set($this->headerName,'origin');
121+
$this->logger?->debug('CSRF validation accepted using origin info.');
122+
123+
returntrue;
124+
}
125+
126+
if ($this->acceptAsFallback) {
127+
$this->logger?->debug('CSRF validation accepted despite the absence of double-submit and origin info.');
128+
129+
returntrue;
130+
}
131+
132+
$this->logger?->debug('CSRF validation failed: double-submit and origin info not found.');
133+
134+
returnfalse;
135+
}
136+
137+
publicfunctionclearCookie(Request$request,Response$response):void
138+
{
139+
$cookieName = ($request->isSecure() ?'__Host-' :'').$this->headerName;
140+
141+
if (!$request->cookies->has($cookieName)) {
142+
$response->headers->clearCookie($cookieName,'/',null,$request->isSecure(),false,'strict');
143+
}
144+
}
145+
146+
publicfunctionpersistStrategy(Request$request):void
147+
{
148+
if ($request->hasSession(true) &&$request->attributes->has($this->headerName)) {
149+
$request->getSession()->set($this->headerName,$request->attributes->get($this->headerName));
150+
}
151+
}
152+
153+
publicfunctiononKernelResponse(ResponseEvent$event):void
154+
{
155+
if (!$event->isMainRequest()) {
156+
return;
157+
}
158+
159+
$this->clearCookie($event->getRequest(),$event->getResponse());
160+
$this->persistStrategy($event->getRequest());
161+
}
162+
163+
/**
164+
* @return bool|null Whether the origin is valid, null if missing
165+
*/
166+
privatefunctionisValidOrigin(Request$request): ?bool
167+
{
168+
$source =$request->headers->get('Origin') ??$request->headers->get('Referer') ??'null';
169+
170+
return'null' ===$source ?null :str_starts_with($source.'/',$request->getScheme().'://'.$request->getHttpHost().'/');
171+
}
172+
173+
privatefunctionisValidDoubleSubmit(Request$request):bool
174+
{
175+
$token =$request->headers->get($this->headerName);
176+
177+
if (!\is_string($token) ||\strlen($token) <32) {
178+
returnfalse;
179+
}
180+
181+
$cookieName = ($request->isSecure() ?'__Host-' :'').$this->headerName;
182+
183+
return$request->cookies->get($cookieName) ===$token;
184+
}
185+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp