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

Appearance settings

[Security] Hiding userFqcn in RememberMe cookie #59232

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

Open
wants to merge 1 commit into
base: 7.3
Choose a base branch
Loading
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 2 src/Symfony/Component/Security/Http/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ CHANGELOG

7.3
---

* Rename property userFqcn to userFqcnHash, remove method getUserFqcn, add method getUserFqcnHash.
* Add encryption support to `OidcTokenHandler` (JWE)

7.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): U
[$series, $tokenValue] = explode(':', $rememberMeDetails->getValue(), 2);
$persistentToken = $this->tokenProvider->loadTokenBySeries($series);

if ($persistentToken->getUserIdentifier() !== $rememberMeDetails->getUserIdentifier() || $persistentToken->getClass() !== $rememberMeDetails->getUserFqcn()) {
if ($persistentToken->getUserIdentifier() !== $rememberMeDetails->getUserIdentifier() || !hash_equals(RememberMeDetails::computeUserFqcnHash($persistentToken->getClass()), $rememberMeDetails->getUserFqcnHash())) {
throw new AuthenticationException('The cookie\'s hash is invalid.');
}

Expand All @@ -89,7 +89,7 @@ public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): U
}

return parent::consumeRememberMeCookie(new RememberMeDetails(
$persistentToken->getClass(),
RememberMeDetails::computeUserFqcnHash($persistentToken->getClass()),
$persistentToken->getUserIdentifier(),
$expires,
$persistentToken->getLastUsed()->getTimestamp().':'.$series.':'.$tokenValue.':'.$persistentToken->getClass()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class RememberMeDetails
public const COOKIE_DELIMITER = ':';

public function __construct(
private string $userFqcn,
private string $userFqcnHash,
private string $userIdentifier,
private int $expires,
private string $value,
Expand All @@ -48,7 +48,12 @@ public static function fromRawCookie(string $rawCookie): self

public static function fromPersistentToken(PersistentToken $persistentToken, int $expires): self
{
return new static($persistentToken->getClass(), $persistentToken->getUserIdentifier(), $expires, $persistentToken->getSeries().':'.$persistentToken->getTokenValue());
return new static(self::computeUserFqcnHash($persistentToken->getClass()), $persistentToken->getUserIdentifier(), $expires, $persistentToken->getSeries().':'.$persistentToken->getTokenValue());
}

public static function computeUserFqcnHash(string $userFqcn): string
{
return hash('sha256', $userFqcn);
}

public function withValue(string $value): self
Expand All @@ -59,9 +64,9 @@ public function withValue(string $value): self
return $details;
}

public function getUserFqcn(): string
public function getUserFqcnHash(): string
{
return $this->userFqcn;
return $this->userFqcnHash;
}

public function getUserIdentifier(): string
Expand All @@ -82,6 +87,6 @@ public function getValue(): string
public function toString(): string
{
// $userIdentifier is encoded because it might contain COOKIE_DELIMITER, we assume other values don't
return implode(self::COOKIE_DELIMITER, [strtr($this->userFqcn, '\\', '.'), strtr(base64_encode($this->userIdentifier), '+/=', '-_~'), $this->expires, $this->value]);
return implode(self::COOKIE_DELIMITER, [$this->userFqcnHash, strtr(base64_encode($this->userIdentifier), '+/=', '-_~'), $this->expires, $this->value]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function createRememberMeCookie(UserInterface $user): void
$expires = time() + $this->options['lifetime'];
$value = $this->signatureHasher->computeSignatureHash($user, $expires);

$details = new RememberMeDetails($user::class, $user->getUserIdentifier(), $expires, $value);
$details = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash($user::class), $user->getUserIdentifier(), $expires, $value);
$this->createCookie($details);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public static function provideSupportsData()

public function testAuthenticate()
{
$rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', 1, 'secret');
$rememberMeDetails = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 1, 'secret');
$request = Request::create('/', 'GET', [], ['_remember_me_cookie' => $rememberMeDetails->toString()]);
$passport = $this->authenticator->authenticate($request);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public function testClearRememberMeCookie()
->method('deleteTokenBySeries')
->with('series1');

$this->request->cookies->set('REMEMBERME', (new RememberMeDetails(InMemoryUser::class, 'wouter', 0, 'series1:tokenvalue'))->toString());
$this->request->cookies->set('REMEMBERME', (new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 0, 'series1:tokenvalue'))->toString());

$this->handler->clearRememberMeCookie();

Expand All @@ -84,7 +84,7 @@ public function testConsumeRememberMeCookieValid()

$this->tokenProvider->expects($this->once())->method('updateToken')->with('series1');

$rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', 360, 'series1:tokenvalue');
$rememberMeDetails = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 360, 'series1:tokenvalue');
$this->handler->consumeRememberMeCookie($rememberMeDetails);

// assert that the cookie has been updated with a new base64 encoded token value
Expand All @@ -110,7 +110,7 @@ public function testConsumeRememberMeCookieInvalidOwner()
->willReturn(new PersistentToken(InMemoryUser::class, 'wouter', 'series1', 'tokenvalue', new \DateTime('-10 min')))
;

$rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'jeremy', 360, 'series1:tokenvalue');
$rememberMeDetails = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'jeremy', 360, 'series1:tokenvalue');

$this->expectException(AuthenticationException::class);
$this->expectExceptionMessage('The cookie\'s hash is invalid.');
Expand All @@ -125,7 +125,7 @@ public function testConsumeRememberMeCookieInvalidValue()
->willReturn(new PersistentToken(InMemoryUser::class, 'wouter', 'series1', 'tokenvalue', new \DateTime('-10 min')))
;

$rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', 360, 'series1:tokenvalue:somethingelse');
$rememberMeDetails = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 360, 'series1:tokenvalue:somethingelse');

$this->expectException(AuthenticationException::class);
$this->expectExceptionMessage('This token was already used. The account is possibly compromised.');
Expand All @@ -151,7 +151,7 @@ public function testConsumeRememberMeCookieValidByValidatorWithoutUpdate()
->willReturn(true)
;

$rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', 360, 'series1:oldTokenValue');
$rememberMeDetails = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 360, 'series1:oldTokenValue');
$handler->consumeRememberMeCookie($rememberMeDetails);

$this->assertFalse($this->request->attributes->has(ResponseListener::COOKIE_ATTR_NAME));
Expand All @@ -168,7 +168,7 @@ public function testConsumeRememberMeCookieInvalidToken()

$this->expectException(CookieTheftException::class);

$this->handler->consumeRememberMeCookie(new RememberMeDetails(InMemoryUser::class, 'wouter', 360, 'series1:tokenvalue'));
$this->handler->consumeRememberMeCookie(new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 360, 'series1:tokenvalue'));
}

public function testConsumeRememberMeCookieExpired()
Expand All @@ -183,7 +183,7 @@ public function testConsumeRememberMeCookieExpired()
$this->expectException(AuthenticationException::class);
$this->expectExceptionMessage('The cookie has expired.');

$this->handler->consumeRememberMeCookie(new RememberMeDetails(InMemoryUser::class, 'wouter', 360, 'series1:tokenvalue'));
$this->handler->consumeRememberMeCookie(new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 360, 'series1:tokenvalue'));
}

public function testBase64EncodedTokens()
Expand All @@ -196,7 +196,7 @@ public function testBase64EncodedTokens()

$this->tokenProvider->expects($this->once())->method('updateToken')->with('series1');

$rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', 360, 'series1:tokenvalue');
$rememberMeDetails = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 360, 'series1:tokenvalue');
$rememberMeDetails = RememberMeDetails::fromRawCookie(base64_encode($rememberMeDetails->toString()));
$this->handler->consumeRememberMeCookie($rememberMeDetails);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public function testCreateRememberMeCookie()

/** @var Cookie $cookie */
$cookie = $this->request->attributes->get(ResponseListener::COOKIE_ATTR_NAME);
$this->assertEquals(strtr(InMemoryUser::class, '\\', '.').':d291dGVy:'.$expire.':'.$signature, $cookie->getValue());
$this->assertEquals(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class).':d291dGVy:'.$expire.':'.$signature, $cookie->getValue());
}

public function testClearRememberMeCookie()
Expand All @@ -77,21 +77,21 @@ public function testConsumeRememberMeCookieValid()
$signature = $this->signatureHasher->computeSignatureHash($user, $expire = time() + 3600);
$this->userProvider->createUser(new InMemoryUser('wouter', null));

$rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', $expire, $signature);
$rememberMeDetails = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', $expire, $signature);
$this->handler->consumeRememberMeCookie($rememberMeDetails);

$this->assertTrue($this->request->attributes->has(ResponseListener::COOKIE_ATTR_NAME));

/** @var Cookie $cookie */
$cookie = $this->request->attributes->get(ResponseListener::COOKIE_ATTR_NAME);
$this->assertNotEquals((new RememberMeDetails(InMemoryUser::class, 'wouter', $expire, $signature))->toString(), $cookie->getValue());
$this->assertNotEquals((new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', $expire, $signature))->toString(), $cookie->getValue());
}

public function testConsumeRememberMeCookieInvalidHash()
{
$this->expectException(AuthenticationException::class);
$this->expectExceptionMessage('The cookie\'s hash is invalid.');
$this->handler->consumeRememberMeCookie(new RememberMeDetails(InMemoryUser::class, 'wouter', time() + 600, 'badsignature'));
$this->handler->consumeRememberMeCookie(new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', time() + 600, 'badsignature'));
}

public function testConsumeRememberMeCookieExpired()
Expand All @@ -101,6 +101,6 @@ public function testConsumeRememberMeCookieExpired()

$this->expectException(AuthenticationException::class);
$this->expectExceptionMessage('The cookie has expired.');
$this->handler->consumeRememberMeCookie(new RememberMeDetails(InMemoryUser::class, 'wouter', 360, $signature));
$this->handler->consumeRememberMeCookie(new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 360, $signature));
}
}
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.