User session is still usable after logout in graylog2-server
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.
| Package | Affected versions | Patched versions |
|---|---|---|
org.graylog2:graylog2-serverMaven | >= 1.0, < 5.0.9 | 5.0.9 |
org.graylog2:graylog2-serverMaven | >= 5.1.0, < 5.1.3 | 5.1.3 |
Affected products
1- Range: >= 5.1.0, < 5.1.3
Patches
2bb88f3d0b2b0Merge pull request from GHSA-3fqm-frhg-7c85
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"
ff90f3e2aa4aMerge pull request from GHSA-3fqm-frhg-7c85
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- github.com/advisories/GHSA-3fqm-frhg-7c85ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-41041ghsaADVISORY
- github.com/Graylog2/graylog2-server/commit/bb88f3d0b2b0351669ab32c60b595ab7242a3fe3ghsax_refsource_MISCWEB
- github.com/Graylog2/graylog2-server/commit/ff90f3e2aa4ae2e0bb613d3236e52c40aa154b20ghsaWEB
- github.com/Graylog2/graylog2-server/security/advisories/GHSA-3fqm-frhg-7c85ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.