Skip to content

Commit ff3b100

Browse files
[Security] Add support for Sec-Fetch-Site to SameOriginCsrfTokenManager
1 parent ad0a07c commit ff3b100

File tree

3 files changed

+50
-16
lines changed

3 files changed

+50
-16
lines changed

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.4
5+
---
6+
7+
* Add support for `Sec-Fetch-Site` to `SameOriginCsrfTokenManager`
8+
49
7.2
510
---
611

src/Symfony/Component/Security/Csrf/SameOriginCsrfTokenManager.php

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,24 @@
2323
*
2424
* This manager is designed to be stateless and compatible with HTTP-caching.
2525
*
26-
* First, we validate the source of the request using the Origin/Referer headers. This relies
27-
* on the app being able to know its own target origin. Don't miss configuring your reverse proxy to
28-
* send the X-Forwarded-* / Forwarded headers if you're behind one.
26+
* Requests are considered secure when either:
27+
* - the Sec-Fetch-Site header contains 'same-origin';
28+
* - the Origin or Referer headers contain the same origin as the request;
29+
* - a special token was double-submitted in the request payload and as a cookie and/or a header.
2930
*
30-
* Then, we validate the request using a cookie and a CsrfToken. If the cookie is found, it should
31-
* contain the same value as the CsrfToken. A JavaScript snippet on the client side is responsible
32-
* for performing this double-submission. The token value should be regenerated on every request
33-
* using a cryptographically secure random generator.
31+
* The check using the Origin/Referer headers relies on the app being able to know its own target
32+
* origin. Don't miss configuring your reverse proxy to send the X-Forwarded-* / Forwarded headers
33+
* if you're behind one.
3434
*
35-
* If either double-submit or Origin/Referer headers are missing, it typically indicates that
36-
* JavaScript is disabled on the client side, or that the JavaScript snippet was not properly
37-
* implemented, or that the Origin/Referer headers were filtered out.
38-
*
39-
* Requests lacking both double-submit and origin information are deemed insecure.
35+
* The check relying on the double-submit requires a JavaScript snippet on the client side,
36+
* responsible for generating a cryptographically-secure random token and attaching it to the request
37+
* payload and as a cookie and/or a header. This check is meant to cover the case where neither
38+
* Sec-Fetch-Site, nor Origin/Referer headers are present.
4039
*
4140
* When a session is found, a behavioral check is added to ensure that the validation method does not
42-
* downgrade from double-submit to origin checks. This prevents attackers from exploiting potentially
43-
* less secure validation methods once a more secure method has been confirmed as functional.
41+
* downgrade from double-submit to origin checks, and vice versa. This prevents attackers from
42+
* exploiting potentially less secure validation methods once a more secure method has been confirmed
43+
* as functional.
4444
*
4545
* On HTTPS connections, the cookie is prefixed with "__Host-" to prevent it from being forged on an
4646
* HTTP channel. On the JS side, the cookie should be set with samesite=strict to strengthen the CSRF
@@ -50,8 +50,8 @@
5050
* cookie. This makes it harder for an attacker to forge a request, though it may also pose challenges
5151
* when setting the header depending on the client-side framework in use.
5252
*
53-
* When a fallback CSRF token manager is provided, only tokens listed in the $tokenIds argument will be
54-
* managed by this manager. All other tokens will be delegated to the fallback manager.
53+
* When a fallback CSRF token manager is provided, only tokens listed in the $tokenIds argument will
54+
* be managed by this manager. All other tokens will be delegated to the fallback manager.
5555
*
5656
* @author Nicolas Grekas <p@tchwork.com>
5757
*/
@@ -235,6 +235,10 @@ public function onKernelResponse(ResponseEvent $event): void
235235
*/
236236
private function isValidOrigin(Request $request): ?bool
237237
{
238+
if (null !== $header = $request->headers->get('Sec-Fetch-Site')) {
239+
return 'same-origin' === $header;
240+
}
241+
238242
$target = $request->getSchemeAndHttpHost().'/';
239243
$source = 'null';
240244

src/Symfony/Component/Security/Csrf/Tests/SameOriginCsrfTokenManagerTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,31 @@ public function testValidRefererInvalidOrigin()
115115
$this->assertSame(1 << 8, $request->attributes->get('csrf-token'));
116116
}
117117

118+
public function testSecFetchSiteSameOrigin()
119+
{
120+
$request = new Request();
121+
$request->headers->set('Sec-Fetch-Site', 'same-origin');
122+
$this->requestStack->push($request);
123+
124+
$token = new CsrfToken('test_token', str_repeat('a', 24));
125+
126+
$this->logger->expects($this->once())->method('debug')->with('CSRF validation accepted using origin info.');
127+
$this->assertTrue($this->csrfTokenManager->isTokenValid($token));
128+
$this->assertSame(1 << 8, $request->attributes->get('csrf-token'));
129+
}
130+
131+
public function testSecFetchSiteCrossSite()
132+
{
133+
$request = new Request();
134+
$request->headers->set('Sec-Fetch-Site', 'cross-site');
135+
$this->requestStack->push($request);
136+
137+
$token = new CsrfToken('test_token', str_repeat('a', 24));
138+
139+
$this->logger->expects($this->once())->method('warning')->with('CSRF validation failed: origin info doesn\'t match.');
140+
$this->assertFalse($this->csrfTokenManager->isTokenValid($token));
141+
}
142+
118143
public function testValidOriginAfterDoubleSubmit()
119144
{
120145
$session = $this->createMock(Session::class);

0 commit comments

Comments
 (0)