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