Skip to content

OpsliBoot多租户隔离失效导致跨租户越权 #47

Description

@FHMTT

版本号:

2.2.1 (master)

问题描述:

在组织管理模块SysOrgRestController中,多个接口存在多租户隔离缺失问题,攻击者在具备基础权限的情况下,可通过构造请求参数实现跨租户的数据写入、修改及删除,导致权限绕过。

  1. insert接口
    在新增组织机构数据时,系统未对请求体中的tenantIdparentId字段进行有效校验或绑定。当创建根节点组织时,后端会直接使用客户端传入的tenantId进行数据写入;当指定父节点组织时,后端会通过父节点自动设置tenantId。攻击者可通过篡改这些字段,将数据插入到目标租户中,实现跨租户写入。
  2. update接口
    更新接口同样存在租户字段未受保护的问题。攻击者可以通过篡改请求体中的字段,将自身租户下的数据迁移至其他租户,实现跨租户写操作。
  3. del接口
    删除操作仅基于传入的id执行,但未校验该资源是否属于当前用户所在租户。攻击者可通过指定其他租户的id,直接删除对应数据,实现跨租户删除。

综上,SysOrgRestController中,未在后端对租户信息进行绑定与归属校验,直接信任前端传入的字段,导致多租户隔离机制失效,影像数据完整性与隔离性。

漏洞分析:

insert接口为例,在Controller层中,接口接收前端传入的SysOrgModel对象,但未对其中字段进行校验,然后直接将数据传入Service层。

// 如果新增的是 根节点数据 则需要验证权限
if(null != model && TreeBuildUtil.DEF_PARENT_ID.equals(model.getParentId())){
    UserModel currUser = UserUtil.getUser();
    // 如果不是超级管理员 和 租户管理员
    if(!StringUtils.equals(UserUtil.SUPER_ADMIN, currUser.getUsername())  &&
            !TenantUtil.SUPER_ADMIN_TENANT_ID.equals(currUser.getTenantId()) ){
        RoleModel defRoleByUserId = UserUtil.getUserDefRoleByUserId(currUser.getId());
        if(null == defRoleByUserId ||
                StringUtils.isEmpty(defRoleByUserId.getDataScope()) ||
                !DictType.DATA_SCOPE_ALL.getValue().equals(defRoleByUserId.getDataScope())){
            // 无组织机构新增权限
            throw new ServiceException(SystemMsg.EXCEPTION_ORG_NOT_PERMISSION);
        }
    }
}
// 调用新增方法
IService.insert(model);

在Service层insert方法中,系统对租户信息的处理依赖如下逻辑:

// 如果上级ID 为空 则默认为 0
if(StringUtils.isEmpty(model.getParentId()) || TOP_PARENT_ID.equals(model.getParentId())){
    model.setParentId(TOP_PARENT_ID);
    model.setParentIds(TOP_PARENT_ID);
}

// 如果上级ID不为空 且 不等于顶级ID
if(StringUtils.isNotEmpty(model.getParentId()) &&
        !TOP_PARENT_ID.equals(model.getParentId())
){
    SysOrgModel sysOrgModel = super.get(model.getParentId());
    // 下级沿用上级租户ID
    model.setTenantId(sysOrgModel.getTenantId());
    // 下级沿用上级ParentIds
    model.setParentIds(
            StrUtil.appendIfMissing(
                    sysOrgModel.getParentIds(), DELIMITER) +
                    sysOrgModel.getId());
}

当创建根节点组织时,tenantId完全来自前端输入,并直接参与后续数据写入;而存在父节点情况下,后端未校验parentId是否属于当前用户所在租户,直接继承父节点租户。

对比分析:

在用户管理模块的实现中(如UserRestController.insert),系统对tenantId进行了显式的权限控制,在无权限情况下强制清空客户端传入的tenantId

// 判断用户是否有 修改租户的能力 (超级管理员除外)
if(StringUtils.isNotEmpty(model.getTenantId())){
    // 如果没有租户修改能力 则清空对应字段
    if(!UserUtil.isHasUpdateTenantPerms(UserUtil.getUser())){
        model.setTenantId(null);
        model.setIzTenantAdmin(null);
        model.setEnableSwitchTenant(DictType.NO_YES_NO.getValue());
    }
}

补充说明:

  • update 接口:逻辑与 insert 类似,后端直接使用了前端传入的 tenantId,从而实现跨租户数据修改。
  • delete 接口:删除操作基于 id 执行,未校验该资源是否属于当前租户,也未附加租户条件,攻击者可通过指定其他租户的 id 实现跨租户删除,属于典型 IDOR 漏洞。

漏洞复现:

insert接口为例,使用默认导入的sql文件,t123456(攻击者)和tenant(目标)用户位于两个不同租户中。tenant所在租户id为1,当前组织如下:
Image
攻击者通过构造请求体,并篡改tenantId,实现越权写入操作:
Image
此外,通过篡改parentId,也可以实现跨租户子机构添加操作:
Image
Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions