Apache ShenYu Admin JWT认证绕过漏洞(CVE-2021-37580)
0x01 漏洞简介
Apache ShenYu(原名 Soul)是一个异步的、跨语言的、多协议的高性能响应式API 网关,并可应用于所有微服务场景。
2021 年 11 月 16日,Apache发布安全公告,公开了Apache ShenYu中的一个身份验证绕过漏洞(CVE-2021-37580),该漏洞的CVSS评分为9.8。由于ShenyuAdminBootstrap中JWT的错误使用,导致攻击者可以绕过身份验证,直接进入目标系统后台。
0x02 影响版本
2.3.0 <= Apache ShenYu <= 2.4.0
0x03 环境搭建
docker下载运行环境:docker run -d -P vulfocus/apache_shenyu-ce_2021_37580
0x04 漏洞分析
下载2.4.0源码:https://github.com/apache/shenyu/archive/refs/tags/v2.4.0.zip
使用开发工具启动 org.apache.shenyu.admin.ShenyuAdminBootstrap
,访问 http://localhost:9095 , 默认用户名和密码分别为: admin
和 123456
。
漏洞为JWT认证问题,当HTTP请求中包含X-Access-Token
时,调用StatelessAuthFilter进行token校验,认证的框架是Apache Shiro。
private static final String HEAD_TOKEN = "X-Access-Token";
@Override
protected boolean onAccessDenied(final ServletRequest servletRequest, final ServletResponse servletResponse)
throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
if (StringUtils.equals(HttpMethod.OPTIONS.name(), httpServletRequest.getMethod())) {
return true;
}
String tokenValue = httpServletRequest.getHeader(HEAD_TOKEN);
if (StringUtils.isBlank(tokenValue)) {
log.error("token is null.");
unionFailResponse(servletResponse);
return false;
}
StatelessToken token = new StatelessToken(tokenValue);
Subject subject = getSubject(servletRequest, servletResponse);
//import org.apache.shiro.subject.Subject;
try {
subject.login(token);
} catch (Exception e) {
log.error("token is warning. token : {}.", tokenValue, e);
unionFailResponse(servletResponse);
return false;
}
return true;
}
那么直接找到shiro的验证逻辑进行断点即可跟进整个认证登录流程。在shenyu-admin/src/main/java/org/apache/shenyu/admin/shiro/config/ShiroRealm.java
中 可以看到两个doGetAuthorizationInfo
方法,其中doGetAuthorizationInfo(final PrincipalCollection principalCollection)
是用来鉴权使用的,我们先跳过,主要是下面的doGetAuthenticationInfo(final AuthenticationToken authenticationToken)
,这个方法是用来进行认证的。
@Override
protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principalCollection) {
UserInfo userInfo = (UserInfo) principalCollection.getPrimaryPrincipal();
Set<String> permissions = permissionService.getAuthPermByUserName(userInfo.getUserName());
if (CollectionUtils.isEmpty(permissions)) {
return null;
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken authenticationToken) {
String token = (String) authenticationToken.getCredentials();
if (StringUtils.isEmpty(token)) {
return null;
}
UserInfo userInfo = getUserInfoByToken(token);
return new SimpleAuthenticationInfo(userInfo, token, this.getName());
}
我们可以看到整个认证的代码很简单,首先获取到token,然后判断token是否为空,不为空的话接着调用getUserInfoByToken
方法获取用户信息实体,然后返回一个SimpleAuthenticationInfo
信息,整个逻辑结束,那么用户能不能登录,核心点就是在getUserInfoByToken方法中。
/**
* 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");
}
DashboardUserVO dashboardUserVO = dashboardUserService.findByUserName(userName);
if (dashboardUserVO == null) {
throw new AuthenticationException(String.format("userName(%s) can not be found.", userName));
}
return UserInfo.builder()
.userName(userName)
.userId(dashboardUserVO.getId())
.build();
}
上面的代码逻辑也很简单,就是根据token获取用户名,然后用户名不为空的话就从数据库中查找该用户名相对应的用户实体信息,找到后就返回一个UserInfo对象。
接着我们看JwtUtils.getIssuer这个方法,可以看到就是简单的decode了下token,然后从里面获取userName相关的信息并且返回。看到这里其实已经发现问题了,在目前看到的逻辑中,没有对token是否有效进行校验。
/**
* according to token to get issuer.
*
* @param token token
* @return Issuer {@link String}
*/
public static String getIssuer(final String token) {
DecodedJWT jwt = JWT.decode(token);
return Optional.ofNullable(jwt).map(item -> item.getClaim("userName").asString()).orElse("");
}
0x05 漏洞复现
访问环境9095所映射出的端口,就是网站的登录入口
首先使用默认初始账号密码 admin/123456 登录进入后台
在System Manager->User 新建一个用户,角色为 default
使用新建的账号进行登录
此时登录界面会提示用户角色为配置权限错误,无法进入后台
抓包发现该账号的登录包已经生成了 jwt token
接下来用上述不具备任何权限的账号的 jwt token 去访问管理员才能访问的接口
http://192.168.237.129:49153/dashboardUser?currentPage=1&pageSize=12
发现访问成功,获取到了所有的账号信息,漏洞复现成功
接下来用上述不具备任何权限的账号的 jwt token 去访问管理员才能访问的接口
http://192.168.237.129:49153/dashboardUser?currentPage=1&pageSize=12
发现访问成功,获取到了所有的账号信息,漏洞复现成功