Open Social - Less critical - Access bypass, Information Disclosure - SA-CONTRIB-2025-015
Description
A missing authorization vulnerability in Open Social allows unauthenticated forceful browsing to access user group event invitations.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A missing authorization vulnerability in Open Social allows unauthenticated forceful browsing to access user group event invitations.
Vulnerability
Overview
CVE-2025-31686 is a Missing Authorization vulnerability in the Drupal-based Open Social distribution, affecting versions from 0.0.0 before 12.3.11 and from 12.4.0 before 12.4.10 [3]. The issue allows Forceful Browsing, meaning an attacker can access resources without proper permission checks. The root cause lies in the social_event_max_enroll and group invitation views, where route subscribers and access checks were incorrectly scoped [1][2].
Attack
Vector
An attacker can exploit this by directly requesting URLs related to group invitations or event enrollment, bypassing intended authorization checks. No prior authentication is required for this forceful browsing attack, as the missing authorization is in the routing layer [1][2]. The fix adds a route match condition (uid_current) to ensure the current user is properly verified before viewing invitations [2].
Impact
Successful exploitation could allow an attacker to view, and potentially interact with, group invitations and event enrollment data of any user, leading to unauthorized disclosure of private group information and potential access to invitation-only events.
Mitigation
Open Social has patched this vulnerability in versions 12.3.11 and 12.4.10 [3]. Users should update to these versions to apply the corrected route and view configurations. No workarounds have been publicly documented.
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 |
|---|---|---|
goalgorilla/open_socialPackagist | < 12.3.11 | 12.3.11 |
goalgorilla/open_socialPackagist | >= 12.4.0, < 12.4.10 | 12.4.10 |
Affected products
2- Drupal/Open Socialv5Range: 0.0.0
Patches
26830b1788616Add routematch on the current user to check invites
7 files changed · +251 −2
modules/social_features/social_event/modules/social_event_max_enroll/social_event_max_enroll.services.yml+1 −1 modified@@ -10,7 +10,7 @@ services: - '@entity_type.manager' - '@config.factory' - '@social_event.enroll' - social_event_invite.route_subscriber: + social_event_max_enroll.route_subscriber: class: Drupal\social_event_max_enroll\Routing\RouteSubscriber tags: - { name: event_subscriber }
modules/social_features/social_group/modules/social_group_invite/config/optional/views.view.social_group_user_invitations.yml+41 −1 modified@@ -27,7 +27,8 @@ display: type: role options: role: - authenticated: authenticated + administrator: administrator + verified: verified cache: type: tag options: { } @@ -660,6 +661,45 @@ display: default_group_multiple: { } group_items: { } plugin_id: numeric + uid_current: + id: uid_current + table: users + field: uid_current + relationship: gc__user + group_type: group + admin_label: '' + entity_type: user + plugin_id: user_current + operator: '=' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + operator_limit_selection: false + operator_list: { } + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } sorts: { } header: { } footer: { }
modules/social_features/social_group/modules/social_group_invite/social_group_invite.install+63 −0 modified@@ -380,3 +380,66 @@ function social_group_invite_update_13001(): void { } } } + +/** + * Update user group invitations view configuration. + */ +function social_group_invite_update_13002() { + $config_factory = \Drupal::configFactory(); + $config = $config_factory->getEditable('views.view.social_group_user_invitations'); + + // Let's check if this configuration object does exist in storage before + // updating it. + if (!$config->isNew()) { + $config->set('display.default.display_options.filters.uid_current', [ + 'admin_label' => '', + 'entity_type' => 'user', + 'expose' => [ + 'description' => '', + 'identifier' => '', + 'label' => '', + 'multiple' => FALSE, + 'operator' => '', + 'operator_id' => '', + 'operator_limit_selection' => FALSE, + 'operator_list' => [], + 'remember' => FALSE, + 'remember_roles' => [ + 'authenticated' => 'authenticated', + ], + 'required' => FALSE, + 'use_operator' => FALSE, + ], + 'exposed' => FALSE, + 'field' => 'uid_current', + 'group' => 1, + 'group_info' => [ + 'default_group' => 'All', + 'default_group_multiple' => [], + 'description' => '', + 'group_items' => [], + 'identifier' => '', + 'label' => '', + 'multiple' => FALSE, + 'optional' => TRUE, + 'remember' => FALSE, + 'widget' => 'select', + ], + 'group_type' => 'group', + 'id' => 'uid_current', + 'is_grouped' => FALSE, + 'operator' => '=', + 'plugin_id' => 'user_current', + 'relationship' => 'gc__user', + 'table' => 'users', + 'value' => '1', + ]); + + $config->set('display.default.display_options.access.options.role', [ + 'administrator' => 'administrator', + 'verified' => 'verified', + ]); + + $config->save(); + } +}
modules/social_features/social_group/modules/social_group_invite/social_group_invite.services.yml+6 −0 modified@@ -1,4 +1,10 @@ services: + social_group_invite.access: + class: Drupal\social_group_invite\Access\SocialGroupInvitesAccess + arguments: [ '@social_group_invite.access_helper' ] + social_group_invite.access_helper: + class: Drupal\social_group_invite\SocialGroupInviteAccessHelper + arguments: [ '@current_route_match', '@config.factory', '@current_user' ] social_group_invite.event_subscriber: class: Drupal\social_group_invite\EventSubscriber\EventSubscribers arguments: ['@current_route_match', '@current_user']
modules/social_features/social_group/modules/social_group_invite/src/Access/SocialGroupInvitesAccess.php+43 −0 added@@ -0,0 +1,43 @@ +<?php + +namespace Drupal\social_group_invite\Access; + +use Drupal\social_group_invite\SocialGroupInviteAccessHelper; + +/** + * Class SocialGroupInvitesAccess. + * + * @package Drupal\social_group_invite\Access + */ +class SocialGroupInvitesAccess { + + /** + * The group invite access helper. + * + * @var \Drupal\social_group_invite\SocialGroupInviteAccessHelper + */ + protected $accessHelper; + + /** + * SocialGroupInvitesAccess constructor. + * + * @param \Drupal\social_group_invite\SocialGroupInviteAccessHelper $accessHelper + * The group invite access helper. + */ + public function __construct(SocialGroupInviteAccessHelper $accessHelper) { + $this->accessHelper = $accessHelper; + } + + /** + * Custom access check for the user invite overview. + * + * @return \Drupal\Core\Access\AccessResult + * Returns the result of the access helper. + * + * @see \Drupal\social_group_invite\SocialGroupInviteAccessHelper::userInviteAccess() + */ + public function userInviteAccess() { + return $this->accessHelper->userInviteAccess(); + } + +}
modules/social_features/social_group/modules/social_group_invite/src/Routing/RouteSubscriber.php+6 −0 modified@@ -26,6 +26,12 @@ protected function alterRoutes(RouteCollection $collection) { $route->setDefaults($defaults); $route->setRequirements($requirements); } + + if ($route = $collection->get('view.social_group_user_invitations.page_1')) { + $requirements = $route->getRequirements(); + $requirements['_custom_access'] = 'social_group_invite.access::userInviteAccess'; + $route->setRequirements($requirements); + } } }
modules/social_features/social_group/modules/social_group_invite/src/SocialGroupInviteAccessHelper.php+91 −0 added@@ -0,0 +1,91 @@ +<?php + +namespace Drupal\social_group_invite; + +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Session\AccountProxyInterface; +use Drupal\user\Entity\User; +use Drupal\user\UserInterface; + +/** + * Class SocialGroupInviteAccessHelper. + * + * @package Drupal\social_group_invite\Access + */ +class SocialGroupInviteAccessHelper { + + /** + * The route match. + * + * @var \Drupal\Core\Routing\RouteMatchInterface + */ + protected $routeMatch; + + /** + * Configuration factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * Entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountProxyInterface + */ + protected $currentUser; + + /** + * SocialGroupInvitesAccess constructor. + * + * @param \Drupal\Core\Routing\RouteMatchInterface $routeMatch + * The route match. + * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory + * Configuration factory. + * @param \Drupal\Core\Session\AccountProxyInterface $currentUser + * The current user. + */ + public function __construct(RouteMatchInterface $routeMatch, ConfigFactoryInterface $configFactory, AccountProxyInterface $currentUser) { + $this->routeMatch = $routeMatch; + $this->configFactory = $configFactory; + $this->currentUser = $currentUser; + } + + /** + * Custom access check for the user invite overview. + * + * @return \Drupal\Core\Access\AccessResult + * Returns the access result. + */ + public function userInviteAccess() { + // @todo: At the moment, we allow user to access only own group invites. + // Additional permissions will be added later. + // $config = $this->configFactory->get('social_group_invite.settings'); + // $enabled_global = $config->get('invite_enroll'); + // + // // If it's globally disabled, we don't want to show the block. + // if (!$enabled_global) { + // return AccessResult::forbidden(); + // } + + // Get the user. + $account = $this->routeMatch->getRawParameter('user'); + if (!empty($account)) { + $account = User::load($account); + if ($account instanceof UserInterface) { + return AccessResult::allowedIf($account->id() === $this->currentUser->id()); + } + } + + return AccessResult::neutral(); + } +}
6fa5181901d4Add routematch on the current user to check invites
7 files changed · +251 −2
modules/social_features/social_event/modules/social_event_max_enroll/social_event_max_enroll.services.yml+1 −1 modified@@ -10,7 +10,7 @@ services: - '@entity_type.manager' - '@config.factory' - '@social_event.enroll' - social_event_invite.route_subscriber: + social_event_max_enroll.route_subscriber: class: Drupal\social_event_max_enroll\Routing\RouteSubscriber tags: - { name: event_subscriber }
modules/social_features/social_group/modules/social_group_invite/config/optional/views.view.social_group_user_invitations.yml+41 −1 modified@@ -27,7 +27,8 @@ display: type: role options: role: - authenticated: authenticated + administrator: administrator + verified: verified cache: type: tag options: { } @@ -660,6 +661,45 @@ display: default_group_multiple: { } group_items: { } plugin_id: numeric + uid_current: + id: uid_current + table: users + field: uid_current + relationship: gc__user + group_type: group + admin_label: '' + entity_type: user + plugin_id: user_current + operator: '=' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + operator_limit_selection: false + operator_list: { } + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } sorts: { } header: { } footer: { }
modules/social_features/social_group/modules/social_group_invite/social_group_invite.install+63 −0 modified@@ -380,3 +380,66 @@ function social_group_invite_update_13001(): void { } } } + +/** + * Update user group invitations view configuration. + */ +function social_group_invite_update_13002() { + $config_factory = \Drupal::configFactory(); + $config = $config_factory->getEditable('views.view.social_group_user_invitations'); + + // Let's check if this configuration object does exist in storage before + // updating it. + if (!$config->isNew()) { + $config->set('display.default.display_options.filters.uid_current', [ + 'admin_label' => '', + 'entity_type' => 'user', + 'expose' => [ + 'description' => '', + 'identifier' => '', + 'label' => '', + 'multiple' => FALSE, + 'operator' => '', + 'operator_id' => '', + 'operator_limit_selection' => FALSE, + 'operator_list' => [], + 'remember' => FALSE, + 'remember_roles' => [ + 'authenticated' => 'authenticated', + ], + 'required' => FALSE, + 'use_operator' => FALSE, + ], + 'exposed' => FALSE, + 'field' => 'uid_current', + 'group' => 1, + 'group_info' => [ + 'default_group' => 'All', + 'default_group_multiple' => [], + 'description' => '', + 'group_items' => [], + 'identifier' => '', + 'label' => '', + 'multiple' => FALSE, + 'optional' => TRUE, + 'remember' => FALSE, + 'widget' => 'select', + ], + 'group_type' => 'group', + 'id' => 'uid_current', + 'is_grouped' => FALSE, + 'operator' => '=', + 'plugin_id' => 'user_current', + 'relationship' => 'gc__user', + 'table' => 'users', + 'value' => '1', + ]); + + $config->set('display.default.display_options.access.options.role', [ + 'administrator' => 'administrator', + 'verified' => 'verified', + ]); + + $config->save(); + } +}
modules/social_features/social_group/modules/social_group_invite/social_group_invite.services.yml+6 −0 modified@@ -1,4 +1,10 @@ services: + social_group_invite.access: + class: Drupal\social_group_invite\Access\SocialGroupInvitesAccess + arguments: [ '@social_group_invite.access_helper' ] + social_group_invite.access_helper: + class: Drupal\social_group_invite\SocialGroupInviteAccessHelper + arguments: [ '@current_route_match', '@config.factory', '@current_user' ] social_group_invite.event_subscriber: class: Drupal\social_group_invite\EventSubscriber\EventSubscribers arguments: ['@current_route_match', '@current_user']
modules/social_features/social_group/modules/social_group_invite/src/Access/SocialGroupInvitesAccess.php+43 −0 added@@ -0,0 +1,43 @@ +<?php + +namespace Drupal\social_group_invite\Access; + +use Drupal\social_group_invite\SocialGroupInviteAccessHelper; + +/** + * Class SocialGroupInvitesAccess. + * + * @package Drupal\social_group_invite\Access + */ +class SocialGroupInvitesAccess { + + /** + * The group invite access helper. + * + * @var \Drupal\social_group_invite\SocialGroupInviteAccessHelper + */ + protected $accessHelper; + + /** + * SocialGroupInvitesAccess constructor. + * + * @param \Drupal\social_group_invite\SocialGroupInviteAccessHelper $accessHelper + * The group invite access helper. + */ + public function __construct(SocialGroupInviteAccessHelper $accessHelper) { + $this->accessHelper = $accessHelper; + } + + /** + * Custom access check for the user invite overview. + * + * @return \Drupal\Core\Access\AccessResult + * Returns the result of the access helper. + * + * @see \Drupal\social_group_invite\SocialGroupInviteAccessHelper::userInviteAccess() + */ + public function userInviteAccess() { + return $this->accessHelper->userInviteAccess(); + } + +}
modules/social_features/social_group/modules/social_group_invite/src/Routing/RouteSubscriber.php+6 −0 modified@@ -26,6 +26,12 @@ protected function alterRoutes(RouteCollection $collection) { $route->setDefaults($defaults); $route->setRequirements($requirements); } + + if ($route = $collection->get('view.social_group_user_invitations.page_1')) { + $requirements = $route->getRequirements(); + $requirements['_custom_access'] = 'social_group_invite.access::userInviteAccess'; + $route->setRequirements($requirements); + } } }
modules/social_features/social_group/modules/social_group_invite/src/SocialGroupInviteAccessHelper.php+91 −0 added@@ -0,0 +1,91 @@ +<?php + +namespace Drupal\social_group_invite; + +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Session\AccountProxyInterface; +use Drupal\user\Entity\User; +use Drupal\user\UserInterface; + +/** + * Class SocialGroupInviteAccessHelper. + * + * @package Drupal\social_group_invite\Access + */ +class SocialGroupInviteAccessHelper { + + /** + * The route match. + * + * @var \Drupal\Core\Routing\RouteMatchInterface + */ + protected $routeMatch; + + /** + * Configuration factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * Entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountProxyInterface + */ + protected $currentUser; + + /** + * SocialGroupInvitesAccess constructor. + * + * @param \Drupal\Core\Routing\RouteMatchInterface $routeMatch + * The route match. + * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory + * Configuration factory. + * @param \Drupal\Core\Session\AccountProxyInterface $currentUser + * The current user. + */ + public function __construct(RouteMatchInterface $routeMatch, ConfigFactoryInterface $configFactory, AccountProxyInterface $currentUser) { + $this->routeMatch = $routeMatch; + $this->configFactory = $configFactory; + $this->currentUser = $currentUser; + } + + /** + * Custom access check for the user invite overview. + * + * @return \Drupal\Core\Access\AccessResult + * Returns the access result. + */ + public function userInviteAccess() { + // @todo: At the moment, we allow user to access only own group invites. + // Additional permissions will be added later. + // $config = $this->configFactory->get('social_group_invite.settings'); + // $enabled_global = $config->get('invite_enroll'); + // + // // If it's globally disabled, we don't want to show the block. + // if (!$enabled_global) { + // return AccessResult::forbidden(); + // } + + // Get the user. + $account = $this->routeMatch->getRawParameter('user'); + if (!empty($account)) { + $account = User::load($account); + if ($account instanceof UserInterface) { + return AccessResult::allowedIf($account->id() === $this->currentUser->id()); + } + } + + return AccessResult::neutral(); + } +}
Vulnerability mechanics
Generated 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-m9w8-wxvp-c9gvghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-31686ghsaADVISORY
- github.com/goalgorilla/open_social/commit/6830b1788616fc24fb3913ce88c5d997a363a5deghsaWEB
- github.com/goalgorilla/open_social/commit/6fa5181901d4be3a64793f29c6ce0c9bd535a42fghsaWEB
- www.drupal.org/sa-contrib-2025-015ghsaWEB
News mentions
0No linked articles in our index yet.