Skip to content

Commit 3babaf0

Browse files
mbiukiclaude
andauthored
fix(security): Add missing authorization checks to DWR endpoints (dotCMS#33676)
## 🔒 Security Fix: DWR Authorization Bypass This PR addresses a **critical security vulnerability** reported in [private-issues#482](dotCMS/private-issues#482) where DWR (Direct Web Remoting) endpoints lacked proper authorization validation. ### 🚨 Vulnerability Summary Users with minimal backend access (System → Back-end user role) could call privileged API endpoints that should be restricted to administrators, leading to: - **Privilege escalation** via `saveRolePermission` - **Information disclosure** of users, roles, and permissions - **System information leakage** via thread monitoring ### 📋 Affected Endpoints Fixed | Endpoint | Severity | Issue | |----------|----------|-------| | `RoleAjax.saveRolePermission` | **CRITICAL** | No authorization check - allows privilege escalation | | `RoleAjax.getRolePermissions` | HIGH | Only authenticated, not authorized | | `RoleAjax.getRole` | HIGH | No validation at all | | `RoleAjax.getUserRole` | HIGH | No validation at all | | `RoleAjax.getCurrentCascadePermissionsJobs` | HIGH | No validation at all | | `RoleAjax.isPermissionableInheriting` | HIGH | Only authenticated, not authorized | | `UserAjax.getUsersList` | HIGH | Only authenticated, not authorized | | `ThreadMonitorTool.getThreads` | MEDIUM | Never calls its own `validateUser()` method | ### ✅ Changes Made #### RoleAjax.java (6 methods) Added `validateRolesPortletPermissions(getLoggedInUser())` to: - `saveRolePermission()` - prevents privilege escalation - `getRolePermissions()` - prevents permission enumeration - `getRole()` - prevents role information disclosure - `getUserRole()` - prevents user role disclosure - `getCurrentCascadePermissionsJobs()` - prevents job enumeration - `isPermissionableInheriting()` - prevents permission inheritance disclosure #### UserAjax.java (1 method) Changed `getUsersList()` from: ```java getLoggedInUser(); // Only checks authentication ``` To: ```java validateUsersPortletPermissions(getLoggedInUser()); // Checks authorization ``` #### ThreadMonitorTool.java (1 method) Added validation call to `getThreads()`: ```java if (!validateUser()) { throw new DotRuntimeException("User does not have access to the CMS Maintenance Portlet"); } ``` ### 🔍 Security Pattern Used All fixes follow the existing security pattern established in protected methods like `RoleAjax.removeUsersFromRole()` and `UserAjax.addUser()`. **Validation methods from `DwrUtil.java`:** - `validateRolesPortletPermissions(User)` - Validates access to Roles portlet - `validateUsersPortletPermissions(User)` - Validates access to Users portlet - `validateUser()` - Validates access to Maintenance portlet (ThreadMonitorTool) ### 🧪 Testing Recommendations Before this fix: ```bash # Unprivileged user with JSESSIONID could call: curl -X POST http://localhost:8080/dwr/call/plaincall/UserAjax.getUsersList.dwr \ -H "Cookie: JSESSIONID=<unprivileged_session>" \ --data-raw "..." # Returns: List of all users ❌ ``` After this fix: ```bash # Same request now returns: # DotSecurityException: "User does not have access to the [roles/users] Portlet" ✅ ``` **Test cases to verify:** 1. Unprivileged user (System → Back-end only) **cannot** call any of the 8 fixed endpoints 2. Admin user (System → Administrator) **can** call all endpoints 3. User with user admin permissions **can** call user-related endpoints 4. User with role admin permissions **can** call role-related endpoints ### 📊 Impact - **Before:** Any backend user could escalate privileges and enumerate system data - **After:** Only users with proper admin permissions can access these endpoints ### 📚 Related Issues - Fixes: dotCMS/private-issues#482 - Historical context: dotCMS#3031 (DWR general lockdown - 2013) - Related migration: dotCMS#22524 (Permissions migration from DWR to REST - 2022) ### 🔐 Security Considerations This is a **critical security fix** that should be: - **Backported** to all supported LTS versions - **Deployed immediately** to affected systems - **Documented** in the security advisory after deployment --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <[email protected]>
1 parent 1646d83 commit 3babaf0

File tree

3 files changed

+22
-6
lines changed

3 files changed

+22
-6
lines changed

dotCMS/src/main/java/com/dotcms/cmsmaintenance/ajax/ThreadMonitorTool.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ public boolean validateUser() {
5353
* Helper method; stringfies the ThreadInfos and returns them as a string array
5454
*/
5555
public String[] getThreads() {
56-
57-
58-
56+
// Validate user has access to the CMS Maintenance Portlet
57+
if (!validateUser()) {
58+
throw new DotRuntimeException("User does not have access to the CMS Maintenance Portlet");
59+
}
60+
5961
ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
6062

6163
StringBuilder sb = new StringBuilder();

dotCMS/src/main/java/com/dotmarketing/business/ajax/RoleAjax.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,8 @@ public void deleteLayout(String layoutId) throws DotDataException, PortalExcepti
712712
* @throws DotRuntimeException
713713
*/
714714
public List<Map<String, Object>> getRolePermissions(String roleId) throws DotDataException, DotSecurityException, PortalException, SystemException {
715+
//Validate if this logged in user has the required permissions to access the roles portlet
716+
validateRolesPortletPermissions(getLoggedInUser());
715717

716718
UserWebAPI userWebAPI = WebAPILocator.getUserWebAPI();
717719
WebContext ctx = WebContextFactory.get();
@@ -799,6 +801,9 @@ public List<Map<String, Object>> getRolePermissions(String roleId) throws DotDat
799801
}
800802

801803
public void saveRolePermission(String roleId, String folderHostId, Map<String, String> permissions, boolean cascade) throws DotDataException, DotSecurityException, PortalException, SystemException {
804+
//Validate if this logged in user has the required permissions to access the roles portlet
805+
validateRolesPortletPermissions(getLoggedInUser());
806+
802807
Logger.info(this, "Applying role permissions for role " + roleId + " and folder/host id " + folderHostId);
803808

804809
UserAPI userAPI = APILocator.getUserAPI();
@@ -901,7 +906,10 @@ public void saveRolePermission(String roleId, String folderHostId, Map<String, S
901906
Logger.info(this, "Done applying role permissions for role " + roleId + " and folder/host id " + folderHostId);
902907
}
903908

904-
public List<Map<String, Object>> getCurrentCascadePermissionsJobs () throws DotDataException, DotSecurityException {
909+
public List<Map<String, Object>> getCurrentCascadePermissionsJobs () throws DotDataException, DotSecurityException, PortalException, SystemException {
910+
//Validate if this logged in user has the required permissions to access the roles portlet
911+
validateRolesPortletPermissions(getLoggedInUser());
912+
905913
HostAPI hostAPI = APILocator.getHostAPI();
906914
FolderAPI folderAPI = APILocator.getFolderAPI();
907915
RoleAPI roleAPI = APILocator.getRoleAPI();
@@ -935,13 +943,17 @@ public List<Map<String, Object>> getCurrentCascadePermissionsJobs () throws DotD
935943
}
936944

937945
public Map<String, Object> getRole(String roleId) throws DotDataException, DotSecurityException, PortalException, SystemException {
946+
//Validate if this logged in user has the required permissions to access the roles portlet
947+
validateRolesPortletPermissions(getLoggedInUser());
938948

939949
RoleAPI roleAPI = APILocator.getRoleAPI();
940950
return roleAPI.loadRoleById(roleId).toMap();
941951

942952
}
943953

944954
public Map<String, Object> getUserRole(String userId) throws DotDataException, DotSecurityException, PortalException, SystemException {
955+
//Validate if this logged in user has the required permissions to access the roles portlet
956+
validateRolesPortletPermissions(getLoggedInUser());
945957

946958
Map<String, Object> toReturn = new HashMap<>();
947959

@@ -975,6 +987,8 @@ private List<String> getPorletTitlesFromLayout (Layout l) throws LanguageExcepti
975987

976988
@CloseDBIfOpened
977989
public Map<String, Object> isPermissionableInheriting(String assetId) throws DotDataException, DotRuntimeException, PortalException, SystemException, DotSecurityException{
990+
//Validate if this logged in user has the required permissions to access the roles portlet
991+
validateRolesPortletPermissions(getLoggedInUser());
978992

979993
UserWebAPI userWebAPI = WebAPILocator.getUserWebAPI();
980994
WebContext ctx = WebContextFactory.get();

dotCMS/src/main/java/com/dotmarketing/portlets/user/ajax/UserAjax.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -851,8 +851,8 @@ public Map<String, Object> getRolesList(String assetInode, String permission, Ma
851851
* An error occurred when retrieving the user list.
852852
*/
853853
public Map<String, Object> getUsersList(String assetInode, String permission, Map<String, String> params) throws Exception {
854-
// Make sure the DWR request calling this method is authenticated
855-
getLoggedInUser();
854+
// Validate if this logged in user has the required permissions to access the users portlet
855+
validateUsersPortletPermissions(getLoggedInUser());
856856
return UserServiceFactory.getInstance().getUserService().getUsersList(assetInode, permission, params);
857857
}
858858

0 commit comments

Comments
 (0)