From e39e3cd54f27bf3acff378e0e9fc0291a96775b5 Mon Sep 17 00:00:00 2001 From: Alex O'Ree Date: Sun, 23 Aug 2026 15:44:50 -0400 Subject: [PATCH 1/3] JSPWIKI-1301 see jira for details --- .../java/org/apache/wiki/WikiSession.java | 8 +++++ .../auth/DefaultAuthenticationManager.java | 31 +++++++++++++++++++ .../org/apache/wiki/auth/SessionMonitor.java | 17 ++++++++++ 3 files changed, 56 insertions(+) diff --git a/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java b/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java index d2b6fae9aa..dab9059a4f 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java @@ -150,6 +150,14 @@ public String antiCsrfToken() { return antiCsrfToken; } + /** + * Regenerates the anti-CSRF token. Called when the session's privilege level changes (i.e. at login), so that + * a token observed before authentication cannot be replayed against the authenticated session. + */ + public void regenerateAntiCsrfToken() { + antiCsrfToken = UUID.randomUUID().toString(); + } + /** {@inheritDoc} */ @Override public Locale getLocale() { diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java index 6734e094d6..287ec3747f 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java @@ -55,6 +55,7 @@ Licensed to the Apache Software Foundation (ASF) under one import java.util.Properties; import java.util.Set; import org.apache.wiki.WikiContext; +import org.apache.wiki.WikiSession; import org.apache.wiki.auth.user.UserProfile; @@ -178,6 +179,7 @@ public boolean login( final HttpServletRequest request ) throws WikiSecurityExce // If the container logged the user in successfully, tell the Session (and add all the Principals) if (!principals.isEmpty()) { + rotateSessionAfterAuthentication( request, session ); fireEvent( WikiSecurityEvent.LOGIN_AUTHENTICATED, getLoginPrincipal( principals ), session, request ); for( final Principal principal : principals ) { fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, principal, session, request ); @@ -269,6 +271,7 @@ public boolean login( final Session session, final HttpServletRequest request, f // Execute the user's specified login module final Set< Principal > principals = doJAASLogin( m_loginModuleClass, handler, m_loginModuleOptions ); if(!principals.isEmpty()) { + rotateSessionAfterAuthentication( request, session ); fireEvent(WikiSecurityEvent.LOGIN_AUTHENTICATED, getLoginPrincipal( principals ), session, request ); for ( final Principal principal : principals ) { fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, principal, session, request ); @@ -282,6 +285,34 @@ public boolean login( final Session session, final HttpServletRequest request, f return false; } + /** + * Session-fixation defense: the pre-authentication HTTP session id and anti-CSRF token must not survive the + * upgrade to an authenticated session. Rotates the servlet session id, re-keys the {@link SessionMonitor} entry + * so the wiki session attaches to the new id, and regenerates the wiki session's anti-CSRF token so a token + * observed before login cannot be replayed against the authenticated session. + * + * @param request the servlet request, may be null (e.g. embedded use); nothing to rotate then + * @param session the wiki session being upgraded + */ + private void rotateSessionAfterAuthentication( final HttpServletRequest request, final Session session ) { + if( request != null ) { + final HttpSession httpSession = request.getSession( false ); + if( httpSession != null ) { + final SessionMonitor monitor = SessionMonitor.getInstance( m_engine ); + monitor.remove( httpSession ); + try { + request.changeSessionId(); + } catch( final IllegalStateException e ) { + LOG.debug( "Unable to rotate the http session id: {}", e.getMessage() ); + } + monitor.register( httpSession, session ); + } + } + if( session instanceof WikiSession ) { + ( ( WikiSession )session ).regenerateAntiCsrfToken(); + } + } + /** * This method builds a database of login names that are being attempted, and will try to delay if there are too many requests coming * in for the same username. diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/SessionMonitor.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/SessionMonitor.java index 76c1ab6f0d..37f03f2b19 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/auth/SessionMonitor.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/SessionMonitor.java @@ -206,6 +206,23 @@ public final void remove( final HttpSession session ) { } } + /** + * (Re-)associates a wiki session with the user's current HTTP session id. Needed after the HTTP session id has + * been rotated at authentication time (session-fixation defense), so that the upgraded privileges attach to the + * new session id instead of the pre-login one. + * + * @param session the user's HTTP session, carrying its current id + * @param wikiSession the wiki session to register under that id + */ + public final void register( final HttpSession session, final Session wikiSession ) { + if( session == null || wikiSession == null ) { + throw new IllegalArgumentException( "Session cannot be null." ); + } + synchronized( m_sessions ) { + m_sessions.put( session.getId(), wikiSession ); + } + } + /** * Returns the current number of active wiki sessions. * @return the number of sessions From c424698c60b8de8a339e73918fefc1d9b2461b2f Mon Sep 17 00:00:00 2001 From: Alex O'Ree Date: Sun, 20 Sep 2026 16:23:16 -0400 Subject: [PATCH 2/3] JSPWIKI-1301 pr suggestion --- .../src/main/java/org/apache/wiki/api/core/Session.java | 8 +++++++- .../src/main/java/org/apache/wiki/WikiSession.java | 1 + .../apache/wiki/auth/DefaultAuthenticationManager.java | 5 ++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/jspwiki-api/src/main/java/org/apache/wiki/api/core/Session.java b/jspwiki-api/src/main/java/org/apache/wiki/api/core/Session.java index 543827e703..87141facb5 100644 --- a/jspwiki-api/src/main/java/org/apache/wiki/api/core/Session.java +++ b/jspwiki-api/src/main/java/org/apache/wiki/api/core/Session.java @@ -128,7 +128,13 @@ public interface Session extends WikiEventListener { * @return the CSRF protection Token associated with this wiki session. */ String antiCsrfToken(); - + + /** + * Regenerates the anti-CSRF token. Called when the session's privilege level changes (i.e. at login), so that + * a token observed before authentication cannot be replayed against the authenticated session. + */ + void regenerateAntiCsrfToken(); + /** * Returns a cached Locale object for this user. It's better to use WikiContext's corresponding getBundle() method, since that * will actually react if the user changes the locale in the middle, but if that's not available (or, for some reason, you need diff --git a/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java b/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java index dab9059a4f..060420e372 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java @@ -154,6 +154,7 @@ public String antiCsrfToken() { * Regenerates the anti-CSRF token. Called when the session's privilege level changes (i.e. at login), so that * a token observed before authentication cannot be replayed against the authenticated session. */ + @Override public void regenerateAntiCsrfToken() { antiCsrfToken = UUID.randomUUID().toString(); } diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java index 287ec3747f..ebedd578b9 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java @@ -308,9 +308,8 @@ private void rotateSessionAfterAuthentication( final HttpServletRequest request, monitor.register( httpSession, session ); } } - if( session instanceof WikiSession ) { - ( ( WikiSession )session ).regenerateAntiCsrfToken(); - } + session.regenerateAntiCsrfToken(); + } /** From 46f89cab84233e0281b688aeba76da55eccc668d Mon Sep 17 00:00:00 2001 From: Alex O'Ree Date: Sun, 20 Sep 2026 16:23:46 -0400 Subject: [PATCH 3/3] JSPWIKI-1301 javadocs --- jspwiki-api/src/main/java/org/apache/wiki/api/core/Session.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jspwiki-api/src/main/java/org/apache/wiki/api/core/Session.java b/jspwiki-api/src/main/java/org/apache/wiki/api/core/Session.java index 87141facb5..214ef1b3d1 100644 --- a/jspwiki-api/src/main/java/org/apache/wiki/api/core/Session.java +++ b/jspwiki-api/src/main/java/org/apache/wiki/api/core/Session.java @@ -132,6 +132,7 @@ public interface Session extends WikiEventListener { /** * Regenerates the anti-CSRF token. Called when the session's privilege level changes (i.e. at login), so that * a token observed before authentication cannot be replayed against the authenticated session. + * @since 3.0.1 */ void regenerateAntiCsrfToken();