RuoYi issue1: Role Menu Permission Overwrite

RuoYi issue1: Role Menu Permission Overwrite

Vulnerability call chain

1.1 Summary

RuoYi has a missing authorization vulnerability: Role Menu Permission Overwrite. 角色被授予操作者本不具备的菜单/接口权限;若该角色已分配给用户,权限会在 Shiro 重新加载后生效。

  • Attack precondition: 非超级管理员拥有 system:role:addsystem:role:edit
  • Affected authorization property: ``sys_role_menu.role_id, sys_role_menu.menu_id, SysRole.menuIds
  • Security impact: 角色被授予操作者本不具备的菜单/接口权限;若该角色已分配给用户,权限会在 Shiro 重新加载后生效。

1.2 Exploit path

直接 POST /system/role/add/system/role/edit,提交当前用户不可见或不可授权的 menuIds

1.3 Key code evidence

  1. ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java

Evidence location: https://github.com/yangzongzhuan/RuoYi/blob/master/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java#L95

   92      @PostMapping("/add")93      @ResponseBody94      public AjaxResult addSave(@Validated SysRole role)95      {96          if (!roleService.checkRoleNameUnique(role))97          {98              return error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在");99          }100          else if (!roleService.checkRoleKeyUnique(role))101          {102              return error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在");103          }104          role.setCreateBy(getLoginName());105          AuthorizationUtils.clearAllCachedAuthorizationInfo();106          return toAjax(roleService.insertRole(role));107  108      }109  110      /**111       * 修改角色112       */113      @RequiresPermissions("system:role:edit")114      @GetMapping("/edit/{roleId}")
  1. ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java

Evidence location: https://github.com/yangzongzhuan/RuoYi/blob/master/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java#L198

  195       * @param role 角色信息196       * @return 结果197       */198      @Override199      @Transactional200      public int updateRole(SysRole role)201      {202          // 修改角色信息203          roleMapper.updateRole(role);204          // 删除角色与菜单关联205          roleMenuMapper.deleteRoleMenuByRoleId(role.getRoleId());206          return insertRoleMenu(role);207      }
  1. ruoyi-system/target/classes/mapper/system/SysRoleMenuMapper.xml

Evidence location: https://github.com/yangzongzhuan/RuoYi/blob/master/ruoyi-system/target/classes/mapper/system/SysRoleMenuMapper.xml#L27

   24          </foreach> 25   	</delete>26  	27  	<insert id="batchRoleMenu">28  		insert into sys_role_menu(role_id, menu_id) values29  		<foreach item="item" index="index" collection="list" separator=",">30  			(#{item.roleId},#{item.menuId})31  		</foreach>32  	</insert>33  	34  </mapper> 
  1. ruoyi-system/target/classes/mapper/system/SysMenuMapper.xml

Evidence location: https://github.com/yangzongzhuan/RuoYi/blob/master/ruoyi-system/target/classes/mapper/system/SysMenuMapper.xml#L64

   61  		order by m.parent_id, m.order_num62  	</select>63  64  	<select id="selectPermsByUserId" parameterType="Long" resultType="String">65  		select distinct m.perms66  		from sys_menu m67  			 left join sys_role_menu rm on m.menu_id = rm.menu_id68  			 left join sys_user_role ur on rm.role_id = ur.role_id69  			 left join sys_role r on r.role_id = ur.role_id70  		where m.visible = '0' and r.status = '0' and ur.user_id = #{userId}71  	</select>72  73  	<select id="selectPermsByRoleId" parameterType="Long" resultType="String">

3. Root Cause Analysis

Root Cause 1: Missing server-side authorization on the vulnerable operation.

The endpoint accepts user-controlled authorization-sensitive identifiers or fields, but the write/read path does not prove that the current caller may operate on the target object.

Root Cause 2: Missing object-scope or grant-bound validation.

The implementation relies on endpoint access, UI filtering, or object existence checks instead of enforcing target ownership, tenant boundary, role ceiling, or grantable-resource constraints at the service layer.

保存角色菜单前校验每个 menuId 属于 menuService.selectMenuAll(currentUserId),或仅允许超级管理员写 sys_role_menu

5. Verification after fix

  • Unauthorized callers receive HTTP 403 or equivalent rejection.
  • Out-of-scope target identifiers are rejected before database writes or sensitive reads.
  • Role, permission, tenant, organization, ownership, or grant-bound ceilings are enforced server-side.
  • Direct HTTP requests are rejected even when front-end controls are hidden.