From 4d0d4977ff0d3c9288c438fec917002bccdb31c3 Mon Sep 17 00:00:00 2001 From: mbiuki Date: Sun, 28 Jun 2026 14:09:44 -0400 Subject: [PATCH 1/2] sec: enforce admin check on layout assignment and role grant endpoints ToolGroupResource._addtouser and ._removefromuser now require the caller to be a CMS Administrator before assigning or removing any layout other than the gettingStarted onboarding layout. Previously any backend user could self-assign the admin Settings layout (which contains the roles portlet), bypassing the intended privilege boundary. RoleAjax.addUserToRole and .removeUsersFromRole now require the caller to be a CMS Administrator in addition to the existing portlet-access check. Previously a user who had gained roles-portlet access could grant themselves (or any user) any role including CMS Administrator via DWR. Together these two gaps formed a privilege-escalation chain that allowed any authenticated backend user to reach CMS Administrator and subsequently execute arbitrary OS commands via OSGi bundle upload. Closes: dotCMS/private-issues#640 Co-Authored-By: Claude Sonnet 4.6 --- .../rest/api/v1/portlet/ToolGroupResource.java | 10 ++++++++++ .../dotmarketing/business/ajax/RoleAjax.java | 18 ++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/portlet/ToolGroupResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/portlet/ToolGroupResource.java index ece7ceb94695..471434db738e 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/portlet/ToolGroupResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/portlet/ToolGroupResource.java @@ -80,6 +80,11 @@ public final Response deleteToolGroupFromUser(@Context final HttpServletRequest user = APILocator.getUserAPI().loadUserById(userid, loggedInUser, true); } + if (!loggedInUser.isAdmin()) { + throw new DotSecurityException( + "User does not have permission to remove layouts from users"); + } + Layout layoutToRemove = getLayout(layoutId); APILocator.getRoleAPI().removeLayoutFromRole(layoutToRemove, @@ -120,6 +125,11 @@ public final Response addToolGroupToUser(@Context final HttpServletRequest reque user = APILocator.getUserAPI().loadUserById(userid, loggedInUser, true); } + if (!layoutId.equalsIgnoreCase("gettingstarted") && !loggedInUser.isAdmin()) { + throw new DotSecurityException( + "User does not have permission to assign layouts"); + } + final Layout layoutToAdd = getLayout(layoutId); APILocator.getLayoutAPI() diff --git a/dotCMS/src/main/java/com/dotmarketing/business/ajax/RoleAjax.java b/dotCMS/src/main/java/com/dotmarketing/business/ajax/RoleAjax.java index 2229f4f41822..4e4160332456 100644 --- a/dotCMS/src/main/java/com/dotmarketing/business/ajax/RoleAjax.java +++ b/dotCMS/src/main/java/com/dotmarketing/business/ajax/RoleAjax.java @@ -282,7 +282,14 @@ private Map getUserMap(final User user) public void removeUsersFromRole(String[] userIds, String roleId) throws DotDataException, NoSuchUserException, DotRuntimeException, PortalException, SystemException, DotSecurityException { //Validate if this logged in user has the required permissions to access the roles portlet - validateRolesPortletPermissions(getLoggedInUser()); + final User callerForRemove = getLoggedInUser(); + validateRolesPortletPermissions(callerForRemove); + if (!callerForRemove.isAdmin()) { + SecurityLogger.logInfo(getClass(), + "Unauthorized attempt to remove role by user " + callerForRemove.getUserId()); + throw new DotSecurityException( + "User does not have permission to remove roles from users"); + } WebContext ctx = WebContextFactory.get(); RoleAPI roleAPI = APILocator.getRoleAPI(); @@ -310,7 +317,14 @@ public void removeUsersFromRole(String[] userIds, String roleId) throws DotDataE public Map addUserToRole(String userId, String roleId) throws DotDataException, DotRuntimeException, PortalException, SystemException, DotSecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { //Validate if this logged in user has the required permissions to access the roles portlet - validateRolesPortletPermissions(getLoggedInUser()); + final User caller = getLoggedInUser(); + validateRolesPortletPermissions(caller); + if (!caller.isAdmin()) { + SecurityLogger.logInfo(getClass(), + "Unauthorized attempt to assign role by user " + caller.getUserId()); + throw new DotSecurityException( + "User does not have permission to assign roles to users"); + } WebContext ctx = WebContextFactory.get(); RoleAPI roleAPI = APILocator.getRoleAPI(); From ff86ac22f74abeda49124e2f20d881f51d858c0f Mon Sep 17 00:00:00 2001 From: mbiuki Date: Fri, 3 Jul 2026 12:09:52 -0400 Subject: [PATCH 2/2] sec: fail-fast before loadUserById in ToolGroupResource endpoints Move the isAdmin() check above the loadUserById() call in both _removefromuser and _addtouser so unauthorized callers are rejected before any DB round-trip for the target user. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01JHg1W7beD4Z1yLoTJpyXss --- .../rest/api/v1/portlet/ToolGroupResource.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/portlet/ToolGroupResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/portlet/ToolGroupResource.java index 471434db738e..096a5bff32a6 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/portlet/ToolGroupResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/portlet/ToolGroupResource.java @@ -76,15 +76,15 @@ public final Response deleteToolGroupFromUser(@Context final HttpServletRequest .init() .getUser(); - if (null != userid){ - user = APILocator.getUserAPI().loadUserById(userid, loggedInUser, true); - } - if (!loggedInUser.isAdmin()) { throw new DotSecurityException( "User does not have permission to remove layouts from users"); } + if (null != userid){ + user = APILocator.getUserAPI().loadUserById(userid, loggedInUser, true); + } + Layout layoutToRemove = getLayout(layoutId); APILocator.getRoleAPI().removeLayoutFromRole(layoutToRemove, @@ -121,15 +121,15 @@ public final Response addToolGroupToUser(@Context final HttpServletRequest reque .init() .getUser(); - if (null != userid){ - user = APILocator.getUserAPI().loadUserById(userid, loggedInUser, true); - } - if (!layoutId.equalsIgnoreCase("gettingstarted") && !loggedInUser.isAdmin()) { throw new DotSecurityException( "User does not have permission to assign layouts"); } + if (null != userid){ + user = APILocator.getUserAPI().loadUserById(userid, loggedInUser, true); + } + final Layout layoutToAdd = getLayout(layoutId); APILocator.getLayoutAPI()