CVE-2026-54357
Description
MISP improper authorization allowed org admins to access site admin user settings; fixed by hardening ACL logic.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
MISP improper authorization allowed org admins to access site admin user settings; fixed by hardening ACL logic.
Vulnerability
An improper authorization vulnerability exists in MISP's user settings and login profile management endpoints. An authenticated organization administrator could access or modify user settings belonging to site-administrator accounts within the same organization. The affected access-control checks scoped administrative actions by organization membership but did not exclude higher-privileged site-administrator users. This affects MISP versions prior to commit ed3d9b8 [1].
Exploitation
An attacker with a valid organization-administrator account and network access to the MISP instance can navigate to the user settings or login profile pages for a site administrator within their own organization. No additional user interaction is required beyond the normal administrative interface. The attacker can view and potentially alter the site administrator's settings and login profile information [1].
Impact
Successful exploitation allows the organization administrator to view or modify sensitive login profile data and settings of a site administrator. This crosses the intended privilege boundary between organization and site-wide administration. Depending on the altered settings, the attacker could compromise the site administrator's account, potentially leading to unauthorized access or control over the entire MISP instance [1].
Mitigation
The issue is patched in commit ed3d9b8 [1]. The fix hardens the ACL logic by excluding site-administrator role IDs from the set of users manageable by organization administrators, and ensures that operations fail closed when a target user is not administrable. MISP users should update to a version containing this commit as soon as possible. No workaround has been provided.
AI Insight generated on Jun 12, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
1ed3d9b862deafix: [security] fixes to the user setting change ACL
4 files changed · +95 −13
app/Controller/UserLoginProfilesController.php+34 −4 modified@@ -50,9 +50,14 @@ public function index($user_id = null) // normal user $conditions = ['user_id' => $this->Auth->user('id')]; // org admin can see people from their own org - if (!$this->_isSiteAdmin() && $this->_isAdmin()) { + if (!$this->_isSiteAdmin() && $this->_isAdmin()) { $conditions = ['User.org_id' => $this->Auth->user('org_id'), - 'user_id' => $user_id]; + 'user_id' => $user_id]; + // An org admin must not see a site admin's login profiles. + $siteAdminRoleIds = $this->UserLoginProfile->User->getSiteAdminRoleIds(); + if (!empty($siteAdminRoleIds)) { + $conditions['NOT'] = ['User.role_id' => $siteAdminRoleIds]; + } $delete_buttons = true; } // full admin can see all users @@ -99,14 +104,39 @@ private function __deleteFetchConditions($id) // no additional filter for siteadmins } else if ($this->_isAdmin()) { - $conditions['User.org_id'] = $this->Auth->user('org_id'); // org admin - } + // Org admin: restrict to non-site-admin users of their own org. + // We filter on UserLoginProfile.user_id (an own-model column) rather + // than User.org_id, because the delete path runs finds with + // recursive => -1, where a condition on the associated User table is + // not joined and would raise an SQL error. + $conditions['UserLoginProfile.user_id'] = $this->__administrableUserIds(); + } else { $conditions['UserLoginProfile.user_id'] = $this->Auth->user('id'); // normal user } return $conditions; } + /** + * User IDs the current org admin is allowed to administer: members of their + * own organisation, excluding site admins. Returns a value that matches no + * rows when the set is empty, so an empty result fails closed. + * @return array + */ + private function __administrableUserIds() + { + $conditions = ['User.org_id' => $this->Auth->user('org_id')]; + $siteAdminRoleIds = $this->UserLoginProfile->User->getSiteAdminRoleIds(); + if (!empty($siteAdminRoleIds)) { + $conditions['NOT'] = ['User.role_id' => $siteAdminRoleIds]; + } + $userIds = $this->UserLoginProfile->User->find('column', [ + 'conditions' => $conditions, + 'fields' => ['User.id'], + ]); + return empty($userIds) ? [-1] : $userIds; + } + public function admin_delete($id) { $profile = $this->UserLoginProfile->find('first', [
app/Controller/UserSettingsController.php+8 −3 modified@@ -84,12 +84,17 @@ public function index() } if (!$this->_isSiteAdmin()) { if ($this->_isAdmin()) { + $orgUserConditions = array('User.org_id' => $this->Auth->user('org_id')); + // An org admin must not see a site admin's settings. + $siteAdminRoleIds = $this->UserSetting->User->getSiteAdminRoleIds(); + if (!empty($siteAdminRoleIds)) { + $orgUserConditions['NOT'] = array('User.role_id' => $siteAdminRoleIds); + } $conditions['AND'][] = array( 'UserSetting.user_id' => $this->UserSetting->User->find( 'list', array( - 'conditions' => array( - 'User.org_id' => $this->Auth->user('org_id') - ), + 'recursive' => -1, + 'conditions' => $orgUserConditions, 'fields' => array( 'User.id', 'User.id' )
app/Model/User.php+32 −0 modified@@ -1117,6 +1117,38 @@ public function getSiteAdmins($excludeUserId = false) )); } + /** + * Role IDs that grant site admin privileges. + * @return array + */ + public function getSiteAdminRoleIds() + { + return $this->Role->find('column', array( + 'conditions' => array('Role.perm_site_admin' => 1), + 'fields' => array('Role.id') + )); + } + + /** + * Whether the given user ID belongs to a site admin. Unlike getSiteAdmins(), + * this deliberately ignores the disabled flag: a disabled site admin must + * still be protected from lower-privileged (org) admins. + * @param int $userId + * @return bool + */ + public function isUserSiteAdmin($userId) + { + if (empty($userId)) { + return false; + } + $target = $this->find('first', array( + 'recursive' => -1, + 'conditions' => array('User.id' => $userId), + 'contain' => array('Role' => array('fields' => array('perm_site_admin'))) + )); + return !empty($target['Role']['perm_site_admin']); + } + public function verifyPassword($user_id, $password) { $currentUser = $this->find('first', array(
app/Model/UserSetting.php+21 −6 modified@@ -363,7 +363,12 @@ public function checkAccess($user, array $setting, $user_id = false) if ($user['Role']['perm_site_admin']) { return true; } else if ($user['Role']['perm_admin']) { - if ($user['org_id'] === $setting['User']['org_id']) { + // An org admin may not manage a site admin's setting, even when the + // site admin belongs to the same organisation. + if ( + $user['org_id'] === $setting['User']['org_id'] && + !$this->User->isUserSiteAdmin($setting['UserSetting']['user_id']) + ) { return true; } } else { @@ -570,17 +575,27 @@ public function setSetting(array $user, array $data) throw new MethodNotAllowedException(__('User self-management is disabled on this instance.')); } if (!empty($data['UserSetting']['user_id']) && is_numeric($data['UserSetting']['user_id'])) { + $targetUserId = $data['UserSetting']['user_id']; $user_to_edit = $this->User->find('first', array( 'recursive' => -1, - 'conditions' => array('User.id' => $data['UserSetting']['user_id']), + 'conditions' => array('User.id' => $targetUserId), 'fields' => array('User.org_id') )); - if ( + // A user may always edit their own settings. A site admin may edit + // anyone's. An org admin may edit non-site-admin users within their + // own org. Anything else is an explicit authorisation failure rather + // than a silent fall-through to the acting user's own settings. + $authorised = + ($targetUserId == $user['id']) || !empty($user['Role']['perm_site_admin']) || - (!empty($user['Role']['perm_admin']) && ($user_to_edit['User']['org_id'] == $user['org_id'])) - ) { - $userSetting['user_id'] = $data['UserSetting']['user_id']; + (!empty($user['Role']['perm_admin']) + && !empty($user_to_edit) + && ($user_to_edit['User']['org_id'] == $user['org_id']) + && !$this->User->isUserSiteAdmin($targetUserId)); + if (!$authorised) { + throw new MethodNotAllowedException(__('You are not authorised to edit the settings of this user.')); } + $userSetting['user_id'] = $targetUserId; } if (empty($userSetting['user_id'])) { $userSetting['user_id'] = $user['id'];
Vulnerability mechanics
Root cause
"Missing authorization check to exclude site administrator accounts from the set of users an organization administrator is allowed to administer."
Attack vector
An authenticated organization administrator (role `perm_admin` = 1) could directly call the user setting or login profile endpoints (e.g. `/userSettings/index` or `/userLoginProfiles/index`) with a target `user_id` belonging to a site administrator (role `perm_site_admin` = 1) in the same organization [ref_id=1]. The old access-control scoped administrative actions by `User.org_id` but did not exclude users whose `Role.perm_site_admin` flag is set, so the org admin's request passed the authorization gate and allowed viewing or altering the site admin's settings and login profiles [patch_id=5749246]. No additional authentication or network position is required beyond a valid org admin session.
Affected code
The vulnerability resides in the ACL logic of `UserLoginProfilesController`, `UserSettingsController`, `UserSetting`, and `User` model files [patch_id=5749246]. The `checkAccess()` and `setSetting()` methods in `UserSetting.php`, the `index()` and `admin_delete` methods in `UserLoginProfilesController.php`, and the `index()` method in `UserSettingsController.php` all lacked checks to prevent an organization administrator from viewing or modifying user settings and login profiles belonging to site administrator accounts within the same organization.
What the fix does
The patch adds a `getSiteAdminRoleIds()` method to `User.php` that returns all role IDs with `perm_site_admin = 1`, and an `isUserSiteAdmin()` helper that checks a given user's role privilege regardless of the disabled flag [patch_id=5749246]. In `UserLoginProfilesController::index()` and `UserSettingsController::index()`, the org admin's `User.org_id` condition is now supplemented with a `NOT` filter excluding those site-admin role IDs [patch_id=5749246]. In `UserSetting::checkAccess()`, the org admin branch now calls `isUserSiteAdmin()` on the target user and denies access when true [patch_id=5749246]. In `UserSetting::setSetting()`, the authorization logic was rewritten as an explicit boolean expression that checks `isUserSiteAdmin()` and throws `MethodNotAllowedException` when the org admin is not authorized, rather than silently falling through [patch_id=5749246]. The new `__administrableUserIds()` helper for the delete path returns `[-1]` when no users are administrable, ensuring an empty result fails closed [patch_id=5749246].
Preconditions
- authThe attacker must hold an organization administrator account (perm_admin = 1) within the same organization as the target site administrator.
- inputThe attacker sends a crafted HTTP request to user settings or login profile endpoints (e.g., /userSettings/index, /userLoginProfiles/index, /userSettings/setSetting, /userLoginProfiles/admin_delete) with the target site administrator's user ID.
- networkThe attack is performed over the network via the authenticated web interface or API.
Generated on Jun 12, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
1News mentions
0No linked articles in our index yet.