JeecgBoot Tenant Privilege Escalation: GET /sys/sysDepartRole/getDeptRoleList Department Role Query Without Tenant Validation
Contributors: huangweigang
1. Impact Scope
2. Vulnerable Endpoint
- GET
/sys/sysDepartRole/getDeptRoleList?departId=...&userId=... (Get Department Role List API)
3. Code Analysis
- Controller:
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartRoleController.java
- Route & method:
@RequestMapping(value = "/getDeptRoleList", method = RequestMethod.GET)
public Result<List<SysDepartRole>> getDeptRoleList(@RequestParam(value = "departId") String departId,@RequestParam(value = "userId") String userId)
- Key code (lines 183–191):
Result<List<SysDepartRole>> result = new Result<>();
//Query roles in the selected department
List<SysDepartRole> deptRoleList = sysDepartRoleService.list(new LambdaQueryWrapper<SysDepartRole>().eq(SysDepartRole::getDepartId,departId));
result.setSuccess(true);
result.setResult(deptRoleList);
return result;
- Problem points:
- The endpoint directly uses the
departId parameter to query the department role list without any tenant ownership validation
- The
userId parameter, although passed in, is not used and cannot serve as permission verification
- Attackers can obtain all role configurations of any department using arbitrary department IDs
- Missing tenant isolation and permission verification mechanisms
4. Reproduction
-- Prerequisites
- Attacker has a valid login session
- Attacker knows or can enumerate the target tenant's department ID
- Can construct arbitrary userId parameter (this parameter is not actually used)
-- Steps (Cross-tenant Role Information Disclosure)
- Using attacker account (Tenant A):
curl -X GET -H "Authorization: Bearer <attacker_token>" "http://<host>/jeecgboot/sys/sysDepartRole/getDeptRoleList?departId=<victim_dept_id>&userId=dummy"
- Observation: API returns 200 OK, returns all roles list under Tenant B's department
- Verification:
- Use Tenant B's legitimate account to call the same endpoint and compare the returned data
- Database query confirms the returned role data belongs to another tenant
5. Impact
- Cross-tenant information disclosure
- Attackers can obtain any tenant's department role information, including role names, role codes, descriptions, etc.
- Permission system exposure
- Exposes other tenants' permission architecture design, helping attackers understand the target system's permission model
- Organizational structure information disclosure
- Role information can reveal the organization's department function division and management hierarchy
- Provides intelligence for further attacks
- After understanding target tenant's permission configuration, targeted privilege escalation attacks can be conducted
6. Remediation
- Add tenant ownership validation
- Verify whether the department belongs to the current tenant before querying:
SysDepart depart = sysDepartService.getById(departId);
LoginUser currentUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
if(depart == null || !depart.getTenantId().equals(currentUser.getTenantId())) {
return Result.error("Unauthorized to access this department role information");
}
- Force tenant filtering
- Add tenant ID filtering condition when querying roles to ensure only current tenant's data is returned
- Use userId parameter for permission verification
- Verify whether the current user has permission to view the specified department's role information
- Database-level tenant isolation
- Configure MyBatis-Plus multi-tenancy plugin to automatically add tenant ID filtering in SQL
- Audit logging
- Record all department role query operations, especially cross-tenant access attempts
JeecgBoot Tenant Privilege Escalation: GET /sys/sysDepartRole/getDeptRoleList Department Role Query Without Tenant Validation
Contributors: huangweigang
1. Impact Scope
2. Vulnerable Endpoint
/sys/sysDepartRole/getDeptRoleList?departId=...&userId=...(Get Department Role List API)3. Code Analysis
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartRoleController.java@RequestMapping(value = "/getDeptRoleList", method = RequestMethod.GET)public Result<List<SysDepartRole>> getDeptRoleList(@RequestParam(value = "departId") String departId,@RequestParam(value = "userId") String userId)Result<List<SysDepartRole>> result = new Result<>();//Query roles in the selected departmentList<SysDepartRole> deptRoleList = sysDepartRoleService.list(new LambdaQueryWrapper<SysDepartRole>().eq(SysDepartRole::getDepartId,departId));result.setSuccess(true);result.setResult(deptRoleList);return result;departIdparameter to query the department role list without any tenant ownership validationuserIdparameter, although passed in, is not used and cannot serve as permission verification4. Reproduction
-- Prerequisites
-- Steps (Cross-tenant Role Information Disclosure)
curl -X GET -H "Authorization: Bearer <attacker_token>" "http://<host>/jeecgboot/sys/sysDepartRole/getDeptRoleList?departId=<victim_dept_id>&userId=dummy"5. Impact
6. Remediation
SysDepart depart = sysDepartService.getById(departId);LoginUser currentUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();if(depart == null || !depart.getTenantId().equals(currentUser.getTenantId())) {return Result.error("Unauthorized to access this department role information");}