首页 网络安全 正文
  • 本文约5814字,阅读需29分钟
  • 71
  • 0

eladmin 2.7 访问控制绕过CVE-2026-8127

摘要

栋科技漏洞库关注到eladmin 2.7的UserController.createUser接口Mass Assignment漏洞,追踪为CVE-2026-8127,CVSS 3.1评分6.3。

eladmin 是一款开源、免费的 Java 后台管理系统,是基于 Spring Boot + Vue 的前后端分离 Java 后台管理框架(Apache-2.0 协议)。

一、基本情况

eladmin 基于 Spring Boot、Spring Security、JPA 等技术栈,提供用户管理、角色权限等企业级功能,后端支持 JPA和MyBatis-Plus。

eladmin 数据权限、动态菜单、定时任务、系统监控、日志审计等常用模块,适合快速开发企业级后台、OA、CMS 及各类管理系统。

eladmin 2.7 访问控制绕过CVE-2026-8127

栋科技漏洞库关注到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

六、参考链接

管理员已设置登录后刷新可查看



扫描二维码,在手机上阅读
评论
更换验证码
友情链接