CVE-2024-23679 (Java, published 2024-01-19T21:15Z)
Description: value: Enonic XP versions less than 7.7.4 are vulnerable to a session fixation issue. An remote and unauthenticated attacker can use prior sessions due to the lack of invalidating session attributes.


Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-384 \u2014 Session Fixation
Authenticating a user, or otherwise establishing a new user session, without invalidating any existing session identifier gives an attacker the opportunity to steal authenticated sessions.
Repository: https://github.com/enonic/xp
Files changed:
modules/lib/lib-auth/src/main/java/com/enonic/xp/lib/auth/LoginHandler.java
modules/lib/lib-auth/src/test/java/com/enonic/xp/lib/auth/LoginHandlerTest.java
Commit message: Invalidate old session after login #9253


Vulnerable code:
    private enum Scope
    {
        SESSION, REQUEST, NONE
    }

        final Session session = this.context.get().getLocalScope().getSession();
            session.setAttribute( authInfo );
        }
        if ( this.sessionTimeout != null )
        {
            setSessionTimeout();
        return IdProviders.from( idProviders.stream().
            sorted( Comparator.comparing( u -> u.getKey().toString() ) ).
            collect( Collectors.toList() ) );
        return ContextBuilder.from( this.context.get() ).
            authInfo( authInfo ).
            repositoryId( SystemConstants.SYSTEM_REPO_ID ).
            branch( SecurityConstants.BRANCH_SECURITY ).build().
            callWith( runnable );

Fixed code:
import com.enonic.xp.context.LocalScope;
        final LocalScope localScope = this.context.get().getLocalScope();
        final Session session = localScope.getSession();

            final var attributes = session.getAttributes();
            session.invalidate();
            final Session newSession = localScope.getSession();

            if ( newSession != null )
            {
                attributes.forEach( newSession::setAttribute );
                session.setAttribute( authInfo );

                if ( this.sessionTimeout != null )
                {
                    setSessionTimeout();
                }
            }
        return IdProviders.from(
            idProviders.stream().sorted( Comparator.comparing( u -> u.getKey().toString() ) ).collect( Collectors.toList() ) );
        return ContextBuilder.from( this.context.get() )
            .authInfo( authInfo )
            .repositoryId( SystemConstants.SYSTEM_REPO_ID )
            .branch( SecurityConstants.BRANCH_SECURITY )
            .build()
            .callWith( runnable );

    private enum Scope
    {
        SESSION, REQUEST, NONE
    }
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
    @Test
    public void testSessionInvalidatedOnLogin()
    {
        final AuthenticationInfo authInfo = TestDataFixtures.createAuthenticationInfo();

        final IdProviders idProviders =
            IdProviders.from( IdProvider.create().displayName( "system" ).key( IdProviderKey.from( "system" ) ).build() );

        Mockito.when( this.securityService.authenticate( Mockito.any() ) ).thenReturn( authInfo );
        Mockito.when( this.securityService.getIdProviders() ).thenReturn( idProviders );

        final SessionMock session = Mockito.spy( new SessionMock() );
        ContextAccessor.current().getLocalScope().setSession( session );

        runScript( "/lib/xp/examples/auth/login.js" );

        verify( session, times( 5 ) ).invalidate();
    }