首页
社区
课程
招聘
[原创]nacos漏洞(CNVD-2023674205)复现
发表于: 2023-5-29 16:56 692

[原创]nacos漏洞(CNVD-2023674205)复现

2023-5-29 16:56
692

nacos漏洞(CNVD-2023674205)复现&踩坑记录

漏洞披露

3月2日,CNVD披露了编号为CNVD-2023674205的nacos认证绕过漏洞

 

image-20230329194024246

 

在nacos官方github项目(https://github.com/alibaba/nacos)发布2.2.0.1版本对应修复版本,移除了默认鉴权插件中依赖的nacos.core.auth.plugin.nacos.token.secret.key默认值

 

image-20230329194049340

漏洞复现&分析

下载2.2.0版本nacos搭建环境,下载链接:https://github.com/alibaba/nacos/releases/download/2.2.0/nacos-server-2.2.0.tar.gz

 

启动也很简单,先安装好java环境(如何安装java自行搜索 (~ ̄▽ ̄)~),进入bin目录下,执行./startsh -m standalone即可

1
2
3
4
5
6
7
8
# 解压缩
unzip nacos-server-1.0.0.zip
# 进入bin目录
cd nacos/bin
# 运行nacos 注意添加 -m standalone
sh startup.sh -m standalone
# 关闭nacos
sh shutdown.sh

image-20230329194813534

 

根据官方的修复公告,查看配置文件 conf/application.properties,发现nacos.core.auth.plugin.nacos.token.secret.key是有默认值的

 

image-20230329194918431

 

而nacos使用jwt构造认证token,使用HS256算法,把配置文件中nacos.core.auth.plugin.nacos.token.secret.key的默认值当作私钥生成Signature,将subject(用户名)和exp戳写到jwt token里

 

查看源码:nacos-2.2.0\plugin-default-impl\src\main\java\com\alibaba\nacos\plugin\auth\impl\JwtTokenManager.java

 

image-20230329195347518

 

其中secretKey即配置文件中nacos.core.auth.plugin.nacos.token.secret.key的值,从JwtTokenManager类的processProperties()函数中可以看到

 

image-20230329195709642

 

在jwt token校验的时候,校验了token签名有效性和是否过期(nacos-2.2.0\plugin-default-impl\src\main\java\com\alibaba\nacos\plugin\auth\impl\NacosAuthManager.java)

 

image-20230329202055205

 

现在知道了jwt的生成和校验逻辑,以及jwt的默认私钥,就可以伪造jwt token绕过认证nacos认证逻辑,构造一个超长有效期的token,就可以使用poc扫描啦

 

image-20230329203703740

 

下面进行验证,以获取配置信息接口为例,首先是没有jwt token访问接口,返回禁止访问

 

image-20230329204525727

 

带上Authorization再访问,jwt token使用刚才构造的,成功返回

 

image-20230329204603952

 

ps:在老版本中(1.1.4版本及以下),如果nacos没有配置信息,则body中没有数据,仅返回200,所以poc不能以响应包的body为标志进行扫描,若以status_code为标志,则误报会很多

 

image-20230329205503214

 

可以删除search参数值,以响应包的body为标志( •̀ ω •́ )✧

 

image-20230329205543959

 

ps:0.x.x的远古版本没有登录逻辑。。访问就直接进入系统了

漏洞修复

1.2.0版本及以上的nacos,修改配置文件中的nacos.core.auth.plugin.nacos.token.secret.key即可

 

1.1.4版本及以下的nacos,由于私钥写死到了代码里,用户无法自行配置,只能升级nacos到最新版

 

image-20230329213321376

踩过的坑

一开始用登录接口来检测jwt默认私钥漏洞,在1.2.0及以上版本中,使用构造的jwt token访问,发现即使不输入账密,也显示登录成功,接口地址为/nacos/v1/auth/users/login(默认配置启动),响应包中校验200状态码和accessToken字段即可

 

image-20230329210310967

 

但在老版本(1.1.4及以下版本)中,接口地址为/nacos/v1/auth/login(默认配置启动),跟新版本的不一样,并且使用了伪造的jwt token也不成功,造成了漏报(っ °Д °;)っ

 

image-20230329210647725

 

勾起了好奇心,下载nacos源码一探究竟:

 

首先是老版本(1.1.4及以前)的源码,登录逻辑为直接比对账密,比对成功即登录成功

 

img

 

而在新版本(1.2.0版本-最新)中,增加了一个header中token的认证逻辑:如果配置文件中的nacos.core.auth.system.type为nacos或者ldap,且请求包含Authorization的header并校验通过,则优先使用请求中的token,可以不用校验账密

 

配置文件(nacos.core.auth.system.type默认为nacos):

 

img

 

登录部分的源码(nacos-2.2.0\plugin-default-impl\src\main\java\com\alibaba\nacos\plugin\auth\impl\controller\UserController.java):

 

校验通过后,响应包会直接使用请求包中的jwt token

 

img

 

跟进authManager.login(request)函数,到达nacos-2.2.0\plugin-default-impl\src\main\java\com\alibaba\nacos\plugin\auth\impl\NacosAuthManager.java里面

 

在resolveToken和validate0函数里校验

 

img

 

resolveToken函数用于提取Authorization的值,若以Bearer 为开头,则取第7个字符以后的字符串返回

 

img

 

validate0用于校验jwt token,使用配置的nacos.core.auth.default.token.secret.key

 

img

 

响应包中返回登录成功,响应包中包含请求header中的token,而老版本仅校验账密导致返回登陆失败

 

image-20230329210310967

 

所以新版本中使用登录接口验证默认私钥漏洞不适用于老版本,构造一个全版本通杀的poc还是不容易的o(TヘTo),还是加上获取配置信息接口一块扫吧。。

题外话

1、User-Agent导致的未授权漏洞

nacos历史上曾经爆出过使用特殊构造的User-Agent造成未授权接口访问的漏洞,在翻官方文档(https://nacos.io/zh-cn/docs/auth.html)中发现这么一段话:

 

img

 

由此可见,使用特殊构造的User-Agent是官方用于服务端之间的可信通信,未考虑到暴露公网的情况,在nacos 1.2-1.4.0版本期间存在这个安全问题,在1.4.1及以上版本中,nacos默认配置关闭了这个特性,用户可以手动开启,开启后需要配置自定义的key value对

2、某poc扫描器的小特性

在写poc时,我的expression是这样写的,校验响应包头中是否有Content-Type字段,并判断是否为application/json

 

image-20230330104449988

 

看着莫得问题,结果poc扫描器显示poc加载失败

 

image-20230330104707316

 

看了看官方文档,也没发现啥问题,文档里也这么用的

 

image-20230330104834881

 

调试了半天,发现前面一定要有response.status的判断才能加载成功。。

 

image-20230330104943680

 

image-20230330105015474

 

但是响应包的状态码本来就不确定,可能为200 400 500,所以就没加状态码的判断,结果就卡在这里了。。(不晓得为啥一定要先判断response.status才能检查headers)

 

被逼无奈,只能写了个response.status != 1来意思一下了(*  ̄︿ ̄)


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//