Enonic XP Session Fixation Vulnerability
Description
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.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Enonic XP before 7.7.4 has a session fixation vulnerability allowing remote unauthenticated attackers to reuse prior sessions due to missing session invalidation on login.
CVE-2024-23679 is a session fixation vulnerability in Enonic XP versions prior to 7.7.4. The root cause is that the application fails to invalidate an existing session upon user login, meaning a session ID remains valid even after authentication [1]. This lack of session invalidation allows an attacker to potentially reuse a session ID that was set before the victim logged in.
An unauthenticated remote attacker can exploit this by tricking a victim into using a session ID controlled by the attacker, for example via a crafted link. When the victim authenticates, the session attributes are not invalidated, so the attacker can continue using the same session ID to access the application with the victim's authenticated privileges [1].
Successful exploitation allows the attacker to hijack the victim's authenticated session, gaining unauthorized access to the Enonic XP application and potentially performing actions with the victim's permissions. The impact could include data exposure, privilege escalation, or other malicious activities depending on the application's functionality.
The vulnerability is fixed in Enonic XP version 7.7.4. The patches implemented in commits [2][3][4] show that the login handler now calls session.invalidate() before creating a new session, ensuring old sessions cannot be reused after login. Users are advised to upgrade to version 7.7.4 or later to mitigate this issue.
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.
| Package | Affected versions | Patched versions |
|---|---|---|
com.enonic.xp:lib-authMaven | < 7.7.4 | 7.7.4 |
Affected products
1Patches
32abac31cec86Invalidate old session after login #9253
2 files changed · +55 −19
modules/lib/lib-auth/src/main/java/com/enonic/xp/lib/auth/LoginHandler.java+34 −19 modified@@ -9,6 +9,7 @@ import com.enonic.xp.context.Context; import com.enonic.xp.context.ContextBuilder; +import com.enonic.xp.context.LocalScope; import com.enonic.xp.portal.PortalRequest; import com.enonic.xp.script.bean.BeanContext; import com.enonic.xp.script.bean.ScriptBean; @@ -30,11 +31,6 @@ public final class LoginHandler implements ScriptBean { - private enum Scope - { - SESSION, REQUEST - } - private String user; private String password; @@ -91,6 +87,9 @@ public LoginResultMapper login() { switch ( this.scope ) { + case NONE: + // do nothing + break; case REQUEST: this.context.get().getLocalScope().setAttribute( authInfo ); break; @@ -110,15 +109,26 @@ public LoginResultMapper login() private void createSession( final AuthenticationInfo authInfo ) { - final Session session = this.context.get().getLocalScope().getSession(); + final LocalScope localScope = this.context.get().getLocalScope(); + final Session session = localScope.getSession(); + if ( session != null ) { - session.setAttribute( authInfo ); - } + final var attributes = session.getAttributes(); + session.invalidate(); - if ( this.sessionTimeout != null ) - { - setSessionTimeout(); + final Session newSession = localScope.getSession(); + + if ( newSession != null ) + { + attributes.forEach( newSession::setAttribute ); + session.setAttribute( authInfo ); + + if ( this.sessionTimeout != null ) + { + setSessionTimeout(); + } + } } } @@ -146,9 +156,8 @@ private AuthenticationInfo attemptLoginWithAllExistingIdProviders() private IdProviders getSortedIdProviders() { IdProviders idProviders = securityService.get().getIdProviders(); - return IdProviders.from( idProviders.stream(). - sorted( Comparator.comparing( u -> u.getKey().toString() ) ). - collect( Collectors.toList() ) ); + return IdProviders.from( + idProviders.stream().sorted( Comparator.comparing( u -> u.getKey().toString() ) ).collect( Collectors.toList() ) ); } private AuthenticationInfo attemptLogin() @@ -218,11 +227,12 @@ private AuthenticationInfo authenticate( IdProviderKey idProvider ) private <T> T runAsAuthenticated( Callable<T> runnable ) { final AuthenticationInfo authInfo = AuthenticationInfo.create().principals( RoleKeys.AUTHENTICATED ).user( User.ANONYMOUS ).build(); - return ContextBuilder.from( this.context.get() ). - authInfo( authInfo ). - repositoryId( SystemConstants.SYSTEM_REPO_ID ). - branch( SecurityConstants.BRANCH_SECURITY ).build(). - callWith( runnable ); + return ContextBuilder.from( this.context.get() ) + .authInfo( authInfo ) + .repositoryId( SystemConstants.SYSTEM_REPO_ID ) + .branch( SecurityConstants.BRANCH_SECURITY ) + .build() + .callWith( runnable ); } private boolean isValidEmail( final String value ) @@ -250,4 +260,9 @@ public void initialize( final BeanContext context ) this.context = context.getBinding( Context.class ); this.portalRequestSupplier = context.getBinding( PortalRequest.class ); } + + private enum Scope + { + SESSION, REQUEST, NONE + } }
modules/lib/lib-auth/src/test/java/com/enonic/xp/lib/auth/LoginHandlerTest.java+21 −0 modified@@ -23,6 +23,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; public class LoginHandlerTest extends ScriptTestSupport @@ -171,6 +173,25 @@ public void testLoginMultipleIdProvidersInOrder() assertEquals( "idprovider3", matcher.loginIdProviderAttempts.get( 2 ).toString() ); } + @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(); + } + private static class AuthTokenMatcher implements ArgumentMatcher<AuthenticationToken> {
1f44674eb9abInvalidate old session after login #9253
2 files changed · +52 −19
modules/lib/lib-auth/src/main/java/com/enonic/xp/lib/auth/LoginHandler.java+31 −19 modified@@ -9,6 +9,7 @@ import com.enonic.xp.context.Context; import com.enonic.xp.context.ContextBuilder; +import com.enonic.xp.context.LocalScope; import com.enonic.xp.portal.PortalRequest; import com.enonic.xp.script.bean.BeanContext; import com.enonic.xp.script.bean.ScriptBean; @@ -30,11 +31,6 @@ public final class LoginHandler implements ScriptBean { - private enum Scope - { - SESSION, REQUEST, NONE - } - private String user; private String password; @@ -113,15 +109,26 @@ public LoginResultMapper login() private void createSession( final AuthenticationInfo authInfo ) { - final Session session = this.context.get().getLocalScope().getSession(); + final LocalScope localScope = this.context.get().getLocalScope(); + final Session session = localScope.getSession(); + if ( session != null ) { - session.setAttribute( authInfo ); - } + final var attributes = session.getAttributes(); + session.invalidate(); - if ( this.sessionTimeout != null ) - { - setSessionTimeout(); + final Session newSession = localScope.getSession(); + + if ( newSession != null ) + { + attributes.forEach( newSession::setAttribute ); + session.setAttribute( authInfo ); + + if ( this.sessionTimeout != null ) + { + setSessionTimeout(); + } + } } } @@ -149,9 +156,8 @@ private AuthenticationInfo attemptLoginWithAllExistingIdProviders() private IdProviders getSortedIdProviders() { IdProviders idProviders = securityService.get().getIdProviders(); - return IdProviders.from( idProviders.stream(). - sorted( Comparator.comparing( u -> u.getKey().toString() ) ). - collect( Collectors.toList() ) ); + return IdProviders.from( + idProviders.stream().sorted( Comparator.comparing( u -> u.getKey().toString() ) ).collect( Collectors.toList() ) ); } private AuthenticationInfo attemptLogin() @@ -221,11 +227,12 @@ private AuthenticationInfo authenticate( IdProviderKey idProvider ) private <T> T runAsAuthenticated( Callable<T> runnable ) { final AuthenticationInfo authInfo = AuthenticationInfo.create().principals( RoleKeys.AUTHENTICATED ).user( User.ANONYMOUS ).build(); - return ContextBuilder.from( this.context.get() ). - authInfo( authInfo ). - repositoryId( SystemConstants.SYSTEM_REPO_ID ). - branch( SecurityConstants.BRANCH_SECURITY ).build(). - callWith( runnable ); + return ContextBuilder.from( this.context.get() ) + .authInfo( authInfo ) + .repositoryId( SystemConstants.SYSTEM_REPO_ID ) + .branch( SecurityConstants.BRANCH_SECURITY ) + .build() + .callWith( runnable ); } private boolean isValidEmail( final String value ) @@ -253,4 +260,9 @@ public void initialize( final BeanContext context ) this.context = context.getBinding( Context.class ); this.portalRequestSupplier = context.getBinding( PortalRequest.class ); } + + private enum Scope + { + SESSION, REQUEST, NONE + } }
modules/lib/lib-auth/src/test/java/com/enonic/xp/lib/auth/LoginHandlerTest.java+21 −0 modified@@ -23,6 +23,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; public class LoginHandlerTest extends ScriptTestSupport @@ -192,6 +194,25 @@ public void testLoginMultipleIdProvidersInOrder() assertEquals( "idprovider3", matcher.loginIdProviderAttempts.get( 2 ).toString() ); } + @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(); + } + private static class AuthTokenMatcher implements ArgumentMatcher<AuthenticationToken> {
0189975691e9Invalidate old session after login #9253
2 files changed · +52 −19
modules/lib/lib-auth/src/main/java/com/enonic/xp/lib/auth/LoginHandler.java+31 −19 modified@@ -9,6 +9,7 @@ import com.enonic.xp.context.Context; import com.enonic.xp.context.ContextBuilder; +import com.enonic.xp.context.LocalScope; import com.enonic.xp.portal.PortalRequest; import com.enonic.xp.script.bean.BeanContext; import com.enonic.xp.script.bean.ScriptBean; @@ -30,11 +31,6 @@ public final class LoginHandler implements ScriptBean { - private enum Scope - { - SESSION, REQUEST, NONE - } - private String user; private String password; @@ -113,15 +109,26 @@ public LoginResultMapper login() private void createSession( final AuthenticationInfo authInfo ) { - final Session session = this.context.get().getLocalScope().getSession(); + final LocalScope localScope = this.context.get().getLocalScope(); + final Session session = localScope.getSession(); + if ( session != null ) { - session.setAttribute( authInfo ); - } + final var attributes = session.getAttributes(); + session.invalidate(); - if ( this.sessionTimeout != null ) - { - setSessionTimeout(); + final Session newSession = localScope.getSession(); + + if ( newSession != null ) + { + attributes.forEach( newSession::setAttribute ); + session.setAttribute( authInfo ); + + if ( this.sessionTimeout != null ) + { + setSessionTimeout(); + } + } } } @@ -149,9 +156,8 @@ private AuthenticationInfo attemptLoginWithAllExistingIdProviders() private IdProviders getSortedIdProviders() { IdProviders idProviders = securityService.get().getIdProviders(); - return IdProviders.from( idProviders.stream(). - sorted( Comparator.comparing( u -> u.getKey().toString() ) ). - collect( Collectors.toList() ) ); + return IdProviders.from( + idProviders.stream().sorted( Comparator.comparing( u -> u.getKey().toString() ) ).collect( Collectors.toList() ) ); } private AuthenticationInfo attemptLogin() @@ -221,11 +227,12 @@ private AuthenticationInfo authenticate( IdProviderKey idProvider ) private <T> T runAsAuthenticated( Callable<T> runnable ) { final AuthenticationInfo authInfo = AuthenticationInfo.create().principals( RoleKeys.AUTHENTICATED ).user( User.ANONYMOUS ).build(); - return ContextBuilder.from( this.context.get() ). - authInfo( authInfo ). - repositoryId( SystemConstants.SYSTEM_REPO_ID ). - branch( SecurityConstants.BRANCH_SECURITY ).build(). - callWith( runnable ); + return ContextBuilder.from( this.context.get() ) + .authInfo( authInfo ) + .repositoryId( SystemConstants.SYSTEM_REPO_ID ) + .branch( SecurityConstants.BRANCH_SECURITY ) + .build() + .callWith( runnable ); } private boolean isValidEmail( final String value ) @@ -253,4 +260,9 @@ public void initialize( final BeanContext context ) this.context = context.getBinding( Context.class ); this.portalRequestSupplier = context.getBinding( PortalRequest.class ); } + + private enum Scope + { + SESSION, REQUEST, NONE + } }
modules/lib/lib-auth/src/test/java/com/enonic/xp/lib/auth/LoginHandlerTest.java+21 −0 modified@@ -23,6 +23,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; public class LoginHandlerTest extends ScriptTestSupport @@ -192,6 +194,25 @@ public void testLoginMultipleIdProvidersInOrder() assertEquals( "idprovider3", matcher.loginIdProviderAttempts.get( 2 ).toString() ); } + @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(); + } + private static class AuthTokenMatcher implements ArgumentMatcher<AuthenticationToken> {
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-4m5p-5w5w-3jcfghsathird-party-advisoryADVISORY
- github.com/enonic/xp/security/advisories/GHSA-4m5p-5w5w-3jcfghsavendor-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2024-23679ghsaADVISORY
- vulncheck.com/advisories/vc-advisory-GHSA-4m5p-5w5w-3jcfghsathird-party-advisoryWEB
- github.com/enonic/xp/commit/0189975691e9e6407a9fee87006f730e84f734ffghsarelatedWEB
- github.com/enonic/xp/commit/1f44674eb9ab3fbab7103e8d08067846e88bace4ghsarelatedWEB
- github.com/enonic/xp/commit/2abac31cec8679074debc4f1fb69c25930e40842ghsarelatedWEB
- github.com/enonic/xp/issues/9253ghsaissue-trackingWEB
News mentions
0No linked articles in our index yet.