VYPR
Critical severityNVD Advisory· Published Nov 16, 2021· Updated Aug 4, 2024

Apache ShenYu Admin bypass JWT authentication

CVE-2021-37580

Description

Apache ShenYu Admin 2.3.0 and 2.4.0 use a hardcoded JWT key, enabling attackers to forge arbitrary tokens and bypass authentication.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Apache ShenYu Admin 2.3.0 and 2.4.0 use a hardcoded JWT key, enabling attackers to forge arbitrary tokens and bypass authentication.

Vulnerability

Apache ShenYu Admin versions 2.3.0 and 2.4.0 contained a flaw in JWT handling. The JwtProperties class used a hardcoded secret key (private String key;) without loading a configurable value, and tokens were generated directly from the username without proper signature validation [1][3]. This allowed anyone with knowledge of the hardcoded key to forge valid admin tokens.

Exploitation

An attacker with network access to the ShenYu Admin dashboard could forge a JWT by using the known hardcoded key (e.g., common test keys like shenyu or a default value). The exploit does not require authentication or user interaction; simply sending a crafted JWT in the Authorization header to the admin API would be accepted as valid [1][3]. The official fix removed the hardcoded key and added an expiredSeconds property, also stopping token generation at login without proper key management [1].

Impact

Successful exploitation allows an attacker to bypass authentication entirely, gaining administrative access to the ShenYu Admin dashboard. This could lead to full control over API gateway configurations, including the ability to modify routes, plugins, and user permissions – resulting in potential disclosure of sensitive data, service disruption, or further compromise of downstream systems [3].

Mitigation

Apache ShenYu released version 2.4.1 on 2021-11-16, which removes the hardcoded JWT key and enforces proper token configuration [1][4]. Users should upgrade to 2.4.1 or later. No known workaround is available for versions 2.3.0 and 2.4.0. The vulnerability is not listed on CISA’s KEV.

AI Insight generated on May 21, 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.

PackageAffected versionsPatched versions
org.apache.shenyu:shenyu-adminMaven
>= 2.3.0, < 2.4.12.4.1

Affected products

2

Patches

1
f78adb26926b

Improve token security (#1802)

https://github.com/apache/shenyudenglimingJul 21, 2021via ghsa
9 files changed · +54 82
  • shenyu-admin/src/main/java/org/apache/shenyu/admin/config/properties/JwtProperties.java+1 1 modified
    @@ -29,5 +29,5 @@
     @ConfigurationProperties(prefix = "shenyu.jwt")
     public class JwtProperties {
     
    -    private String key;
    +    private Long expiredSeconds = 24 * 60 * 60 * 1000L;
     }
    
  • shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/LoginDashboardUserVO.java+2 2 modified
    @@ -20,14 +20,15 @@
     import lombok.Data;
     import lombok.EqualsAndHashCode;
     import lombok.NoArgsConstructor;
    -import org.apache.shenyu.admin.utils.JwtUtils;
    +import lombok.experimental.Accessors;
     import org.springframework.beans.BeanUtils;
     
     import java.util.Optional;
     
     /**
      * login dashboard return user info's vo.
      */
    +@Accessors(chain = true)
     @Data
     @NoArgsConstructor
     @EqualsAndHashCode(callSuper = true)
    @@ -51,7 +52,6 @@ public static LoginDashboardUserVO buildLoginDashboardUserVO(final DashboardUser
                     .map(item -> {
                         LoginDashboardUserVO vo = new LoginDashboardUserVO();
                         BeanUtils.copyProperties(item, vo);
    -                    vo.setToken(JwtUtils.generateToken(vo.getUserName()));
                         return vo;
                     }).orElse(null);
         }
    
  • shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DashboardUserServiceImpl.java+7 1 modified
    @@ -22,6 +22,7 @@
     import lombok.extern.slf4j.Slf4j;
     import org.apache.commons.collections4.CollectionUtils;
     import org.apache.commons.lang3.StringUtils;
    +import org.apache.shenyu.admin.config.properties.JwtProperties;
     import org.apache.shenyu.admin.config.properties.LdapProperties;
     import org.apache.shenyu.admin.config.properties.SecretProperties;
     import org.apache.shenyu.admin.mapper.DashboardUserMapper;
    @@ -42,6 +43,7 @@
     import org.apache.shenyu.admin.model.vo.RoleVO;
     import org.apache.shenyu.admin.service.DashboardUserService;
     import org.apache.shenyu.admin.utils.AesUtils;
    +import org.apache.shenyu.admin.utils.JwtUtils;
     import org.apache.shenyu.common.constant.AdminConstants;
     import org.springframework.beans.BeanUtils;
     import org.springframework.ldap.NameNotFoundException;
    @@ -79,6 +81,8 @@ public class DashboardUserServiceImpl implements DashboardUserService {
         @Nullable
         private final LdapTemplate ldapTemplate;
     
    +    private final JwtProperties jwtProperties;
    +
         /**
          * create or update dashboard user.
          *
    @@ -193,7 +197,9 @@ public LoginDashboardUserVO login(final String userName, final String password)
             if (Objects.isNull(dashboardUserVO)) {
                 dashboardUserVO = loginByDatabase(userName, password);
             }
    -        return LoginDashboardUserVO.buildLoginDashboardUserVO(dashboardUserVO);
    +        return LoginDashboardUserVO.buildLoginDashboardUserVO(dashboardUserVO)
    +                .setToken(JwtUtils.generateToken(dashboardUserVO.getUserName(), dashboardUserVO.getPassword(),
    +                        jwtProperties.getExpiredSeconds()));
         }
     
         private DashboardUserVO loginByLdap(final String userName, final String password) {
    
  • shenyu-admin/src/main/java/org/apache/shenyu/admin/shiro/config/ShiroRealm.java+8 20 modified
    @@ -17,6 +17,7 @@
     
     package org.apache.shenyu.admin.shiro.config;
     
    +import lombok.RequiredArgsConstructor;
     import org.apache.commons.collections4.CollectionUtils;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.shenyu.admin.model.custom.UserInfo;
    @@ -40,18 +41,14 @@
     /**
      * shiro custom's realm.
      */
    +@RequiredArgsConstructor
     @Service("shiroRealm")
     public class ShiroRealm extends AuthorizingRealm {
     
         private final PermissionService permissionService;
     
         private final DashboardUserService dashboardUserService;
     
    -    public ShiroRealm(final PermissionService permissionService, final DashboardUserService dashboardUserService) {
    -        this.permissionService = permissionService;
    -        this.dashboardUserService = dashboardUserService;
    -    }
    -
         @Override
         public boolean supports(final AuthenticationToken token) {
             return token instanceof StatelessToken;
    @@ -77,19 +74,6 @@ protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken a
                 return null;
             }
     
    -        UserInfo userInfo = getUserInfoByToken(token);
    -
    -        return new SimpleAuthenticationInfo(userInfo, token, this.getName());
    -    }
    -
    -    /**
    -     * check token valid.
    -     *
    -     * @param token user token
    -     * @return userInfo {@link UserInfo}
    -     */
    -    private UserInfo getUserInfoByToken(final String token) {
    -
             String userName = JwtUtils.getIssuer(token);
             if (StringUtils.isEmpty(userName)) {
                 throw new AuthenticationException("userName is null");
    @@ -100,9 +84,13 @@ private UserInfo getUserInfoByToken(final String token) {
                 throw new AuthenticationException(String.format("userName(%s) can not be found.", userName));
             }
     
    -        return UserInfo.builder()
    +        if (!JwtUtils.verifyToken(token, dashboardUserVO.getPassword())) {
    +            throw new AuthenticationException("token is error.");
    +        }
    +
    +        return new SimpleAuthenticationInfo(UserInfo.builder()
                     .userName(userName)
                     .userId(dashboardUserVO.getId())
    -                .build();
    +                .build(), token, this.getName());
         }
     }
    
  • shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/JwtUtils.java+20 28 modified
    @@ -26,14 +26,10 @@
     import lombok.Data;
     import lombok.experimental.UtilityClass;
     import lombok.extern.slf4j.Slf4j;
    -import org.apache.shenyu.admin.config.properties.JwtProperties;
     import org.apache.shenyu.admin.model.custom.UserInfo;
    -import org.apache.shenyu.admin.spring.SpringBeanUtils;
     import org.apache.shiro.SecurityUtils;
     import org.apache.shiro.util.StringUtils;
     
    -import java.time.LocalDate;
    -import java.time.ZoneId;
     import java.util.Date;
     import java.util.Optional;
     
    @@ -45,6 +41,8 @@
     @Data
     public final class JwtUtils {
     
    +    private static final long TOKEN_EXPIRE_SECONDS = 24 * 60 * 60 * 1000L;
    +
         /**
          * according to token to get isUserInfo.
          *
    @@ -66,50 +64,44 @@ public static String getIssuer(final String token) {
         }
     
         /**
    -     * according to token to get issuer date.
    +     * generate jwt token.
          *
    -     * @param token token
    -     * @return issuer date {@link LocalDate}
    +     * @param userName login's userName
    +     * @param key secretKey
    +     * @return token
          */
    -    public static LocalDate getIssuerDate(final String token) {
    -        DecodedJWT jwt = verifierToken(token);
    -        if (jwt == null) {
    -            return null;
    -        }
    -        Date date = jwt.getIssuedAt();
    -        return Optional.ofNullable(date)
    -                .map(it -> it.toInstant().atZone(ZoneId.systemDefault()).toLocalDate())
    -                .orElse(null);
    +    public static String generateToken(final String userName, final String key) {
    +        return generateToken(userName, key, null);
         }
     
         /**
          * generate jwt token.
          *
          * @param userName login's userName
    +     * @param key secretKey
    +     * @param expireSeconds expireSeconds
          * @return token
          */
    -    public static String generateToken(final String userName) {
    +    public static String generateToken(final String userName, final String key, final Long expireSeconds) {
             try {
    -            return JWT.create().withClaim("userName", userName).withExpiresAt(new Date()).sign(generateAlgorithm());
    +            return JWT.create()
    +                    .withClaim("userName", userName)
    +                    .withExpiresAt(new Date(System.currentTimeMillis() + Optional.ofNullable(expireSeconds).orElse(TOKEN_EXPIRE_SECONDS)))
    +                    .sign(Algorithm.HMAC256(key));
             } catch (IllegalArgumentException | JWTCreationException e) {
                 log.error("JWTToken generate fail ", e);
             }
             return StringUtils.EMPTY_STRING;
         }
     
    -    private static DecodedJWT verifierToken(final String token) {
    -        DecodedJWT jwt = null;
    +    public static boolean verifyToken(final String token, final String key) {
             try {
    -            JWTVerifier verifier = JWT.require(generateAlgorithm()).build();
    -            jwt = verifier.verify(token);
    +            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(key)).build();
    +            verifier.verify(token);
    +            return true;
             } catch (JWTVerificationException e) {
                 log.info("jwt decode fail, token: {} ", token, e);
             }
    -        return jwt;
    -    }
    -
    -    private static Algorithm generateAlgorithm() {
    -        JwtProperties properties = SpringBeanUtils.getInstance().getBean(JwtProperties.class);
    -        return Algorithm.HMAC256(properties.getKey());
    +        return false;
         }
     }
    
  • shenyu-admin/src/main/resources/application.yml+1 1 modified
    @@ -88,7 +88,7 @@ shenyu:
         object-class: person
         login-field: cn
       jwt:
    -    key: 2095132720951327
    +    expired-seconds: 86400000
       shiro:
         white-list:
           - /
    
  • shenyu-admin/src/test/java/org/apache/shenyu/admin/controller/PlatformControllerTest.java+1 12 modified
    @@ -17,10 +17,8 @@
     
     package org.apache.shenyu.admin.controller;
     
    -import org.apache.shenyu.admin.config.properties.JwtProperties;
     import org.apache.shenyu.admin.service.DashboardUserService;
     import org.apache.shenyu.admin.service.EnumService;
    -import org.apache.shenyu.admin.spring.SpringBeanUtils;
     import org.apache.shenyu.admin.utils.ShenyuResultMessage;
     import org.apache.shenyu.admin.model.vo.DashboardUserVO;
     import org.apache.shenyu.admin.model.vo.LoginDashboardUserVO;
    @@ -32,7 +30,6 @@
     import org.mockito.InjectMocks;
     import org.mockito.Mock;
     import org.mockito.junit.MockitoJUnitRunner;
    -import org.springframework.context.ConfigurableApplicationContext;
     import org.springframework.http.HttpMethod;
     import org.springframework.test.web.servlet.MockMvc;
     import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
    @@ -43,8 +40,6 @@
     import static org.hamcrest.core.Is.is;
     import static org.mockito.ArgumentMatchers.eq;
     import static org.mockito.BDDMockito.given;
    -import static org.mockito.Mockito.mock;
    -import static org.mockito.Mockito.when;
     import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
     import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
     
    @@ -69,7 +64,7 @@ public final class PlatformControllerTest {
         /**
          * dashboardUser mock data.
          */
    -    private final DashboardUserVO dashboardUserVO = new DashboardUserVO("1", "admin", "123456",
    +    private final DashboardUserVO dashboardUserVO = new DashboardUserVO("1", "admin", "2095132720951327",
                 1, true, DateUtils.localDateTimeToString(LocalDateTime.now()),
                 DateUtils.localDateTimeToString(LocalDateTime.now()));
     
    @@ -86,12 +81,6 @@ public void setUp() {
          */
         @Test
         public void testLoginDashboardUser() throws Exception {
    -        ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
    -        SpringBeanUtils.getInstance().setCfgContext(context);
    -        JwtProperties jwtProperties = new JwtProperties();
    -        jwtProperties.setKey("2095132720951327");
    -        when(context.getBean(JwtProperties.class)).thenReturn(jwtProperties);
    -
             final String loginUri = "/platform/login?userName=admin&password=123456";
     
             LoginDashboardUserVO loginDashboardUserVO = LoginDashboardUserVO.buildLoginDashboardUserVO(dashboardUserVO);
    
  • shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DashboardUserServiceTest.java+8 13 modified
    @@ -19,24 +19,22 @@
     
     import org.apache.shenyu.admin.config.properties.JwtProperties;
     import org.apache.shenyu.admin.config.properties.SecretProperties;
    -import org.apache.shenyu.admin.mapper.DataPermissionMapper;
    -import org.apache.shenyu.admin.model.dto.DashboardUserDTO;
    -import org.apache.shenyu.admin.model.entity.DashboardUserDO;
     import org.apache.shenyu.admin.mapper.DashboardUserMapper;
    +import org.apache.shenyu.admin.mapper.DataPermissionMapper;
     import org.apache.shenyu.admin.mapper.RoleMapper;
     import org.apache.shenyu.admin.mapper.UserRoleMapper;
    +import org.apache.shenyu.admin.model.dto.DashboardUserDTO;
    +import org.apache.shenyu.admin.model.entity.DashboardUserDO;
     import org.apache.shenyu.admin.model.page.CommonPager;
     import org.apache.shenyu.admin.model.page.PageParameter;
     import org.apache.shenyu.admin.model.query.DashboardUserQuery;
    -import org.apache.shenyu.admin.service.impl.DashboardUserServiceImpl;
    -import org.apache.shenyu.admin.spring.SpringBeanUtils;
     import org.apache.shenyu.admin.model.vo.DashboardUserVO;
    +import org.apache.shenyu.admin.service.impl.DashboardUserServiceImpl;
     import org.junit.Test;
     import org.junit.runner.RunWith;
     import org.mockito.InjectMocks;
     import org.mockito.Mock;
     import org.mockito.junit.MockitoJUnitRunner;
    -import org.springframework.context.ConfigurableApplicationContext;
     import org.springframework.test.util.ReflectionTestUtils;
     
     import java.sql.Timestamp;
    @@ -49,7 +47,6 @@
     import static org.mockito.ArgumentMatchers.anyString;
     import static org.mockito.ArgumentMatchers.eq;
     import static org.mockito.BDDMockito.given;
    -import static org.mockito.Mockito.mock;
     import static org.mockito.Mockito.never;
     import static org.mockito.Mockito.times;
     import static org.mockito.Mockito.verify;
    @@ -84,6 +81,9 @@ public final class DashboardUserServiceTest {
         @Mock
         private SecretProperties secretProperties;
     
    +    @Mock
    +    private JwtProperties jwtProperties;
    +
         @Test
         public void testCreateOrUpdate() {
             DashboardUserDTO dashboardUserDTO = DashboardUserDTO.builder()
    @@ -161,13 +161,8 @@ public void testListByPage() {
     
         @Test
         public void testLogin() {
    -        ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
    -        SpringBeanUtils.getInstance().setCfgContext(context);
    -        JwtProperties jwtProperties = mock(JwtProperties.class);
    -        when(jwtProperties.getKey()).thenReturn("test");
    -        when(context.getBean(JwtProperties.class)).thenReturn(jwtProperties);
    -
             ReflectionTestUtils.setField(dashboardUserService, "secretProperties", secretProperties);
    +        ReflectionTestUtils.setField(dashboardUserService, "jwtProperties", jwtProperties);
             DashboardUserDO dashboardUserDO = createDashboardUserDO();
             String key = "2095132720951327";
             String iv = "6075877187097700";
    
  • shenyu-admin/src/test/java/org/apache/shenyu/admin/utils/JwtUtilsTest.java+6 4 modified
    @@ -24,7 +24,6 @@
     import org.springframework.context.ConfigurableApplicationContext;
     import static org.hamcrest.Matchers.is;
     import static org.hamcrest.Matchers.notNullValue;
    -import static org.junit.Assert.assertNull;
     import static org.junit.Assert.assertThat;
     import static org.mockito.Mockito.mock;
     import static org.mockito.Mockito.when;
    @@ -36,11 +35,15 @@ public class JwtUtilsTest {
     
         public static final String TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ1c2VyTmFtZSIsImlhdCI6MTYxMTU5MDUwOH0.yAuGpmg1DSYNryZQQA6d66HO87E8eWAFLJVhYscx8K8";
     
    +    private static final Long EXPIRED_SECONDS = 86400L;
    +
    +    private static final String KEY = "jwt-token";
    +
         @Before
         public void setUp() {
             ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
             JwtProperties jwtProperties = mock(JwtProperties.class);
    -        when(jwtProperties.getKey()).thenReturn("jwt-key");
    +        when(jwtProperties.getExpiredSeconds()).thenReturn(EXPIRED_SECONDS);
             when(context.getBean(JwtProperties.class)).thenReturn(jwtProperties);
             SpringBeanUtils.getInstance().setCfgContext(context);
         }
    @@ -52,9 +55,8 @@ public void testGetIssuer() {
     
         @Test
         public void testGenerateToken() {
    -        String token = JwtUtils.generateToken("userName");
    +        String token = JwtUtils.generateToken("userName", KEY);
             assertThat(token, notNullValue());
    -        assertNull(JwtUtils.getIssuerDate(token));
             assertThat(JwtUtils.getIssuer(token), is("userName"));
         }
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.