VYPR
Low severityNVD Advisory· Published Aug 30, 2023· Updated Oct 1, 2024

User session is still usable after logout in graylog2-server

CVE-2023-41041

Description

Graylog is a free and open log management platform. In a multi-node Graylog cluster, after a user has explicitly logged out, a user session may still be used for API requests until it has reached its original expiry time. Each node maintains an in-memory cache of user sessions. Upon a cache-miss, the session is loaded from the database. After that, the node operates solely on the cached session. Modifications to sessions will update the cached version as well as the session persisted in the database. However, each node maintains their isolated version of the session. When the user logs out, the session is removed from the node-local cache and deleted from the database. The other nodes will however still use the cached session. These nodes will only fail to accept the session id if they intent to update the session in the database. They will then notice that the session is gone. This is true for most API requests originating from user interaction with the Graylog UI because these will lead to an update of the session's "last access" timestamp. If the session update is however prevented by setting the X-Graylog-No-Session-Extension:true header in the request, the node will consider the (cached) session valid until the session is expired according to its timeout setting. No session identifiers are leaked. After a user has logged out, the UI shows the login screen again, which gives the user the impression that their session is not valid anymore. However, if the session becomes compromised later, it can still be used to perform API requests against the Graylog cluster. The time frame for this is limited to the configured session lifetime, starting from the time when the user logged out. This issue has been addressed in versions 5.0.9 and 5.1.3. Users are advised to upgrade.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.graylog2:graylog2-serverMaven
>= 1.0, < 5.0.95.0.9
org.graylog2:graylog2-serverMaven
>= 5.1.0, < 5.1.35.1.3

Affected products

1

Patches

2
bb88f3d0b2b0

Merge pull request from GHSA-3fqm-frhg-7c85

https://github.com/Graylog2/graylog2-serverBernd AhlersJul 5, 2023via ghsa
8 files changed · +111 31
  • changelog/unreleased/ghsa-3fqm-frhg-7c85.toml+2 0 added
    @@ -0,0 +1,2 @@
    +type = "security"
    +message = "Fix stale session cache after logout. [GHSA-3fqm-frhg-7c85](https://github.com/Graylog2/graylog2-server/security/advisories/GHSA-3fqm-frhg-7c85)"
    
  • graylog2-server/src/main/java/org/graylog2/rest/resources/users/UsersResource.java+26 17 modified
    @@ -28,14 +28,12 @@
     import io.swagger.annotations.ApiResponse;
     import io.swagger.annotations.ApiResponses;
     import org.apache.commons.lang.StringUtils;
    -import org.apache.shiro.SecurityUtils;
     import org.apache.shiro.authz.annotation.RequiresAuthentication;
     import org.apache.shiro.authz.annotation.RequiresPermissions;
     import org.apache.shiro.authz.permission.WildcardPermission;
     import org.apache.shiro.mgt.DefaultSecurityManager;
     import org.apache.shiro.session.Session;
    -import org.apache.shiro.session.mgt.DefaultSessionManager;
    -import org.apache.shiro.session.mgt.eis.SessionDAO;
    +import org.apache.shiro.subject.Subject;
     import org.graylog.security.UserContext;
     import org.graylog.security.permissions.GRNPermission;
     import org.graylog2.audit.AuditEventTypes;
    @@ -61,6 +59,7 @@
     import org.graylog2.security.AccessTokenService;
     import org.graylog2.security.MongoDBSessionService;
     import org.graylog2.security.MongoDbSession;
    +import org.graylog2.security.UserSessionTerminationService;
     import org.graylog2.shared.rest.resources.RestResource;
     import org.graylog2.shared.security.RestPermissions;
     import org.graylog2.shared.users.ChangeUserRequest;
    @@ -137,6 +136,8 @@ public class UsersResource extends RestResource {
         private final RoleService roleService;
         private final MongoDBSessionService sessionService;
         private final SearchQueryParser searchQueryParser;
    +    private final UserSessionTerminationService sessionTerminationService;
    +    private final DefaultSecurityManager securityManager;
     
         protected static final ImmutableMap<String, SearchQueryField> SEARCH_FIELD_MAPPING = ImmutableMap.<String, SearchQueryField>builder()
                 .put(UserOverviewDTO.FIELD_ID, SearchQueryField.create("_id", SearchQueryField.Type.OBJECT_ID))
    @@ -150,12 +151,15 @@ public UsersResource(UserManagementService userManagementService,
                              PaginatedUserService paginatedUserService,
                              AccessTokenService accessTokenService,
                              RoleService roleService,
    -                         MongoDBSessionService sessionService) {
    +                         MongoDBSessionService sessionService,
    +                         UserSessionTerminationService sessionTerminationService, DefaultSecurityManager securityManager) {
             this.userManagementService = userManagementService;
             this.accessTokenService = accessTokenService;
             this.roleService = roleService;
             this.sessionService = sessionService;
             this.paginatedUserService = paginatedUserService;
    +        this.sessionTerminationService = sessionTerminationService;
    +        this.securityManager = securityManager;
             this.searchQueryParser = new SearchQueryParser(UserOverviewDTO.FIELD_FULL_NAME, SEARCH_FIELD_MAPPING);
         }
     
    @@ -439,8 +443,8 @@ public void changeUser(@ApiParam(name = "userId", value = "The ID of the user to
             if (isPermitted("*")) {
                 final Long sessionTimeoutMs = cr.sessionTimeoutMs();
                 if (Objects.nonNull(sessionTimeoutMs) && sessionTimeoutMs != 0 && (user.getSessionTimeoutMs() != sessionTimeoutMs)) {
    -                    updateExistingSession(user, sessionTimeoutMs);
    -                    user.setSessionTimeoutMs(sessionTimeoutMs);
    +                user.setSessionTimeoutMs(sessionTimeoutMs);
    +                terminateSessions(user);
                 }
             }
     
    @@ -451,17 +455,22 @@ public void changeUser(@ApiParam(name = "userId", value = "The ID of the user to
             userManagementService.update(user, cr);
         }
     
    -    private void updateExistingSession(User user, long newSessionTimeOut) {
    -        AllUserSessions allUserSessions = AllUserSessions.create(sessionService);
    -        allUserSessions.forUser(user).ifPresent(userSession -> {
    -            userSession.setTimeout(newSessionTimeOut);
    -            Session session = sessionService.daoToSimpleSession(userSession);
    -
    -            DefaultSecurityManager securityManager = (DefaultSecurityManager) SecurityUtils.getSecurityManager();
    -            DefaultSessionManager sessionManager = (DefaultSessionManager) securityManager.getSessionManager();
    -            SessionDAO sessionDAO = sessionManager.getSessionDAO();
    -            sessionDAO.update(session);
    -        });
    +    private void terminateSessions(User user) {
    +        final List<Session> allSessions = sessionTerminationService.getActiveSessionsForUser(user);
    +
    +        final Subject subject = getSubject();
    +        final Session currentSession = subject.getSession(false);
    +        final User currentUser = getCurrentUser();
    +
    +        if (currentSession != null && currentUser != null && user.getId().equals(currentUser.getId())) {
    +            // Stop all sessions but handle the current session differently by issuing a proper logout
    +            allSessions.stream()
    +                    .filter(session -> !session.getId().equals(currentSession.getId()))
    +                    .forEach(Session::stop);
    +            securityManager.logout(subject);
    +        } else {
    +            allSessions.forEach(Session::stop);
    +        }
         }
     
         private boolean rolesContainAdmin(List<String> roles) {
    
  • graylog2-server/src/main/java/org/graylog2/security/MongoDbSessionDAO.java+14 1 modified
    @@ -23,6 +23,8 @@
     import com.github.rholder.retry.WaitStrategies;
     import com.google.common.collect.Lists;
     import com.google.common.collect.Maps;
    +import com.google.common.eventbus.EventBus;
    +import com.google.common.eventbus.Subscribe;
     import com.mongodb.DuplicateKeyException;
     import org.apache.shiro.session.Session;
     import org.apache.shiro.session.mgt.SimpleSession;
    @@ -44,8 +46,19 @@ public class MongoDbSessionDAO extends CachingSessionDAO {
         private final MongoDBSessionService mongoDBSessionService;
     
         @Inject
    -    public MongoDbSessionDAO(MongoDBSessionService mongoDBSessionService) {
    +    public MongoDbSessionDAO(MongoDBSessionService mongoDBSessionService, EventBus eventBus) {
             this.mongoDBSessionService = mongoDBSessionService;
    +        eventBus.register(this);
    +    }
    +
    +    @SuppressWarnings("unused")
    +    @Subscribe
    +    public void sessionDeleted(SessionDeletedEvent event) {
    +        final Session cachedSession = getCachedSession(event.sessionId());
    +        if (cachedSession != null) {
    +            LOG.debug("Removing deleted session from cache.");
    +            uncache(cachedSession);
    +        }
         }
     
         @Override
    
  • graylog2-server/src/main/java/org/graylog2/security/MongoDBSessionServiceImpl.java+14 1 modified
    @@ -27,6 +27,8 @@
     import org.bson.types.ObjectId;
     import org.graylog2.database.MongoConnection;
     import org.graylog2.database.PersistedServiceImpl;
    +import org.graylog2.events.ClusterEventBus;
    +import org.graylog2.plugin.database.Persisted;
     
     import javax.annotation.Nullable;
     import javax.inject.Inject;
    @@ -36,9 +38,12 @@
     
     @Singleton
     public class MongoDBSessionServiceImpl extends PersistedServiceImpl implements MongoDBSessionService {
    +    private final ClusterEventBus eventBus;
    +
         @Inject
    -    public MongoDBSessionServiceImpl(MongoConnection mongoConnection) {
    +    public MongoDBSessionServiceImpl(MongoConnection mongoConnection, ClusterEventBus clusterEventBus) {
             super(mongoConnection);
    +        this.eventBus = clusterEventBus;
     
             final MongoDatabase database = mongoConnection.getMongoDatabase();
             final MongoCollection<Document> sessions = database.getCollection(MongoDbSession.COLLECTION_NAME);
    @@ -84,4 +89,12 @@ public SimpleSession daoToSimpleSession(MongoDbSession sessionDAO) {
             return session;
         }
     
    +    @Override
    +    public <T extends Persisted> int destroy(T model) {
    +        int affectedDocs = super.destroy(model);
    +        if (affectedDocs != 0 && model instanceof MongoDbSession session) {
    +            eventBus.post(new SessionDeletedEvent(session.getSessionId()));
    +        }
    +        return affectedDocs;
    +    }
     }
    
  • graylog2-server/src/main/java/org/graylog2/security/SessionDeletedEvent.java+22 0 added
    @@ -0,0 +1,22 @@
    +/*
    + * Copyright (C) 2020 Graylog, Inc.
    + *
    + * This program is free software: you can redistribute it and/or modify
    + * it under the terms of the Server Side Public License, version 1,
    + * as published by MongoDB, Inc.
    + *
    + * This program is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    + * Server Side Public License for more details.
    + *
    + * You should have received a copy of the Server Side Public License
    + * along with this program. If not, see
    + * <http://www.mongodb.com/licensing/server-side-public-license>.
    + */
    +package org.graylog2.security;
    +
    +import com.fasterxml.jackson.annotation.JsonProperty;
    +
    +public record SessionDeletedEvent(@JsonProperty("session_id") String sessionId) {
    +}
    
  • graylog2-server/src/main/java/org/graylog2/security/UserSessionTerminationService.java+8 7 modified
    @@ -39,6 +39,7 @@
     import javax.inject.Singleton;
     import java.io.Serializable;
     import java.util.EnumSet;
    +import java.util.List;
     import java.util.Optional;
     import java.util.Set;
     import java.util.stream.Collectors;
    @@ -140,15 +141,15 @@ public void runGlobalSessionTermination() {
             clusterConfigService.write(GlobalTerminationRevisionConfig.withCurrentRevision());
         }
     
    +    public List<Session> getActiveSessionsForUser(User user) {
    +        return getSessionIDsForUser(user).stream().map(this::getActiveSessionForID).flatMap(Optional::stream).toList();
    +    }
    +
         private void terminateSessionsForUser(User user) {
             try {
    -            final Set<String> sessionIds = getSessionIDsForUser(user);
    -
    -            for (final String sessionId : sessionIds) {
    -                getActiveSessionForID(sessionId).ifPresent(session -> {
    -                    LOG.info("Terminating session for user <{}/{}>", user.getName(), user.getId());
    -                    session.stop();
    -                });
    +            for (final Session session : getActiveSessionsForUser(user)) {
    +                LOG.info("Terminating session for user <{}/{}>", user.getName(), user.getId());
    +                session.stop();
                 }
             } catch (Exception e) {
                 LOG.error("Couldn't terminate session for user <{}/{}>", user.getName(), user.getId(), e);
    
  • graylog2-server/src/test/java/org/graylog2/rest/resources/users/UsersResourceTest.java+13 4 modified
    @@ -17,6 +17,7 @@
     package org.graylog2.rest.resources.users;
     
     import com.google.common.collect.ImmutableSet;
    +import org.apache.shiro.mgt.DefaultSecurityManager;
     import org.apache.shiro.subject.Subject;
     import org.bson.types.ObjectId;
     import org.graylog.testing.mongodb.MongoDBInstance;
    @@ -29,6 +30,7 @@
     import org.graylog2.security.AccessTokenService;
     import org.graylog2.security.MongoDBSessionService;
     import org.graylog2.security.PasswordAlgorithmFactory;
    +import org.graylog2.security.UserSessionTerminationService;
     import org.graylog2.security.hashing.SHA1HashPasswordAlgorithm;
     import org.graylog2.shared.security.Permissions;
     import org.graylog2.shared.security.RestPermissions;
    @@ -88,15 +90,20 @@ public class UsersResourceTest {
         private Subject subject;
         @Mock
         private UserManagementService userManagementService;
    +    @Mock
    +    private UserSessionTerminationService sessionTerminationService;
    +    @Mock
    +    private DefaultSecurityManager securityManager;
     
         UserImplFactory userImplFactory;
     
         @Before
         public void setUp() throws Exception {
             userImplFactory = new UserImplFactory(new Configuration(),
    -                                              new Permissions(ImmutableSet.of(new RestPermissions())));
    +                new Permissions(ImmutableSet.of(new RestPermissions())));
             usersResource = new TestUsersResource(userManagementService, paginatedUserService, accessTokenService,
    -                                              roleService, sessionService, new HttpConfiguration(), subject);
    +                roleService, sessionService, new HttpConfiguration(), subject,
    +                sessionTerminationService, securityManager);
         }
     
         /**
    @@ -137,8 +144,10 @@ public static class TestUsersResource extends UsersResource {
             public TestUsersResource(UserManagementService userManagementService, PaginatedUserService paginatedUserService,
                                      AccessTokenService accessTokenService, RoleService roleService,
                                      MongoDBSessionService sessionService, HttpConfiguration configuration,
    -                                 Subject subject) {
    -            super(userManagementService, paginatedUserService, accessTokenService, roleService, sessionService);
    +                                 Subject subject, UserSessionTerminationService sessionTerminationService,
    +                                 DefaultSecurityManager securityManager) {
    +            super(userManagementService, paginatedUserService, accessTokenService, roleService, sessionService,
    +                    sessionTerminationService, securityManager);
                 this.subject = subject;
                 super.configuration = configuration;
             }
    
  • graylog2-web-interface/src/components/users/TimeoutInput.tsx+12 1 modified
    @@ -18,8 +18,9 @@ import * as React from 'react';
     import { useState, useEffect } from 'react';
     import PropTypes from 'prop-types';
     
    -import { Row, Col, HelpBlock, Input } from 'components/bootstrap';
    +import { Row, Col, HelpBlock, Input, Alert } from 'components/bootstrap';
     import TimeoutUnitSelect from 'components/users/TimeoutUnitSelect';
    +import { Icon } from 'components/common';
     
     import { MS_DAY, MS_HOUR, MS_MINUTE, MS_SECOND } from './timeoutConstants';
     
    @@ -84,6 +85,16 @@ const TimeoutInput = ({ value: propsValue, onChange }: Props) => {
                labelClassName="col-sm-3"
                wrapperClassName="col-sm-9"
                label="Sessions Timeout">
    +      <Row className="no-bm">
    +        <Col xs={12}>
    +          <Alert bsStyle="info">
    +            <Icon name="info-circle" />{' '}<b>Changing the session timeout</b><br />
    +            Changing the timeout setting for sessions will log the user out of Graylog and will invalidate all their
    +            current sessions. If you are changing the setting for your own user, you will be logged out at the moment
    +            of saving the setting. In that case, make sure to save any pending changes before changing the timeout.
    +          </Alert>
    +        </Col>
    +      </Row>
           <>
             <Input type="checkbox"
                    id="session-timeout-never"
    
ff90f3e2aa4a

Merge pull request from GHSA-3fqm-frhg-7c85

https://github.com/Graylog2/graylog2-serverBernd AhlersJul 5, 2023via ghsa
8 files changed · +111 31
  • changelog/unreleased/ghsa-3fqm-frhg-7c85.toml+2 0 added
    @@ -0,0 +1,2 @@
    +type = "security"
    +message = "Fix stale session cache after logout. [GHSA-3fqm-frhg-7c85](https://github.com/Graylog2/graylog2-server/security/advisories/GHSA-3fqm-frhg-7c85)"
    
  • graylog2-server/src/main/java/org/graylog2/rest/resources/users/UsersResource.java+26 17 modified
    @@ -28,14 +28,12 @@
     import io.swagger.annotations.ApiResponse;
     import io.swagger.annotations.ApiResponses;
     import org.apache.commons.lang.StringUtils;
    -import org.apache.shiro.SecurityUtils;
     import org.apache.shiro.authz.annotation.RequiresAuthentication;
     import org.apache.shiro.authz.annotation.RequiresPermissions;
     import org.apache.shiro.authz.permission.WildcardPermission;
     import org.apache.shiro.mgt.DefaultSecurityManager;
     import org.apache.shiro.session.Session;
    -import org.apache.shiro.session.mgt.DefaultSessionManager;
    -import org.apache.shiro.session.mgt.eis.SessionDAO;
    +import org.apache.shiro.subject.Subject;
     import org.graylog.security.UserContext;
     import org.graylog.security.permissions.GRNPermission;
     import org.graylog2.audit.AuditEventTypes;
    @@ -61,6 +59,7 @@
     import org.graylog2.security.AccessTokenService;
     import org.graylog2.security.MongoDBSessionService;
     import org.graylog2.security.MongoDbSession;
    +import org.graylog2.security.UserSessionTerminationService;
     import org.graylog2.shared.rest.resources.RestResource;
     import org.graylog2.shared.security.RestPermissions;
     import org.graylog2.shared.users.ChangeUserRequest;
    @@ -137,6 +136,8 @@ public class UsersResource extends RestResource {
         private final RoleService roleService;
         private final MongoDBSessionService sessionService;
         private final SearchQueryParser searchQueryParser;
    +    private final UserSessionTerminationService sessionTerminationService;
    +    private final DefaultSecurityManager securityManager;
     
         protected static final ImmutableMap<String, SearchQueryField> SEARCH_FIELD_MAPPING = ImmutableMap.<String, SearchQueryField>builder()
                 .put(UserOverviewDTO.FIELD_ID, SearchQueryField.create("_id", SearchQueryField.Type.OBJECT_ID))
    @@ -150,12 +151,15 @@ public UsersResource(UserManagementService userManagementService,
                              PaginatedUserService paginatedUserService,
                              AccessTokenService accessTokenService,
                              RoleService roleService,
    -                         MongoDBSessionService sessionService) {
    +                         MongoDBSessionService sessionService,
    +                         UserSessionTerminationService sessionTerminationService, DefaultSecurityManager securityManager) {
             this.userManagementService = userManagementService;
             this.accessTokenService = accessTokenService;
             this.roleService = roleService;
             this.sessionService = sessionService;
             this.paginatedUserService = paginatedUserService;
    +        this.sessionTerminationService = sessionTerminationService;
    +        this.securityManager = securityManager;
             this.searchQueryParser = new SearchQueryParser(UserOverviewDTO.FIELD_FULL_NAME, SEARCH_FIELD_MAPPING);
         }
     
    @@ -439,8 +443,8 @@ public void changeUser(@ApiParam(name = "userId", value = "The ID of the user to
             if (isPermitted("*")) {
                 final Long sessionTimeoutMs = cr.sessionTimeoutMs();
                 if (Objects.nonNull(sessionTimeoutMs) && sessionTimeoutMs != 0 && (user.getSessionTimeoutMs() != sessionTimeoutMs)) {
    -                    updateExistingSession(user, sessionTimeoutMs);
    -                    user.setSessionTimeoutMs(sessionTimeoutMs);
    +                user.setSessionTimeoutMs(sessionTimeoutMs);
    +                terminateSessions(user);
                 }
             }
     
    @@ -451,17 +455,22 @@ public void changeUser(@ApiParam(name = "userId", value = "The ID of the user to
             userManagementService.update(user, cr);
         }
     
    -    private void updateExistingSession(User user, long newSessionTimeOut) {
    -        AllUserSessions allUserSessions = AllUserSessions.create(sessionService);
    -        allUserSessions.forUser(user).ifPresent(userSession -> {
    -            userSession.setTimeout(newSessionTimeOut);
    -            Session session = sessionService.daoToSimpleSession(userSession);
    -
    -            DefaultSecurityManager securityManager = (DefaultSecurityManager) SecurityUtils.getSecurityManager();
    -            DefaultSessionManager sessionManager = (DefaultSessionManager) securityManager.getSessionManager();
    -            SessionDAO sessionDAO = sessionManager.getSessionDAO();
    -            sessionDAO.update(session);
    -        });
    +    private void terminateSessions(User user) {
    +        final List<Session> allSessions = sessionTerminationService.getActiveSessionsForUser(user);
    +
    +        final Subject subject = getSubject();
    +        final Session currentSession = subject.getSession(false);
    +        final User currentUser = getCurrentUser();
    +
    +        if (currentSession != null && currentUser != null && user.getId().equals(currentUser.getId())) {
    +            // Stop all sessions but handle the current session differently by issuing a proper logout
    +            allSessions.stream()
    +                    .filter(session -> !session.getId().equals(currentSession.getId()))
    +                    .forEach(Session::stop);
    +            securityManager.logout(subject);
    +        } else {
    +            allSessions.forEach(Session::stop);
    +        }
         }
     
         private boolean rolesContainAdmin(List<String> roles) {
    
  • graylog2-server/src/main/java/org/graylog2/security/MongoDbSessionDAO.java+14 1 modified
    @@ -23,6 +23,8 @@
     import com.github.rholder.retry.WaitStrategies;
     import com.google.common.collect.Lists;
     import com.google.common.collect.Maps;
    +import com.google.common.eventbus.EventBus;
    +import com.google.common.eventbus.Subscribe;
     import com.mongodb.DuplicateKeyException;
     import org.apache.shiro.session.Session;
     import org.apache.shiro.session.mgt.SimpleSession;
    @@ -44,8 +46,19 @@ public class MongoDbSessionDAO extends CachingSessionDAO {
         private final MongoDBSessionService mongoDBSessionService;
     
         @Inject
    -    public MongoDbSessionDAO(MongoDBSessionService mongoDBSessionService) {
    +    public MongoDbSessionDAO(MongoDBSessionService mongoDBSessionService, EventBus eventBus) {
             this.mongoDBSessionService = mongoDBSessionService;
    +        eventBus.register(this);
    +    }
    +
    +    @SuppressWarnings("unused")
    +    @Subscribe
    +    public void sessionDeleted(SessionDeletedEvent event) {
    +        final Session cachedSession = getCachedSession(event.sessionId());
    +        if (cachedSession != null) {
    +            LOG.debug("Removing deleted session from cache.");
    +            uncache(cachedSession);
    +        }
         }
     
         @Override
    
  • graylog2-server/src/main/java/org/graylog2/security/MongoDBSessionServiceImpl.java+14 1 modified
    @@ -27,6 +27,8 @@
     import org.bson.types.ObjectId;
     import org.graylog2.database.MongoConnection;
     import org.graylog2.database.PersistedServiceImpl;
    +import org.graylog2.events.ClusterEventBus;
    +import org.graylog2.plugin.database.Persisted;
     
     import javax.annotation.Nullable;
     import javax.inject.Inject;
    @@ -36,9 +38,12 @@
     
     @Singleton
     public class MongoDBSessionServiceImpl extends PersistedServiceImpl implements MongoDBSessionService {
    +    private final ClusterEventBus eventBus;
    +
         @Inject
    -    public MongoDBSessionServiceImpl(MongoConnection mongoConnection) {
    +    public MongoDBSessionServiceImpl(MongoConnection mongoConnection, ClusterEventBus clusterEventBus) {
             super(mongoConnection);
    +        this.eventBus = clusterEventBus;
     
             final MongoDatabase database = mongoConnection.getMongoDatabase();
             final MongoCollection<Document> sessions = database.getCollection(MongoDbSession.COLLECTION_NAME);
    @@ -84,4 +89,12 @@ public SimpleSession daoToSimpleSession(MongoDbSession sessionDAO) {
             return session;
         }
     
    +    @Override
    +    public <T extends Persisted> int destroy(T model) {
    +        int affectedDocs = super.destroy(model);
    +        if (affectedDocs != 0 && model instanceof MongoDbSession session) {
    +            eventBus.post(new SessionDeletedEvent(session.getSessionId()));
    +        }
    +        return affectedDocs;
    +    }
     }
    
  • graylog2-server/src/main/java/org/graylog2/security/SessionDeletedEvent.java+22 0 added
    @@ -0,0 +1,22 @@
    +/*
    + * Copyright (C) 2020 Graylog, Inc.
    + *
    + * This program is free software: you can redistribute it and/or modify
    + * it under the terms of the Server Side Public License, version 1,
    + * as published by MongoDB, Inc.
    + *
    + * This program is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    + * Server Side Public License for more details.
    + *
    + * You should have received a copy of the Server Side Public License
    + * along with this program. If not, see
    + * <http://www.mongodb.com/licensing/server-side-public-license>.
    + */
    +package org.graylog2.security;
    +
    +import com.fasterxml.jackson.annotation.JsonProperty;
    +
    +public record SessionDeletedEvent(@JsonProperty("session_id") String sessionId) {
    +}
    
  • graylog2-server/src/main/java/org/graylog2/security/UserSessionTerminationService.java+8 7 modified
    @@ -39,6 +39,7 @@
     import javax.inject.Singleton;
     import java.io.Serializable;
     import java.util.EnumSet;
    +import java.util.List;
     import java.util.Optional;
     import java.util.Set;
     import java.util.stream.Collectors;
    @@ -140,15 +141,15 @@ public void runGlobalSessionTermination() {
             clusterConfigService.write(GlobalTerminationRevisionConfig.withCurrentRevision());
         }
     
    +    public List<Session> getActiveSessionsForUser(User user) {
    +        return getSessionIDsForUser(user).stream().map(this::getActiveSessionForID).flatMap(Optional::stream).toList();
    +    }
    +
         private void terminateSessionsForUser(User user) {
             try {
    -            final Set<String> sessionIds = getSessionIDsForUser(user);
    -
    -            for (final String sessionId : sessionIds) {
    -                getActiveSessionForID(sessionId).ifPresent(session -> {
    -                    LOG.info("Terminating session for user <{}/{}>", user.getName(), user.getId());
    -                    session.stop();
    -                });
    +            for (final Session session : getActiveSessionsForUser(user)) {
    +                LOG.info("Terminating session for user <{}/{}>", user.getName(), user.getId());
    +                session.stop();
                 }
             } catch (Exception e) {
                 LOG.error("Couldn't terminate session for user <{}/{}>", user.getName(), user.getId(), e);
    
  • graylog2-server/src/test/java/org/graylog2/rest/resources/users/UsersResourceTest.java+13 4 modified
    @@ -17,6 +17,7 @@
     package org.graylog2.rest.resources.users;
     
     import com.google.common.collect.ImmutableSet;
    +import org.apache.shiro.mgt.DefaultSecurityManager;
     import org.apache.shiro.subject.Subject;
     import org.bson.types.ObjectId;
     import org.graylog.testing.mongodb.MongoDBInstance;
    @@ -30,6 +31,7 @@
     import org.graylog2.security.AccessTokenService;
     import org.graylog2.security.MongoDBSessionService;
     import org.graylog2.security.PasswordAlgorithmFactory;
    +import org.graylog2.security.UserSessionTerminationService;
     import org.graylog2.security.hashing.SHA1HashPasswordAlgorithm;
     import org.graylog2.shared.security.Permissions;
     import org.graylog2.shared.security.RestPermissions;
    @@ -90,15 +92,20 @@ public class UsersResourceTest {
         private Subject subject;
         @Mock
         private UserManagementService userManagementService;
    +    @Mock
    +    private UserSessionTerminationService sessionTerminationService;
    +    @Mock
    +    private DefaultSecurityManager securityManager;
     
         UserImplFactory userImplFactory;
     
         @Before
         public void setUp() throws Exception {
             userImplFactory = new UserImplFactory(new Configuration(),
    -                                              new Permissions(ImmutableSet.of(new RestPermissions())));
    +                new Permissions(ImmutableSet.of(new RestPermissions())));
             usersResource = new TestUsersResource(userManagementService, paginatedUserService, accessTokenService,
    -                                              roleService, sessionService, new HttpConfiguration(), subject);
    +                roleService, sessionService, new HttpConfiguration(), subject,
    +                sessionTerminationService, securityManager);
         }
     
         /**
    @@ -139,8 +146,10 @@ public static class TestUsersResource extends UsersResource {
             public TestUsersResource(UserManagementService userManagementService, PaginatedUserService paginatedUserService,
                                      AccessTokenService accessTokenService, RoleService roleService,
                                      MongoDBSessionService sessionService, HttpConfiguration configuration,
    -                                 Subject subject) {
    -            super(userManagementService, paginatedUserService, accessTokenService, roleService, sessionService);
    +                                 Subject subject, UserSessionTerminationService sessionTerminationService,
    +                                 DefaultSecurityManager securityManager) {
    +            super(userManagementService, paginatedUserService, accessTokenService, roleService, sessionService,
    +                    sessionTerminationService, securityManager);
                 this.subject = subject;
                 super.configuration = configuration;
             }
    
  • graylog2-web-interface/src/components/users/TimeoutInput.tsx+12 1 modified
    @@ -18,8 +18,9 @@ import * as React from 'react';
     import { useState } from 'react';
     import PropTypes from 'prop-types';
     
    -import { Row, Col, HelpBlock, Input } from 'components/bootstrap';
    +import { Row, Col, HelpBlock, Input, Alert } from 'components/bootstrap';
     import TimeoutUnitSelect from 'components/users/TimeoutUnitSelect';
    +import { Icon } from 'components/common';
     
     import { MS_DAY, MS_HOUR, MS_MINUTE, MS_SECOND } from './timeoutConstants';
     
    @@ -82,6 +83,16 @@ const TimeoutInput = ({ value: propsValue, onChange }: Props) => {
                labelClassName="col-sm-3"
                wrapperClassName="col-sm-9"
                label="Sessions Timeout">
    +      <Row className="no-bm">
    +        <Col xs={12}>
    +          <Alert bsStyle="info">
    +            <Icon name="info-circle" />{' '}<b>Changing the session timeout</b><br />
    +            Changing the timeout setting for sessions will log the user out of Graylog and will invalidate all their
    +            current sessions. If you are changing the setting for your own user, you will be logged out at the moment
    +            of saving the setting. In that case, make sure to save any pending changes before changing the timeout.
    +          </Alert>
    +        </Col>
    +      </Row>
           <>
             <Input type="checkbox"
                    id="session-timeout-never"
    

Vulnerability mechanics

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

References

5

News mentions

0

No linked articles in our index yet.