Gentle_knife's Studio.

Java反射和CC链

Word count: 1.1kReading time: 4 min
2025/07/28
loading

Java 反序列化

反射

大白话说Java反射:入门、使用、原理 - 陈树义 - 博客园

知道类名,用正射:

1
Apple apple = new Apple(); 

Java 中一切皆对象,类是Class对象,方法是Method对象,构造方法是Constructor对象,字段是Field对象。

不知道类名,只能把类名先存变量里面,再一句一句跟代码说要对这个类干什么,这是反射:

1
Class clz = Class.forName("com.zhenai.api.Apple");

对于名字叫 xxx 的类,获取 xxx 类的Class对象(请注意这里不等于 xxx 对象),就是根据 xxx 这个名字,把 xxx 的档案调出来。

1
Constructor appleConstructor = clz.getConstructor();

对于这个对象,获取 xxx 的构造方法/construct/无参函数的对象也调出来,也就是Constructor对象。就是根据 xxx 的档案,把 xxx 的 DNA 也调出来。

1
Object appleObj = appleConstructor.newInstance();

Constructor对象新建一个实例。

根据 xxx 的 DNA,新建一个 xxx。

如果要调用方法:

1
Method setPriceMethod = clz.getMethod("setPrice", int.class);

Class对象获取Method对象,这个Method对象是一个名字叫setPrice,参数类型是int的方法 。

在 xxx 的档案里找一个叫setPrice的技能档案,把这个技能教给 xxx。

1
setPriceMethod.invoke(appleObj, 14);

Method对象调用 invoke 方法,传入对象名字和参数。。

让 xxx 以指定的方式使用技能。

CC1链——全网最菜的分析思路 - FreeBuf网络安全行业门户

反序列化和 readObject

序列化是把对象变成字节流,反序列化是把字节流还原成对象。

定义类 Student.java

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.io.Serializable;

// 表示这个类可以被“序列化”处理
public class Student implements Serializable {
public String name;
public int age;

// 构造方法:创建一个学生对象
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}

implements Serializable必须写上这个接口这个类才可以被序列化。

主程序 Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.*;

public class Main {
public static void main(String[] args) throws Exception {
// 创建一个学生对象
Student stu = new Student("小明", 20);

// 1️⃣ 将对象序列化写入文件 student.ser
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.ser"));
oos.writeObject(stu); // 序列化
oos.close();

// 2️⃣ 从文件中读取对象,进行反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.ser"));
Student stu2 = (Student) ois.readObject(); // 反序列化
ois.close();

// 打印读取到的对象内容
System.out.println("姓名:" + stu2.name);
System.out.println("年龄:" + stu2.age);
}
}

这段代码演示了序列化和反序列化的过程。

反序列化不仅可以把字节流还原成对象,如果类里面有readObject这个方法,那么反序列化的时候会调用这个方法。如果该方法中有恶意代码,也会执行。所以自定义了 readObject()方法的类就是我们寻找的入口点。

如果现在有一个程序Demo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.io.*;

public class Demo implements Serializable {
private String data;

public Demo(String data) {
this.data = data;
}

// 反序列化时自动调用
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
System.out.println("readObject 被调用了!");
in.defaultReadObject(); // 反序列化默认操作,必须调用
System.out.println("data的值是:" + data);
}
}

可以看到开发者自己写了一个readObject,那么我们这里可以写一个调用反序列化的主程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
public static void main(String[] args) throws Exception {
Demo obj = new Demo("Hello");

// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("demo.ser"));
oos.writeObject(obj);
oos.close();

// 反序列化(触发readObject)
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("demo.ser"));
Demo newObj = (Demo) ois.readObject();
ois.close();
}
}

输出:

1
2
readObject 被调用了!
data的值是:Hello

readObject 是起点,能执行命令的类是终点,从起点到终点,前一个触发了后一个,所以是一条链。

通过反射来执行 Runtime 命令:

1
2
3
4
Class clazz = Runtime.class;
Method getRuntime = clazz.getDeclaredMethod("getRuntime", null);
Runtime runtime = (Runtime)getRuntime.invoke(null, null);
runtime.exec("open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator");

获取类,获取方法,获取实例,执行命令

CommonsCollections1 链

InvokerTransformer 类

使用InvokerTransformer 对象调用 transform()方法,会以反射的方式 执行任意方法。

1
2
3
4
5
6
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class} ,
new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Runtime runtime = Runtime.getRuntime();
invokerTransformer.transform(runtime);

这里看不出反射是因为反射被封装在transform()方法内部。

CATALOG
  1. 1. Java 反序列化
    1. 1.1. 反射
    2. 1.2. 反序列化和 readObject
    3. 1.3. CommonsCollections1 链
      1. 1.3.1. InvokerTransformer 类