JeecgBoot Tenant Privilege Escalation: GET /sys/position/getPositionUserList Position Member Query Without Tenant Validation
Contributors: huangweigang
1. Impact Scope
2. Vulnerable Endpoint
- GET
/sys/position/getPositionUserList?positionId=...&pageNo=1&pageSize=10 (Get Position User List API)
3. Code Analysis
- Controller:
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysPositionController.java
- Route & method:
@GetMapping("/getPositionUserList")
public Result<IPage<SysUser>> getPositionUserList(@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, @RequestParam(name = "positionId") String positionId)
- Key code (lines 358–373):
Page<SysUser> page = new Page<>(pageNo, pageSize);
IPage<SysUser> pageList = userPositionService.getPositionUserList(page, positionId);
List<String> userIds = pageList.getRecords().stream().map(SysUser::getId).collect(Collectors.toList());
if (null != userIds && userIds.size() > 0) {
Map<String, String> useDepNames = userService.getDepNamesByUserIds(userIds);
pageList.getRecords().forEach(item -> {
item.setOrgCodeTxt(useDepNames.get(item.getId()));
});
}
return Result.ok(pageList);
- Problem points:
- The endpoint directly uses the
positionId parameter to query the user list under the position
- Does not verify whether the position belongs to the current tenant
- Attackers can obtain personnel information of other tenants by enumerating position IDs
- Returned user information includes username, real name, department affiliation and other sensitive data
4. Reproduction
-- Prerequisites
- Attacker has a valid login session
- Attacker knows or can enumerate the target tenant's position ID
- System has position management functionality configured
-- Steps (Cross-tenant Personnel Information Disclosure)
- Using attacker account (Tenant A):
curl -X GET -H "Authorization: Bearer <attacker_token>" "http://<host>/jeecgboot/sys/position/getPositionUserList?positionId=<victim_position_id>&pageNo=1&pageSize=100"
- Observation: API returns 200 OK, returns user list under the target tenant's position, including:
- User ID (id)
- Username (username)
- Real name (realname)
- Department name (orgCodeTxt)
- Other user attribute information
- Verification:
- Use Tenant B's administrator account to query the same position's member list
- Database query sys_user_position association table to confirm data ownership
5. Impact
- Cross-tenant personnel information disclosure
- Attackers can obtain other tenants' employee information (names, accounts, departments, etc.)
- Serious invasion of user privacy, violating data protection regulations
- Organizational structure exposure
- Organization structure can be inferred through the correspondence between positions and personnel
- Understand personnel allocation for different positions
- Provides intelligence for social engineering attacks
- After obtaining real names and position information, targeted phishing attacks can be conducted
- Impersonate specific position personnel for fraud
- Account enumeration
- Obtain a list of valid usernames, providing targets for brute force attacks
6. Remediation
- Position tenant ownership validation
- Verify whether the position belongs to the current tenant before querying:
SysPosition position = sysPositionService.getById(positionId);
LoginUser currentUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
if(position == null || !position.getTenantId().equals(currentUser.getTenantId())) {
return Result.error("Unauthorized to access this position data");
}
- Force tenant filtering
- Add tenant validation in the Service layer's getPositionUserList method
- Ensure only current tenant's position and user information is returned
- Data desensitization
- Apply desensitization to sensitive user information (such as phone numbers, emails)
- Return different granularity of user information based on querier's permissions
- Permission level control
- Restrict only HR administrators or department heads to view position member lists
- Audit logging
- Record all position member query operations
- Monitor abnormal query behavior (such as querying many different positions in a short time)
JeecgBoot Tenant Privilege Escalation: GET /sys/position/getPositionUserList Position Member Query Without Tenant Validation
Contributors: huangweigang
1. Impact Scope
2. Vulnerable Endpoint
/sys/position/getPositionUserList?positionId=...&pageNo=1&pageSize=10(Get Position User List API)3. Code Analysis
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysPositionController.java@GetMapping("/getPositionUserList")public Result<IPage<SysUser>> getPositionUserList(@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, @RequestParam(name = "positionId") String positionId)Page<SysUser> page = new Page<>(pageNo, pageSize);IPage<SysUser> pageList = userPositionService.getPositionUserList(page, positionId);List<String> userIds = pageList.getRecords().stream().map(SysUser::getId).collect(Collectors.toList());if (null != userIds && userIds.size() > 0) {Map<String, String> useDepNames = userService.getDepNamesByUserIds(userIds);pageList.getRecords().forEach(item -> {item.setOrgCodeTxt(useDepNames.get(item.getId()));});}return Result.ok(pageList);positionIdparameter to query the user list under the position4. Reproduction
-- Prerequisites
-- Steps (Cross-tenant Personnel Information Disclosure)
curl -X GET -H "Authorization: Bearer <attacker_token>" "http://<host>/jeecgboot/sys/position/getPositionUserList?positionId=<victim_position_id>&pageNo=1&pageSize=100"5. Impact
6. Remediation
SysPosition position = sysPositionService.getById(positionId);LoginUser currentUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();if(position == null || !position.getTenantId().equals(currentUser.getTenantId())) {return Result.error("Unauthorized to access this position data");}