VYPR
Moderate severityNVD Advisory· Published Nov 14, 2023· Updated Aug 29, 2024

Weak Authentication in Session Handling in typo3/cms-core

CVE-2023-47127

Description

TYPO3 is an open source PHP based web content management system released under the GNU GPL. In typo3 installations there are always at least two different sites. Eg. first.example.org and second.example.com. In affected versions a session cookie generated for the first site can be reused on the second site without requiring additional authentication. This vulnerability has been addressed in versions 8.7.55, 9.5.44, 10.4.41, 11.5.33, and 12.4.8. Users are advised to upgrade. There are no known workarounds for this vulnerability.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

TYPO3 sites share session cookies, allowing cross-site authentication reuse without additional credentials.

Vulnerability

Description

CVE-2023-47127 affects TYPO3, an open-source PHP content management system. In multi-site TYPO3 installations, the session cookie generated for one site (e.g., first.example.org) could be reused on another site (e.g., second.example.com) without requiring additional authentication. The root cause is that the cookie domain and path were not properly restricted to the originating site, allowing the same session identifier to be valid across different sites [1].

Exploitation

An attacker who obtains a valid session cookie from one TYPO3 site can present that cookie to a second TYPO3 site within the same installation. No additional authentication is needed because the cookie scope was not limited. The attack does not require any special network position; it is a cross-site session reuse weakness [1]. The vulnerability is present in TYPO3 versions prior to 8.7.55, 9.5.44, 10.4.41, 11.5.33, and 12.4.8.

Impact

Successful exploitation allows an attacker to impersonate a user across different TYPO3 sites without knowing their credentials. This can lead to unauthorized access to content, administrative functions, or sensitive data on the affected sites. The severity is elevated because many TYPO3 deployments host multiple distinct sites on the same installation [4].

Mitigation

The vulnerability has been fixed by limiting the session cookie scope to the requesting site's domain and path. The commit [3] introduces a CookieScopeTrait and uses getCookieScope() to restrict the cookie to the appropriate domain and path. Users must upgrade to patched versions (8.7.55, 9.5.44, 10.4.41, 11.5.33, or 12.4.8) as no workarounds are available [1].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
typo3/cms-corePackagist
>= 8.0.0, < 8.7.558.7.55
typo3/cms-corePackagist
>= 9.0.0, < 9.5.449.5.44
typo3/cms-corePackagist
>= 10.0.0, < 10.4.4110.4.41
typo3/cms-corePackagist
>= 11.0.0, < 11.5.3311.5.33
typo3/cms-corePackagist
>= 12.0.0, < 12.4.812.4.8

Affected products

3

Patches

1
535dfbdc54fd

[SECURITY] Limit user session to cookie domain

https://github.com/TYPO3/typo3Benjamin FranzkeNov 14, 2023via ghsa
9 files changed · +210 63
  • typo3/sysext/core/Classes/Http/CookieScope.php+27 0 added
    @@ -0,0 +1,27 @@
    +<?php
    +
    +declare(strict_types=1);
    +
    +/*
    + * This file is part of the TYPO3 CMS project.
    + *
    + * It is free software; you can redistribute it and/or modify it under
    + * the terms of the GNU General Public License, either version 2
    + * of the License, or any later version.
    + *
    + * For the full copyright and license information, please read the
    + * LICENSE.txt file that was distributed with this source code.
    + *
    + * The TYPO3 project - inspiring people to share!
    + */
    +
    +namespace TYPO3\CMS\Core\Http;
    +
    +final class CookieScope
    +{
    +    public function __construct(
    +        public readonly string $domain,
    +        public readonly bool $hostOnly,
    +        public readonly string $path,
    +    ) {}
    +}
    
  • typo3/sysext/core/Classes/Http/CookieScopeTrait.php+72 0 added
    @@ -0,0 +1,72 @@
    +<?php
    +
    +declare(strict_types=1);
    +
    +/*
    + * This file is part of the TYPO3 CMS project.
    + *
    + * It is free software; you can redistribute it and/or modify it under
    + * the terms of the GNU General Public License, either version 2
    + * of the License, or any later version.
    + *
    + * For the full copyright and license information, please read the
    + * LICENSE.txt file that was distributed with this source code.
    + *
    + * The TYPO3 project - inspiring people to share!
    + */
    +
    +namespace TYPO3\CMS\Core\Http;
    +
    +trait CookieScopeTrait
    +{
    +    /**
    +     * Returns the domain and path to be used for setting cookies.
    +     * The information is taken from the value in $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'] if set,
    +     * otherwise the normalized request params are used.
    +     */
    +    private function getCookieScope(NormalizedParams $normalizedParams): CookieScope
    +    {
    +        $cookieDomain = $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'] ?? '';
    +        // If a specific cookie domain is defined for a given application type, use that domain
    +        if (!empty($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'])) {
    +            $cookieDomain = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'];
    +        }
    +        if (!$cookieDomain) {
    +            return new CookieScope(
    +                domain: $normalizedParams->getRequestHostOnly(),
    +                hostOnly: true,
    +                // If no cookie domain is set, use the base path
    +                path: $normalizedParams->getSitePath(),
    +            );
    +        }
    +        if ($cookieDomain[0] === '/') {
    +            $match = [];
    +            $matchCount = @preg_match($cookieDomain, $normalizedParams->getRequestHostOnly(), $match);
    +            if ($matchCount === false) {
    +                $this->logger->critical(
    +                    'The regular expression for the cookie domain ({domain}) contains errors. The session is not shared across sub-domains.',
    +                    ['domain' => $cookieDomain]
    +                );
    +            }
    +            if ($matchCount === false || $matchCount === 0) {
    +                return new CookieScope(
    +                    domain: $normalizedParams->getRequestHostOnly(),
    +                    hostOnly: true,
    +                    // If no cookie domain could be matched, use the base path
    +                    path: $normalizedParams->getSitePath(),
    +                );
    +            }
    +            $cookieDomain = $match[0];
    +        }
    +
    +        return new CookieScope(
    +            // Normalize cookie domain by removing leading and trailing dots,
    +            // see https://www.rfc-editor.org/rfc/rfc6265#section-4.1.2.3
    +            // > Note that a leading %x2E ("."), if present, is ignored even though that character is not permitted,
    +            // > but a trailing %x2E ("."), if present, will cause the user agent to ignore the attribute.
    +            domain: trim($cookieDomain, '.'),
    +            hostOnly: false,
    +            path: '/',
    +        );
    +    }
    +}
    
  • typo3/sysext/core/Classes/Http/SetCookieService.php+15 45 modified
    @@ -32,6 +32,7 @@
     class SetCookieService
     {
         use CookieHeaderTrait;
    +    use CookieScopeTrait;
     
         protected readonly LoggerInterface $logger;
     
    @@ -65,9 +66,7 @@ public function setSessionCookie(UserSession $userSession, NormalizedParams $nor
             $isRefreshTimeBasedCookie = $this->isRefreshTimeBasedCookie($userSession);
             if ($this->isSetSessionCookie($userSession) || $isRefreshTimeBasedCookie) {
                 // Get the domain to be used for the cookie (if any):
    -            $cookieDomain = $this->getCookieDomain($normalizedParams);
    -            // If no cookie domain is set, use the base path:
    -            $cookiePath = $cookieDomain ? '/' : $normalizedParams->getSitePath();
    +            $cookieScope = $this->getCookieScope($normalizedParams);
                 // If the cookie lifetime is set, use it:
                 $cookieExpire = $isRefreshTimeBasedCookie ? $GLOBALS['EXEC_TIME'] + $this->lifetime : 0;
                 // Valid options are "strict", "lax" or "none", whereas "none" only works in HTTPS requests (default & fallback is "strict")
    @@ -78,13 +77,19 @@ public function setSessionCookie(UserSession $userSession, NormalizedParams $nor
                 // SameSite "none" needs the secure option (only allowed on HTTPS)
                 $isSecure = $cookieSameSite === Cookie::SAMESITE_NONE || $normalizedParams->isHttps();
                 $sessionId = $userSession->getIdentifier();
    -            $cookieValue = $userSession->getJwt();
    +            $cookieValue = $userSession->getJwt($cookieScope);
                 $setCookie = new Cookie(
                     $this->name,
                     $cookieValue,
                     $cookieExpire,
    -                $cookiePath,
    -                $cookieDomain,
    +                $cookieScope->path,
    +                // Host-Only cookies need to be provided without an explicit domain,
    +                // see https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2.3
    +                // and https://datatracker.ietf.org/doc/html/rfc6265#section-5.3
    +                // | * If the value of the Domain attribute is "example.com", the user agent will include the cookie
    +                // |   in the Cookie header when making HTTP requests to example.com, www.example.com, and www.corp.example.com
    +                // | * If the server omits the Domain attribute, the user agent will return the cookie only to the origin server.
    +                $cookieScope->hostOnly ? null : $cookieScope->domain,
                     $isSecure,
                     true,
                     false,
    @@ -93,45 +98,12 @@ public function setSessionCookie(UserSession $userSession, NormalizedParams $nor
                 $message = $isRefreshTimeBasedCookie ? 'Updated Cookie: {session}, {domain}' : 'Set Cookie: {session}, {domain}';
                 $this->logger->debug($message, [
                     'session' => sha1($sessionId),
    -                'domain' => $cookieDomain,
    +                'domain' => $cookieScope->domain,
                 ]);
             }
             return $setCookie;
         }
     
    -    /**
    -     * Gets the domain to be used on setting cookies.
    -     * The information is taken from the value in $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'].
    -     *
    -     * @return string The domain to be used on setting cookies
    -     */
    -    protected function getCookieDomain(NormalizedParams $normalizedParams): string
    -    {
    -        $result = '';
    -        $cookieDomain = $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'] ?? '';
    -        // If a specific cookie domain is defined for a given application type, use that domain
    -        if (!empty($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'])) {
    -            $cookieDomain = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'];
    -        }
    -        if ($cookieDomain) {
    -            if ($cookieDomain[0] === '/') {
    -                $match = [];
    -                $matchCnt = @preg_match($cookieDomain, $normalizedParams->getRequestHostOnly(), $match);
    -                if ($matchCnt === false) {
    -                    $this->logger->critical(
    -                        'The regular expression for the cookie domain ({domain}) contains errors. The session is not shared across sub-domains.',
    -                        ['domain' => $cookieDomain]
    -                    );
    -                } elseif ($matchCnt) {
    -                    $result = $match[0];
    -                }
    -            } else {
    -                $result = $cookieDomain;
    -            }
    -        }
    -        return $result;
    -    }
    -
         /**
          * Determine whether a session cookie needs to be set (lifetime=0)
          */
    @@ -180,15 +152,13 @@ public function isCookieSet(?ServerRequestInterface $request, ?UserSession $user
          */
         public function removeCookie(NormalizedParams $normalizedParams): Cookie
         {
    -        $cookieDomain = $this->getCookieDomain($normalizedParams);
    -        // If no cookie domain is set, use the base path
    -        $cookiePath = $cookieDomain ? '/' : $normalizedParams->getSitePath();
    +        $scope = $this->getCookieScope($normalizedParams);
             return new Cookie(
                 $this->name,
                 '',
                 -1,
    -            $cookiePath,
    -            $cookieDomain
    +            $scope->path,
    +            $scope->domain
             );
         }
     }
    
  • typo3/sysext/core/Classes/Session/UserSessionManager.php+9 3 modified
    @@ -22,6 +22,7 @@
     use Psr\Log\LoggerAwareTrait;
     use TYPO3\CMS\Core\Authentication\IpLocker;
     use TYPO3\CMS\Core\Crypto\Random;
    +use TYPO3\CMS\Core\Http\CookieScopeTrait;
     use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
     use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface;
     use TYPO3\CMS\Core\Utility\GeneralUtility;
    @@ -43,6 +44,7 @@
     class UserSessionManager implements LoggerAwareInterface
     {
         use LoggerAwareTrait;
    +    use CookieScopeTrait;
     
         protected const SESSION_ID_LENGTH = 32;
         protected const GARBAGE_COLLECTION_LIFETIME = 86400;
    @@ -59,17 +61,19 @@ class UserSessionManager implements LoggerAwareInterface
         protected int $garbageCollectionForAnonymousSessions = self::LIFETIME_OF_ANONYMOUS_SESSION_DATA;
         protected SessionBackendInterface $sessionBackend;
         protected IpLocker $ipLocker;
    +    protected string $loginType;
     
         /**
          * Constructor. Marked as internal, as it is recommended to use the factory method "create"
          *
          * @internal it is recommended to use the factory method "create"
          */
    -    public function __construct(SessionBackendInterface $sessionBackend, int $sessionLifetime, IpLocker $ipLocker)
    +    public function __construct(SessionBackendInterface $sessionBackend, int $sessionLifetime, IpLocker $ipLocker, string $loginType)
         {
             $this->sessionBackend = $sessionBackend;
             $this->sessionLifetime = $sessionLifetime;
             $this->ipLocker = $ipLocker;
    +        $this->loginType = $loginType;
         }
     
         protected function setGarbageCollectionTimeoutForAnonymousSessions(int $garbageCollectionForAnonymousSessions = 0): void
    @@ -91,7 +95,8 @@ public function createFromRequestOrAnonymous(ServerRequestInterface $request, st
         {
             try {
                 $cookieValue = (string)($request->getCookieParams()[$cookieName] ?? '');
    -            $sessionId = UserSession::resolveIdentifierFromJwt($cookieValue);
    +            $scope = $this->getCookieScope($request->getAttribute('normalizedParams'));
    +            $sessionId = UserSession::resolveIdentifierFromJwt($cookieValue, $scope);
             } catch (\Exception $exception) {
                 $this->logger->debug('Could not resolve session identifier from JWT', ['exception' => $exception]);
             }
    @@ -354,7 +359,8 @@ public static function create(string $loginType, int $sessionLifetime = null, Se
                 self::class,
                 $sessionManager->getSessionBackend($loginType),
                 $sessionLifetime,
    -            $ipLocker
    +            $ipLocker,
    +            $loginType
             );
             if ($loginType === 'FE') {
                 $object->setGarbageCollectionTimeoutForAnonymousSessions((int)($GLOBALS['TYPO3_CONF_VARS']['FE']['sessionDataLifetime'] ?? 0));
    
  • typo3/sysext/core/Classes/Session/UserSession.php+30 5 modified
    @@ -17,7 +17,10 @@
     
     namespace TYPO3\CMS\Core\Session;
     
    +use TYPO3\CMS\Core\Http\CookieScope;
    +use TYPO3\CMS\Core\Log\LogManager;
     use TYPO3\CMS\Core\Security\JwtTrait;
    +use TYPO3\CMS\Core\Utility\GeneralUtility;
     
     /**
      * Represents all information about a user's session.
    @@ -195,17 +198,19 @@ public function needsUpdate(): bool
     
         /**
          * Gets session ID wrapped in JWT to be used for emitting a new cookie.
    -     * `Cookie: <JWT(HS256, [identifier => <session-id>], <signature>)>`
    +     * `Cookie: <JWT(HS256, [identifier => <session-id>], <signature(encryption-key, cookie-domain)>)>`
          *
    +     * @param ?CookieScope $scope
          * @return string the session ID wrapped in JWT to be used for emitting a new cookie
          */
    -    public function getJwt(): string
    +    public function getJwt(?CookieScope $scope = null): string
         {
             // @todo payload could be organized in a new `SessionToken` object
             return self::encodeHashSignedJwt(
                 [
                     'identifier' => $this->identifier,
                     'time' => (new \DateTimeImmutable())->format(\DateTimeImmutable::RFC3339),
    +                'scope' => $scope,
                 ],
                 self::createSigningKeyFromEncryptionKey(UserSession::class)
             );
    @@ -246,20 +251,40 @@ public static function createNonFixated(string $identifier): self
     
         /**
          * Verifies and resolves the session ID from a submitted cookie value:
    -     * `Cookie: <JWT(HS256, [identifier => <session-id>], <signature>)>`
    +     * `Cookie: <JWT(HS256, [identifier => <session-id>], <signature(encryption-key, cookie-domain)>)>`
          *
          * @param string $cookieValue submitted cookie value
    +     * @param CookieScope $scope
          * @return non-empty-string|null session ID, null in case verification failed
          * @throws \Exception
          * @see getJwt()
          */
    -    public static function resolveIdentifierFromJwt(string $cookieValue): ?string
    +    public static function resolveIdentifierFromJwt(string $cookieValue, CookieScope $scope): ?string
         {
             if ($cookieValue === '') {
                 return null;
             }
    +
             $payload = self::decodeJwt($cookieValue, self::createSigningKeyFromEncryptionKey(UserSession::class));
    -        return !empty($payload->identifier) && is_string($payload->identifier) ? $payload->identifier : null;
    +
    +        $identifier = !empty($payload->identifier) && is_string($payload->identifier) ? $payload->identifier : null;
    +        if ($identifier === null) {
    +            return null;
    +        }
    +
    +        $domainScope = (string)($payload->scope->domain ?? '');
    +        $pathScope = (string)($payload->scope->path ?? '');
    +        if ($domainScope === '' || $pathScope === '') {
    +            $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(self::class);
    +            $logger->notice('A session cookie with out a domain scope has been used', ['cookieHash' => substr(sha1($cookieValue), 0, 12)]);
    +            return $identifier;
    +        }
    +        if ($domainScope !== $scope->domain || $pathScope !== $scope->path) {
    +            // invalid scope, the cookie jwt has been used on a wrong path or domain
    +            return null;
    +        }
    +
    +        return $identifier;
         }
     
         /**
    
  • typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php+2 1 modified
    @@ -80,7 +80,8 @@ public function logoffCleansFormProtectionIfBackendUserIsLoggedIn(): void
             $userSessionManager = new UserSessionManager(
                 $sessionBackendMock,
                 86400,
    -            new IpLocker(0, 0)
    +            new IpLocker(0, 0),
    +            'BE'
             );
     
             $GLOBALS['BE_USER'] = $this->getMockBuilder(BackendUserAuthentication::class)->getMock();
    
  • typo3/sysext/core/Tests/Unit/Session/UserSessionManagerTest.php+33 6 modified
    @@ -20,6 +20,7 @@
     use Psr\Http\Message\ServerRequestInterface;
     use Psr\Log\NullLogger;
     use TYPO3\CMS\Core\Authentication\IpLocker;
    +use TYPO3\CMS\Core\Http\NormalizedParams;
     use TYPO3\CMS\Core\Security\JwtTrait;
     use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
     use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface;
    @@ -62,7 +63,8 @@ public function willExpireWillExpire(int $sessionLifetime, int $gracePeriod, boo
             $subject = new UserSessionManager(
                 $sessionBackendMock,
                 $sessionLifetime,
    -            new IpLocker(0, 0)
    +            new IpLocker(0, 0),
    +            'FE'
             );
             $session = $subject->createAnonymousSession();
             self::assertEquals($expectedResult, $subject->willExpire($session, $gracePeriod));
    @@ -75,7 +77,8 @@ public function hasExpiredIsCalculatedCorrectly(): void
             $subject = new UserSessionManager(
                 $sessionBackendMock,
                 60,
    -            new IpLocker(0, 0)
    +            new IpLocker(0, 0),
    +            'FE'
             );
             $expiredSession = UserSession::createFromRecord('random-string', ['ses_tstamp' => time() - 500]);
             self::assertTrue($subject->hasExpired($expiredSession));
    @@ -100,17 +103,31 @@ public function createFromRequestOrAnonymousCreatesProperSessionObjectForValidSe
             $subject = new UserSessionManager(
                 $sessionBackendMock,
                 50,
    -            new IpLocker(0, 0)
    +            new IpLocker(0, 0),
    +            'FE'
             );
             $subject->setLogger(new NullLogger());
    +        $cookieDomain = 'example.org';
             $validSessionJwt = self::encodeHashSignedJwt(
                 [
                     'identifier' => 'valid-session',
                     'time' => (new \DateTimeImmutable())->format(\DateTimeImmutable::RFC3339),
    +                'scope' => [
    +                    'domain' => $cookieDomain,
    +                    'path' => '/',
    +                ],
                 ],
                 self::createSigningKeyFromEncryptionKey(UserSession::class)
             );
    +
    +        $normalizedParams = $this->createMock(NormalizedParams::class);
    +        $normalizedParams->method('getRequestHostOnly')->willReturn($cookieDomain);
    +        $normalizedParams->method('getSitePath')->willReturn('/');
             $request = $this->createMock(ServerRequestInterface::class);
    +        $request->method('getAttribute')->willReturnCallback(static fn(string $name): mixed => match ($name) {
    +            'normalizedParams' => $normalizedParams,
    +            default => null,
    +        });
             $request->method('getCookieParams')->willReturn(['bar' => $validSessionJwt]);
             $persistedSession = $subject->createFromRequestOrAnonymous($request, 'bar');
             self::assertEquals(13, $persistedSession->getUserId());
    @@ -134,11 +151,19 @@ public function createFromRequestOrAnonymousCreatesProperSessionObjectForInvalid
             $subject = new UserSessionManager(
                 $sessionBackendMock,
                 50,
    -            new IpLocker(0, 0)
    +            new IpLocker(0, 0),
    +            'FE'
             );
             $subject->setLogger(new NullLogger());
     
    +        $cookieDomain = 'example.org';
    +        $normalizedParams = $this->createMock(NormalizedParams::class);
    +        $normalizedParams->method('getRequestHostOnly')->willReturn($cookieDomain);
             $request = $this->createMock(ServerRequestInterface::class);
    +        $request->method('getAttribute')->willReturnCallback(static fn(string $name): mixed => match ($name) {
    +            'normalizedParams' => $normalizedParams,
    +            default => null,
    +        });
             $request->method('getCookieParams')->willReturnOnConsecutiveCalls([], ['foo' => 'invalid-session']);
             $anonymousSession = $subject->createFromRequestOrAnonymous($request, 'foo');
             self::assertTrue($anonymousSession->isNew());
    @@ -165,7 +190,8 @@ public function updateSessionWillSetLastUpdated(): void
             $subject = new UserSessionManager(
                 $sessionBackendMock,
                 60,
    -            new IpLocker(0, 0)
    +            new IpLocker(0, 0),
    +            'FE'
             );
             $session = UserSession::createFromRecord('random-string', ['ses_tstamp' => time() - 500]);
             $session = $subject->updateSession($session);
    @@ -188,7 +214,8 @@ public function fixateAnonymousSessionWillUpdateSessionObject(): void
             $subject = new UserSessionManager(
                 $sessionBackendMock,
                 60,
    -            new IpLocker(0, 0)
    +            new IpLocker(0, 0),
    +            'FE'
             );
             $session = UserSession::createFromRecord('random-string', ['ses_tstamp' => time() - 500]);
             $session = $subject->fixateAnonymousSession($session);
    
  • typo3/sysext/core/Tests/Unit/Session/UserSessionTest.php+3 1 modified
    @@ -17,6 +17,7 @@
     
     namespace TYPO3\CMS\Core\Tests\Unit\Session;
     
    +use TYPO3\CMS\Core\Http\CookieScope;
     use TYPO3\CMS\Core\Security\JwtTrait;
     use TYPO3\CMS\Core\Session\UserSession;
     use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
    @@ -38,6 +39,7 @@ public function createFromRecordTest(): void
                 'ses_tstamp' => 1607041477,
                 'ses_permanent' => 1,
             ];
    +        $scope = new CookieScope(domain: 'example.com', hostOnly: true, path: '/');
     
             $session = UserSession::createFromRecord($record['ses_id'], $record, true);
     
    @@ -61,7 +63,7 @@ public function createFromRecordTest(): void
     
             self::assertTrue($session->dataWasUpdated());
             self::assertEquals(['override' => 'data'], $session->getData());
    -        self::assertSame($record['ses_id'], UserSession::resolveIdentifierFromJwt($session->getJwt()));
    +        self::assertSame($record['ses_id'], UserSession::resolveIdentifierFromJwt($session->getJwt($scope), $scope) ?? '');
         }
     
         /**
    
  • typo3/sysext/frontend/Tests/Functional/Authentication/FrontendUserAuthenticationTest.php+19 2 modified
    @@ -19,6 +19,9 @@
     
     use GuzzleHttp\Cookie\SetCookie;
     use Psr\Log\NullLogger;
    +use TYPO3\CMS\Core\Core\Environment;
    +use TYPO3\CMS\Core\Http\NormalizedParams;
    +use TYPO3\CMS\Core\Http\ServerRequest;
     use TYPO3\CMS\Core\Security\Nonce;
     use TYPO3\CMS\Core\Security\RequestToken;
     use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait;
    @@ -64,6 +67,19 @@ public function canCreateNewAndExistingSessionWithValidRequestToken(): void
         {
             $this->importCSVDataSet(__DIR__ . '/Fixtures/fe_users.csv');
     
    +        $normalizedParams = new NormalizedParams(
    +            [
    +                'REQUEST_URI' => '/',
    +                'HTTP_HOST' => 'localhost',
    +                'DOCUMENT_ROOT' => Environment::getPublicPath(),
    +                'SCRIPT_FILENAME' => Environment::getPublicPath() . '/index.php',
    +                'SCRIPT_NAME' => '/index.php',
    +            ],
    +            $GLOBALS['TYPO3_CONF_VARS']['SYS'],
    +            Environment::getPublicPath() . '/index.php',
    +            Environment::getPublicPath()
    +        );
    +
             $nonce = Nonce::create();
             $requestToken = RequestToken::create('core/user-auth/fe')->toHashSignedJwt($nonce);
             $request = (new InternalRequest())
    @@ -77,6 +93,7 @@ public function canCreateNewAndExistingSessionWithValidRequestToken(): void
                         '__RequestToken' => $requestToken,
                     ]
                 )
    +            ->withAttribute('normalizedParams', $normalizedParams)
                 ->withCookieParams([123 => 'bogus', 'typo3nonce_' . $nonce->getSigningIdentifier()->name => $nonce->toHashSignedJwt()]);
     
             $response = $this->executeFrontendSubRequest($request);
    @@ -86,8 +103,8 @@ public function canCreateNewAndExistingSessionWithValidRequestToken(): void
     
             // Now check whether the existing session is retrieved by providing the retrieved JWT token in the cookie params.
             $cookie = SetCookie::fromString($response->getHeaderLine('Set-Cookie'));
    -        $request = (new InternalRequest())
    -            ->withPageId(self::ROOT_PAGE_ID)
    +        $request = (new ServerRequest('http://localhost/'))
    +            ->withAttribute('normalizedParams', $normalizedParams)
                 ->withCookieParams([$cookie->getName() => $cookie->getValue()]);
     
             $frontendUserAuthentication = new FrontendUserAuthentication();
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.