JeecgBoot Tenant Privilege Escalation: GET /sys/sysDepartRole/list Department ID Parameter Bypasses Tenant Validation
Contributors: huangweigang
1. Impact Scope
2. Vulnerable Endpoint
- GET
/sys/sysDepartRole/list?deptId=... (Department Role Paginated List Query 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:
@GetMapping(value = "/list")
public Result<?> queryPageList(SysDepartRole sysDepartRole, @RequestParam(name="pageNo", defaultValue="1") Integer pageNo, @RequestParam(name="pageSize", defaultValue="10") Integer pageSize, @RequestParam(name="deptId",required=false) String deptId, HttpServletRequest req)
- Key code (lines 73–104):
QueryWrapper<SysDepartRole> queryWrapper = QueryGenerator.initQueryWrapper(sysDepartRole, req.getParameterMap());
if(oConvertUtils.isNotEmpty(deptId)){
queryWrapper.eq("depart_id",deptId);
IPage<SysDepartRole> pageList = sysDepartRoleService.page(page, queryWrapper);
return Result.ok(pageList);
}
- Problem points:
- The endpoint directly uses the user-provided
deptId parameter to query department roles without verifying whether the department belongs to the current tenant
- The original tenant validation logic has been commented out (lines 81-92), leaving no tenant isolation checks in the current implementation
- Attackers can enumerate or guess department IDs of other tenants to obtain their department role configuration information
- Missing validation of the ownership relationship between tenant ID and department ID
4. Reproduction
-- Prerequisites
- Attacker has a valid login session (authenticated user)
- Attacker knows or can enumerate the target tenant's department ID (departId)
- System has multi-tenancy mode enabled
-- Steps (Cross-tenant Information Disclosure)
- Log in to the system using attacker account (Tenant A)
curl -X GET -H "Authorization: Bearer <attacker_token>" "http://<host>/jeecgboot/sys/sysDepartRole/list?deptId=<victim_dept_id>&pageNo=1&pageSize=10"
- Observation: API returns 200 OK, response data contains the department role list information of target Tenant B
- Verification:
- Switch to Tenant B's account and call the same endpoint to confirm data consistency
- Or query the database to verify that the returned department roles actually belong to another tenant
5. Impact
- Cross-tenant information disclosure
- Attackers can obtain other tenants' department role configuration information, including role codes, role names, permission scopes and other sensitive organizational structure data
- Business isolation failure
- Violates the data isolation principle of multi-tenant systems, where data from different tenants should be completely isolated
- Organizational structure information disclosure
- Exposes other tenants' organizational structures and permission system designs, which may be used for further attacks
- Compliance risks
- Multi-tenant data leakage may violate data protection regulations (such as GDPR, compliance requirements)
6. Remediation
- Server-side tenant ownership validation
- In the
queryPageList method, query department information based on deptId and verify whether the department's tenantId matches the current logged-in user's tenant ID:
SysDepart depart = sysDepartService.getById(deptId);
if(depart == null || !depart.getTenantId().equals(currentUser.getTenantId())) {
return Result.error("Unauthorized to access this department data");
}
- Enable tenant filter
- Force add current tenant ID filter in query conditions:
queryWrapper.eq("tenant_id", TenantContext.getTenant());
- Database-level isolation
- Use MyBatis-Plus multi-tenancy plugin to automatically add tenant filtering conditions at the SQL level
- Audit logging
- Record cross-tenant access attempts for security auditing and anomaly detection
JeecgBoot Tenant Privilege Escalation: GET /sys/sysDepartRole/list Department ID Parameter Bypasses Tenant Validation
Contributors: huangweigang
1. Impact Scope
2. Vulnerable Endpoint
/sys/sysDepartRole/list?deptId=...(Department Role Paginated List Query API)3. Code Analysis
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartRoleController.java@GetMapping(value = "/list")public Result<?> queryPageList(SysDepartRole sysDepartRole, @RequestParam(name="pageNo", defaultValue="1") Integer pageNo, @RequestParam(name="pageSize", defaultValue="10") Integer pageSize, @RequestParam(name="deptId",required=false) String deptId, HttpServletRequest req)QueryWrapper<SysDepartRole> queryWrapper = QueryGenerator.initQueryWrapper(sysDepartRole, req.getParameterMap());if(oConvertUtils.isNotEmpty(deptId)){queryWrapper.eq("depart_id",deptId);IPage<SysDepartRole> pageList = sysDepartRoleService.page(page, queryWrapper);return Result.ok(pageList);}deptIdparameter to query department roles without verifying whether the department belongs to the current tenant4. Reproduction
-- Prerequisites
-- Steps (Cross-tenant Information Disclosure)
curl -X GET -H "Authorization: Bearer <attacker_token>" "http://<host>/jeecgboot/sys/sysDepartRole/list?deptId=<victim_dept_id>&pageNo=1&pageSize=10"5. Impact
6. Remediation
queryPageListmethod, query department information based ondeptIdand verify whether the department'stenantIdmatches the current logged-in user's tenant ID:SysDepart depart = sysDepartService.getById(deptId);if(depart == null || !depart.getTenantId().equals(currentUser.getTenantId())) {return Result.error("Unauthorized to access this department data");}queryWrapper.eq("tenant_id", TenantContext.getTenant());