Apache ShenYu Admin ultra vires
Description
Apache ShenYu Admin allows low-privilege administrators to create users with higher privileges, leading to privilege escalation in version 2.5.0.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Apache ShenYu Admin allows low-privilege administrators to create users with higher privileges, leading to privilege escalation in version 2.5.0.
Vulnerability
CVE-2022-42735 is an improper privilege management vulnerability in Apache ShenYu Admin. The application fails to enforce proper role hierarchies, allowing low-level administrators to create user accounts with higher privileges than their own [1]. This flaw stems from insufficient validation when assigning roles during user creation.
Exploitation
An attacker with low-level administrative access to the ShenYu Admin dashboard can exploit this by creating new users with elevated roles. No additional authentication is required beyond the initial admin credentials [1]. The attack surface is limited to the admin interface, but accessible to any low-privilege admin.
Impact
Successful exploitation leads to privilege escalation, granting the attacker full administrative control over the ShenYu instance. This could allow further compromise of managed services, data access, and configuration changes [1].
Mitigation
The vulnerability has been patched in Apache ShenYu version 2.5.1. Users should upgrade immediately or apply the fix from pull request #3958, which introduces a super administrator role with configurable exclusive privileges to prevent such escalations [1][3].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.shenyu:shenyu-adminMaven | < 2.5.1 | 2.5.1 |
Affected products
2Patches
163e224670076[type fixbug] Apache ShenYu Admin ultra vires (#3958)
16 files changed · +224 −65
shenyu-admin/src/main/java/org/apache/shenyu/admin/config/properties/DashboardProperties.java+83 −1 modified@@ -17,15 +17,20 @@ package org.apache.shenyu.admin.config.properties; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +import java.util.ArrayList; +import java.util.List; + /** * admin dashboard properties. */ @Configuration @ConfigurationProperties(prefix = "shenyu.dashboard.core") -public class DashboardProperties { +public class DashboardProperties implements InitializingBean { /** * record log limit. @@ -46,6 +51,21 @@ public class DashboardProperties { */ private Boolean enablePrintApiLog = false; + /** + * enable OnlySuperAdminPermission. + * default is true + */ + private Boolean enableOnlySuperAdminPermission = true; + + + /** + * Only the super administrator root user has the privileges. + * default is 3. + * + * @see #afterPropertiesSet() + */ + private List<String> onlySuperAdminPermission; + /** * get recordLogLimit. * @@ -99,4 +119,66 @@ public Boolean getEnablePrintApiLog() { public void setEnablePrintApiLog(final Boolean enablePrintApiLog) { this.enablePrintApiLog = enablePrintApiLog; } + + /** + * get enableOnlySuperAdminPermission. + * + * @return enable + */ + public Boolean getEnableOnlySuperAdminPermission() { + return enableOnlySuperAdminPermission; + } + + /** + * set enableOnlySuperAdminPermission. + * + * @param enableOnlySuperAdminPermission enable + */ + public void setEnableOnlySuperAdminPermission(final Boolean enableOnlySuperAdminPermission) { + this.enableOnlySuperAdminPermission = enableOnlySuperAdminPermission; + } + + /** + * get onlySuperAdminPermission. + * + * @return super admin permission + */ + public List<String> getOnlySuperAdminPermission() { + return onlySuperAdminPermission; + } + + /** + * set onlySuperAdminPermission. + * + * @param onlySuperAdminPermission onlySuperAdminPermission + */ + public void setOnlySuperAdminPermission(final List<String> onlySuperAdminPermission) { + this.onlySuperAdminPermission = onlySuperAdminPermission; + } + + @Override + public void afterPropertiesSet() { + if (!Boolean.TRUE.equals(enableOnlySuperAdminPermission)) { + onlySuperAdminPermission = new ArrayList<>(); + return; + } + if (CollectionUtils.isEmpty(onlySuperAdminPermission)) { + onlySuperAdminPermission = new ArrayList<>(); + // user + onlySuperAdminPermission.add("system:manager:add"); + onlySuperAdminPermission.add("system:manager:edit"); + onlySuperAdminPermission.add("system:manager:delete"); + // role + onlySuperAdminPermission.add("system:role:edit"); + onlySuperAdminPermission.add("system:role:add"); + onlySuperAdminPermission.add("system:role:delete"); + // resource + onlySuperAdminPermission.add("system:resource:addButton"); + onlySuperAdminPermission.add("system:resource:addMenu"); + onlySuperAdminPermission.add("system:resource:editButton"); + onlySuperAdminPermission.add("system:resource:editMenu"); + onlySuperAdminPermission.add("system:resource:deleteMenu"); + onlySuperAdminPermission.add("system:resource:deleteButton"); + } + } }
shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/DashboardUserController.java+5 −2 modified@@ -30,6 +30,8 @@ import org.apache.shenyu.admin.model.vo.DashboardUserEditVO; import org.apache.shenyu.admin.model.vo.DashboardUserVO; import org.apache.shenyu.admin.service.DashboardUserService; +import org.apache.shenyu.admin.utils.Assert; +import org.apache.shenyu.admin.utils.SessionUtil; import org.apache.shenyu.admin.utils.ShenyuResultMessage; import org.apache.shenyu.admin.validation.annotation.Existed; import org.apache.shenyu.common.utils.ShaUtils; @@ -182,7 +184,8 @@ public ShenyuAdminResult modifyPassword(@PathVariable("id") @DeleteMapping("/batch") @RequiresPermissions("system:manager:delete") public ShenyuAdminResult deleteDashboardUser(@RequestBody @NotEmpty final List<@NotBlank String> ids) { - Integer deleteCount = dashboardUserService.delete(new HashSet<>(ids)); - return ShenyuAdminResult.success(ShenyuResultMessage.DELETE_SUCCESS, deleteCount); + // [mandatory] This function can only be used by the admin user + Assert.isTrue(SessionUtil.isAdmin(), "This function can only be used by the admin(root) user"); + return ShenyuAdminResult.success(ShenyuResultMessage.DELETE_SUCCESS, dashboardUserService.delete(new HashSet<>(ids))); } }
shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/ResourceController.java+2 −2 modified@@ -108,7 +108,7 @@ public ShenyuAdminResult getMenuTree() { * @return {@linkplain ShenyuAdminResult} */ @GetMapping("/button") - @RequiresPermissions("system:resource:addButton") + @RequiresPermissions(value = {"system:resource:addButton", "system:resource:list"}, logical = Logical.OR) public ShenyuAdminResult getButton(final String id) { List<ResourceVO> resourceVOList = resourceService.findByParentId(id); if (CollectionUtils.isNotEmpty(resourceVOList)) { @@ -124,7 +124,7 @@ public ShenyuAdminResult getButton(final String id) { * @return {@linkplain ShenyuAdminResult} */ @GetMapping("/{id}") - @RequiresPermissions(value = {"system:resource:editButton", "system:resource:editMenu"}, logical = Logical.OR) + @RequiresPermissions(value = {"system:resource:list", "system:resource:editMenu", "system:resource:list"}, logical = Logical.OR) public ShenyuAdminResult detailResource(@PathVariable("id") final String id) { return Optional.ofNullable(resourceService.findById(id)) .map(item -> ShenyuAdminResult.success(ShenyuResultMessage.DETAIL_SUCCESS, item))
shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/ResourceMapper.java+9 −0 modified@@ -50,6 +50,15 @@ public interface ResourceMapper extends ExistProvider { */ ResourceDO selectById(String id); + /** + * select resource by user name.<br> + * Get all resources with permissions under the specified user. + * + * @param userName user name + * @return {@link ResourceDO} list + */ + List<ResourceDO> selectByUserName(@Param("userName") String userName); + /** * select resource by id batch. *
shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/DashboardUserDTO.java+0 −4 modified@@ -18,7 +18,6 @@ package org.apache.shenyu.admin.model.dto; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import java.io.Serializable; import java.util.List; @@ -45,7 +44,6 @@ public class DashboardUserDTO implements Serializable { /** * user password. */ - @NotBlank private String password; /** @@ -56,8 +54,6 @@ public class DashboardUserDTO implements Serializable { /** * current role list. */ - @NotEmpty - @NotNull private List<@NotBlank String> roles; /**
shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DashboardUserServiceImpl.java+26 −12 modified@@ -17,6 +17,7 @@ package org.apache.shenyu.admin.service.impl; +import com.google.common.collect.Lists; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.shenyu.admin.config.properties.JwtProperties; @@ -43,17 +44,9 @@ import org.apache.shenyu.admin.utils.Assert; import org.apache.shenyu.admin.utils.JwtUtils; import org.apache.shenyu.admin.utils.ListUtil; +import org.apache.shenyu.admin.utils.SessionUtil; import org.apache.shenyu.common.constant.AdminConstants; import org.apache.shenyu.common.utils.ShaUtils; - -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import javax.annotation.Nullable; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ldap.NameNotFoundException; @@ -62,7 +55,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.google.common.collect.Lists; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; /** * Implementation of the {@link org.apache.shenyu.admin.service.DashboardUserService}. @@ -118,6 +116,9 @@ public int createOrUpdate(final DashboardUserDTO dashboardUserDTO) { @Override public int create(final DashboardUserDTO dashboardUserDTO) { + Assert.notBlack(dashboardUserDTO.getPassword(), "password is not null"); + Assert.notEmpty(dashboardUserDTO.getRoles(), "role is not empty"); + Assert.isNull(dashboardUserMapper.selectByUserName(dashboardUserDTO.getUserName()), "the user is existed"); DashboardUserDO dashboardUserDO = DashboardUserDO.buildDashboardUserDO(dashboardUserDTO); // create new user final int insertCount = dashboardUserMapper.insertSelective(dashboardUserDO); @@ -130,7 +131,14 @@ public int create(final DashboardUserDTO dashboardUserDTO) { @Override public int update(final DashboardUserDTO dashboardUserDTO) { + // 【mandatory】This function can only be used by the admin user + Assert.isTrue(SessionUtil.isAdmin(), "This function can only be used by the admin(root) user"); DashboardUserDO dashboardUserDO = DashboardUserDO.buildDashboardUserDO(dashboardUserDTO); + if (Objects.equals(dashboardUserDO.getUserName(), SessionUtil.visitorName())) { + Assert.isTrue(Boolean.TRUE.equals(dashboardUserDO.getEnabled()), "You cannot disable yourself"); + } else { + Assert.isTrue(!Objects.equals(dashboardUserDO.getId(), SessionUtil.visitor().getUserId()), "Super administrator name is not allowed to be modified"); + } // update old user if (CollectionUtils.isNotEmpty(dashboardUserDTO.getRoles())) { if (!AdminConstants.ADMIN_NAME.equals(dashboardUserDTO.getUserName())) { @@ -139,6 +147,9 @@ public int update(final DashboardUserDTO dashboardUserDTO) { bindUserRole(dashboardUserDTO.getId(), dashboardUserDTO.getRoles()); } final DashboardUserDO before = dashboardUserMapper.selectById(dashboardUserDO.getId()); + if (StringUtils.isBlank(dashboardUserDO.getPassword())) { + dashboardUserDO.setPassword(before.getPassword()); + } final int updateCount = dashboardUserMapper.updateSelective(dashboardUserDO); if (updateCount > 0) { publisher.onUpdated(dashboardUserDO, before); @@ -160,6 +171,9 @@ public int delete(final Set<String> ids) { // skip default admin user .filter(u -> !Objects.equals(u.getUserName(), AdminConstants.ADMIN_NAME)) .collect(Collectors.toList()); + if (CollectionUtils.isEmpty(deletedUser)) { + return 0; + } final List<String> deletedIds = ListUtil.map(deletedUser, DashboardUserDO::getId); int deleteCount = dashboardUserMapper.deleteByIdList(deletedIds); if (deleteCount > 0) { @@ -261,7 +275,7 @@ public LoginDashboardUserVO login(final String userName, final String password) jwtProperties.getExpiredSeconds())).setExpiredTime(jwtProperties.getExpiredSeconds()); }).orElse(null); } - + /** * modify password. * @@ -278,7 +292,7 @@ public int modifyPassword(final DashboardUserModifyPasswordDTO dashboardUserModi } return updateCount; } - + private DashboardUserVO loginByLdap(final String userName, final String password) { Assert.notNull(ldapProperties, "ldap config is not enable"); String searchBase = String.format("%s=%s,%s", ldapProperties.getLoginField(), LdapEncoder.nameEncode(userName), ldapProperties.getBaseDn());
shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/PermissionServiceImpl.java+19 −27 modified@@ -18,15 +18,12 @@ package org.apache.shenyu.admin.service.impl; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.shenyu.admin.mapper.DashboardUserMapper; +import org.apache.shenyu.admin.config.properties.DashboardProperties; import org.apache.shenyu.admin.mapper.PermissionMapper; import org.apache.shenyu.admin.mapper.ResourceMapper; -import org.apache.shenyu.admin.mapper.UserRoleMapper; import org.apache.shenyu.admin.model.custom.UserInfo; import org.apache.shenyu.admin.model.dto.PermissionDTO; import org.apache.shenyu.admin.model.entity.PermissionDO; -import org.apache.shenyu.admin.model.entity.UserRoleDO; import org.apache.shenyu.admin.model.event.resource.BatchResourceCreatedEvent; import org.apache.shenyu.admin.model.event.resource.BatchResourceDeletedEvent; import org.apache.shenyu.admin.model.event.resource.ResourceCreatedEvent; @@ -40,6 +37,7 @@ import org.apache.shenyu.admin.utils.JwtUtils; import org.apache.shenyu.admin.utils.ListUtil; import org.apache.shenyu.admin.utils.ResourceUtil; +import org.apache.shenyu.admin.utils.SessionUtil; import org.apache.shenyu.common.constant.AdminConstants; import org.apache.shenyu.common.constant.ResourceTypeConstants; import org.springframework.context.event.EventListener; @@ -58,22 +56,18 @@ @Service public class PermissionServiceImpl implements PermissionService { - private final DashboardUserMapper dashboardUserMapper; - - private final UserRoleMapper userRoleMapper; - private final PermissionMapper permissionMapper; private final ResourceMapper resourceMapper; - public PermissionServiceImpl(final DashboardUserMapper dashboardUserMapper, - final UserRoleMapper userRoleMapper, - final PermissionMapper permissionMapper, - final ResourceMapper resourceMapper) { - this.dashboardUserMapper = dashboardUserMapper; - this.userRoleMapper = userRoleMapper; + private final DashboardProperties dashboardProperties; + + public PermissionServiceImpl(final PermissionMapper permissionMapper, + final ResourceMapper resourceMapper, + final DashboardProperties dashboardProperties) { this.permissionMapper = permissionMapper; this.resourceMapper = resourceMapper; + this.dashboardProperties = dashboardProperties; } /** @@ -93,7 +87,6 @@ public PermissionMenuVO getPermissionMenu(final String token) { if (CollectionUtils.isEmpty(resourceVOList)) { return null; } - return new PermissionMenuVO(ResourceUtil.buildMenu(resourceVOList), getAuthPerm(resourceVOList), getAllAuthPerms()); } @@ -107,7 +100,9 @@ public PermissionMenuVO getPermissionMenu(final String token) { public Set<String> getAuthPermByUserName(final String userName) { List<ResourceVO> resourceVOList = getResourceListByUserName(userName); if (CollectionUtils.isNotEmpty(resourceVOList)) { - return getAuthPerm(resourceVOList).stream().map(AuthPerm::getPerms).collect(Collectors.toSet()); + return getAuthPerm(resourceVOList).stream() + .map(AuthPerm::getPerms) + .collect(Collectors.toSet()); } return Collections.emptySet(); } @@ -170,18 +165,15 @@ public void onRoleUpdated(final RoleUpdatedEvent event) { * @return {@linkplain List} */ private List<ResourceVO> getResourceListByUserName(final String userName) { - List<UserRoleDO> userRoles = userRoleMapper.findByUserId(dashboardUserMapper.selectByUserName(userName).getId()); - Set<String> resourceIds = permissionMapper.findByObjectIds(ListUtil.map(userRoles, UserRoleDO::getRoleId)) - .stream() - .map(PermissionDO::getResourceId) - .filter(StringUtils::isNoneBlank) - .collect(Collectors.toSet()); - - if (CollectionUtils.isEmpty(resourceIds)) { - return Collections.emptyList(); + if (SessionUtil.isAdmin()) { + return ListUtil.map(resourceMapper.selectByUserName(userName), ResourceVO::buildResourceVO); } - - return ListUtil.map(resourceMapper.selectByIdsBatch(resourceIds), ResourceVO::buildResourceVO); + // filter [Only the super administrator root user has the privileges] + return resourceMapper.selectByUserName(userName) + .stream() + .filter(r -> !dashboardProperties.getOnlySuperAdminPermission().contains(r.getPerms())) + .map(ResourceVO::buildResourceVO) + .collect(Collectors.toList()); } private PermissionDO buildPermissionFromResourceId(final String resourceId) {
shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ResourceServiceImpl.java+12 −2 modified@@ -19,6 +19,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.shenyu.admin.aspect.annotation.Pageable; +import org.apache.shenyu.admin.config.properties.DashboardProperties; import org.apache.shenyu.admin.mapper.ResourceMapper; import org.apache.shenyu.admin.model.dto.CreateResourceDTO; import org.apache.shenyu.admin.model.dto.ResourceDTO; @@ -54,10 +55,14 @@ public class ResourceServiceImpl implements ResourceService { private final ResourceEventPublisher publisher; + private final DashboardProperties properties; + public ResourceServiceImpl(final ResourceMapper resourceMapper, - final ResourceEventPublisher publisher) { + final ResourceEventPublisher publisher, + final DashboardProperties properties) { this.resourceMapper = resourceMapper; this.publisher = publisher; + this.properties = properties; } /** @@ -172,7 +177,12 @@ public CommonPager<ResourceVO> listByPage(final ResourceQuery resourceQuery) { */ @Override public List<MenuInfo> getMenuTree() { - List<ResourceVO> resourceVOList = ListUtil.map(resourceMapper.selectAll(), ResourceVO::buildResourceVO); + // Hide super administrator special privileges + List<ResourceVO> resourceVOList = resourceMapper.selectAll() + .stream() + .filter(r -> !properties.getOnlySuperAdminPermission().contains(r.getPerms())) + .map(ResourceVO::buildResourceVO) + .collect(Collectors.toList()); return CollectionUtils.isEmpty(resourceVOList) ? null : ResourceUtil.buildMenu(resourceVOList); }
shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/RoleServiceImpl.java+10 −1 modified@@ -20,6 +20,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.shenyu.admin.aspect.annotation.Pageable; +import org.apache.shenyu.admin.config.properties.DashboardProperties; import org.apache.shenyu.admin.mapper.PermissionMapper; import org.apache.shenyu.admin.mapper.ResourceMapper; import org.apache.shenyu.admin.mapper.RoleMapper; @@ -61,17 +62,21 @@ public class RoleServiceImpl implements RoleService { private final RoleMapper roleMapper; + private final DashboardProperties properties; + private final PermissionMapper permissionMapper; private final ResourceMapper resourceMapper; private final RoleEventPublisher roleEventPublisher; public RoleServiceImpl(final RoleMapper roleMapper, + final DashboardProperties properties, final PermissionMapper permissionMapper, final ResourceMapper resourceMapper, final RoleEventPublisher roleEventPublisher) { this.roleMapper = roleMapper; + this.properties = properties; this.permissionMapper = permissionMapper; this.resourceMapper = resourceMapper; this.roleEventPublisher = roleEventPublisher; @@ -183,7 +188,11 @@ public List<RoleVO> selectAll() { * @return {@linkplain PermissionInfo} */ private PermissionInfo getAllPermissions() { - final List<ResourceVO> resourceVOList = ListUtil.map(resourceMapper.selectAll(), ResourceVO::buildResourceVO); + List<ResourceVO> resourceVOList = resourceMapper.selectAll() + .stream() + .filter(r -> !properties.getOnlySuperAdminPermission().contains(r.getPerms())) + .map(ResourceVO::buildResourceVO) + .collect(Collectors.toList()); return PermissionInfo.builder() .treeList(getTreeModelList(resourceVOList)) .permissionIds(ListUtil.map(resourceVOList, ResourceVO::getId))
shenyu-admin/src/main/resources/application.yml+15 −2 modified@@ -98,8 +98,21 @@ shenyu: - /csrf swagger: enable: true - -logging: + dashboard: + core: + onlySuperAdminPermission: + - system:manager:add + - system:manager:edit + - system:manager:delete + - system:role:add + - system:role:edit + - system:role:delete + - system:resource:addButton + - system:resource:addMenu + - system:resource:editButton + - system:resource:editMenu + - system:resource:deleteButton + - system:resource:deleteMenu level: root: info org.springframework.boot: info
shenyu-admin/src/main/resources/mappers/dashboard-user-sqlmap.xml+1 −0 modified@@ -50,6 +50,7 @@ <include refid="Base_Column_List"/> FROM dashboard_user WHERE user_name = #{userName, jdbcType=VARCHAR} + limit 1 </select> <select id="findByQuery" resultMap="BaseResultMap">
shenyu-admin/src/main/resources/mappers/resouce-sqlmap.xml+16 −0 modified@@ -139,6 +139,22 @@ select true from resource where id = #{id} limit 1 </select> + <select id="selectByUserName" resultType="org.apache.shenyu.admin.model.entity.ResourceDO"> + select + <include refid="Base_Column_List"/> + from resource r + where exists(select 1 + from permission p + where r.id = p.resource_id + and object_id in (select role_id + from user_role r + where exists(select 1 + from dashboard_user u + where u.id = r.user_id + and u.user_name = #{userName})) + ) + </select> + <insert id="insert" parameterType="org.apache.shenyu.admin.model.entity.ResourceDO"> INSERT INTO resource (id,
shenyu-admin/src/test/java/org/apache/shenyu/admin/controller/DashboardUserControllerTest.java+3 −0 modified@@ -17,13 +17,15 @@ package org.apache.shenyu.admin.controller; +import org.apache.shenyu.admin.model.custom.UserInfo; import org.apache.shenyu.admin.model.dto.DashboardUserDTO; import org.apache.shenyu.admin.model.page.CommonPager; import org.apache.shenyu.admin.model.page.PageParameter; import org.apache.shenyu.admin.model.vo.DashboardUserEditVO; import org.apache.shenyu.admin.model.vo.DashboardUserVO; import org.apache.shenyu.admin.model.vo.RoleVO; import org.apache.shenyu.admin.service.DashboardUserService; +import org.apache.shenyu.admin.utils.SessionUtil; import org.apache.shenyu.admin.utils.ShenyuResultMessage; import org.apache.shenyu.common.utils.GsonUtils; import org.assertj.core.util.Lists; @@ -152,6 +154,7 @@ public void updateDashboardUser() throws Exception { public void deleteDashboardUser() throws Exception { final String url = "/dashboardUser/batch"; final List<String> ids = Lists.newArrayList(); + SessionUtil.setLocalVisitor(UserInfo.builder().userId("1").userName("admin").build()); given(dashboardUserService.delete(any())).willReturn(0); mockMvc.perform(delete(url, ids) .content(GsonUtils.getInstance().toJson(ids))
shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DashboardUserServiceTest.java+3 −0 modified@@ -22,6 +22,7 @@ import org.apache.shenyu.admin.mapper.DashboardUserMapper; import org.apache.shenyu.admin.mapper.RoleMapper; import org.apache.shenyu.admin.mapper.UserRoleMapper; +import org.apache.shenyu.admin.model.custom.UserInfo; import org.apache.shenyu.admin.model.dto.DashboardUserDTO; import org.apache.shenyu.admin.model.dto.RoleDTO; import org.apache.shenyu.admin.model.entity.DashboardUserDO; @@ -34,6 +35,7 @@ import org.apache.shenyu.admin.service.impl.DashboardUserServiceImpl; import org.apache.shenyu.admin.service.publish.UserEventPublisher; import org.apache.shenyu.admin.utils.ListUtil; +import org.apache.shenyu.admin.utils.SessionUtil; import org.apache.shenyu.common.utils.ShaUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -95,6 +97,7 @@ public final class DashboardUserServiceTest { @Test public void testCreateOrUpdate() { + SessionUtil.setLocalVisitor(UserInfo.builder().userId("1").userName("admin").build()); DashboardUserDTO dashboardUserDTO = DashboardUserDTO.builder() .userName(TEST_USER_NAME).password(TEST_PASSWORD).roles(Collections.singletonList("1")) .build();
shenyu-admin/src/test/java/org/apache/shenyu/admin/service/PermissionServiceTest.java+15 −11 modified@@ -17,6 +17,7 @@ package org.apache.shenyu.admin.service; +import org.apache.shenyu.admin.config.properties.DashboardProperties; import org.apache.shenyu.admin.mapper.DashboardUserMapper; import org.apache.shenyu.admin.mapper.PermissionMapper; import org.apache.shenyu.admin.mapper.ResourceMapper; @@ -64,24 +65,27 @@ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) public final class PermissionServiceTest { - + @Mock private DashboardUserMapper mockDashboardUserMapper; - + @Mock private UserRoleMapper mockUserRoleMapper; - + @Mock private PermissionMapper mockPermissionMapper; - + @Mock private ResourceMapper mockResourceMapper; - + private PermissionServiceImpl permissionServiceImplUnderTest; - + @Mock private org.apache.shiro.mgt.SecurityManager securityManager; - + + @Mock + private DashboardProperties dashboardProperties; + @BeforeEach public void setUp() throws Exception { SecurityUtils.setSecurityManager(securityManager); @@ -124,11 +128,11 @@ public void setUp() throws Exception { // when(mockResourceMapper.selectById("1346777157943259136")).thenReturn(resourceDO3); // when(mockResourceMapper.selectById("1347053375029653504")).thenReturn(resourceDO4); // when(mockResourceMapper.selectAll()).thenReturn(Arrays.asList(resourceDO1, resourceDO2, resourceDO3, resourceDO4)); - when(mockResourceMapper.selectByIdsBatch(resourceIds)).thenReturn(Arrays.asList(resourceDO2, resourceDO3, resourceDO1, resourceDO4)); + when(mockResourceMapper.selectByUserName("admin")).thenReturn(Arrays.asList(resourceDO2, resourceDO3, resourceDO1, resourceDO4)); when(mockResourceMapper.selectByResourceType(ResourceTypeConstants.MENU_TYPE_2)).thenReturn(Collections.singletonList(resourceDO4)); - permissionServiceImplUnderTest = new PermissionServiceImpl(mockDashboardUserMapper, mockUserRoleMapper, mockPermissionMapper, mockResourceMapper); + permissionServiceImplUnderTest = new PermissionServiceImpl(mockPermissionMapper, mockResourceMapper, dashboardProperties); } - + @Test public void testGetPermissionMenu() { try (MockedStatic<JwtUtils> mocked = mockStatic(JwtUtils.class)) { @@ -150,7 +154,7 @@ public void testGetPermissionMenu() { assertThat(result, is(expectedResult)); } } - + @Test public void testGetAuthPermByUserName() { final Set<String> result = permissionServiceImplUnderTest.getAuthPermByUserName("admin");
shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ResourceServiceTest.java+5 −1 modified@@ -27,6 +27,7 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; +import org.apache.shenyu.admin.config.properties.DashboardProperties; import org.apache.shenyu.admin.mapper.PermissionMapper; import org.apache.shenyu.admin.mapper.ResourceMapper; import org.apache.shenyu.admin.model.dto.CreateResourceDTO; @@ -76,6 +77,9 @@ public class ResourceServiceTest { @Mock private ResourceEventPublisher publisher; + @Mock + private DashboardProperties properties; + @Test public void testCreateResourceBatch() { @@ -223,7 +227,7 @@ public void testGetMenuTreeWhenThereAreResources() { reset(resourceMapper); when(resourceMapper.selectAll()).thenReturn(mockSelectAllResult); - + List<PermissionMenuVO.MenuInfo> menuInfoList = ResourceUtil.buildMenu(mockSelectAllResult.stream().map(ResourceVO::buildResourceVO).collect(Collectors.toList())); assertThat(resourceService.getMenuTree(), equalTo(menuInfoList)); }
Vulnerability mechanics
Root cause
"Missing privilege escalation checks allow low-privilege ShenYu Admin users to create, modify, or delete users and roles with higher privileges than their own."
Attack vector
An attacker with a low-privilege ShenYu Admin dashboard account can send crafted HTTP requests to the Admin API endpoints for user/role/resource management. Because the service previously did not verify whether the caller was the super administrator (root) before performing sensitive operations, a low-level administrator could create new users with elevated roles, modify existing users, or delete users. The patch introduces checks via `SessionUtil.isAdmin()` and a configurable `onlySuperAdminPermission` list that restricts certain permissions (e.g., `system:manager:add`, `system:role:edit`) exclusively to the root user [patch_id=1641240].
Affected code
The vulnerability spans multiple service and controller files in `shenyu-admin`. The core logic resides in `PermissionServiceImpl.getResourceListByUserName()` which previously queried permissions via `userRoleMapper` and `permissionMapper` without any admin check. `DashboardUserServiceImpl.create()`, `update()`, and `delete()` lacked privilege verification. `DashboardUserController.deleteDashboardUser()` also lacked an admin check. The patch introduces filtering in `ResourceServiceImpl.getMenuTree()` and `RoleServiceImpl.getAllPermissions()` to hide super-admin-only resources from non-root users [patch_id=1641240].
What the fix does
The patch adds a `DashboardProperties` class that implements `InitializingBean` and defines an `onlySuperAdminPermission` list of permission strings (e.g., `system:manager:add`, `system:role:edit`) that are reserved for the super administrator [patch_id=1641240]. In `PermissionServiceImpl.getResourceListByUserName()`, resources whose `perms` match this list are filtered out for non-admin users. In `DashboardUserServiceImpl.update()` and `DashboardUserController.deleteDashboardUser()`, explicit `SessionUtil.isAdmin()` assertions are added to prevent non-root users from modifying or deleting accounts. The `ResourceServiceImpl.getMenuTree()` and `RoleServiceImpl.getAllPermissions()` also filter out super-admin-only resources from the menu tree and permission list, hiding them from lower-privilege users.
Preconditions
- authAttacker must have a valid low-privilege ShenYu Admin dashboard account.
- networkAttacker must have network access to the ShenYu Admin API endpoints.
Generated on May 23, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-vf8h-2wwj-jq22ghsaADVISORY
- lists.apache.org/thread/2k8764jmckmc19qc8x51nlnngq71pcf7ghsavendor-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2022-42735ghsaADVISORY
- github.com/apache/shenyu/pull/3958ghsaWEB
News mentions
0No linked articles in our index yet.