尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Java 反序列化漏洞基础:从 readObject 到命令执行

Java 反序列化漏洞基础:从 readObject 到命令执行
📅 发布时间:2026/7/29 22:26:34

Java 反序列化漏洞基础:从 readObject 到命令执行

写在前面

前面我们复现了 Log4j2 和 fastjson 的漏洞,它们都属于"JNDI 注入"那一挂–触发点是组件自己的特性(Log4j2 的 lookup、fastjson 的 autoType),最终都靠 JNDI 去加载远程类。

从这篇开始,我们要进入 Java 安全里另一个更底层、也更经典的话题:反序列化漏洞。

如果说 JNDI 注入是"借组件的刀杀人",那反序列化漏洞就是"借 Java 自己的机制杀人"–它不依赖某个特定组件的 bug,而是利用 Java 反序列化机制本身的"过度信任"。CommonsCollections的 CC1-CC6 六条利用链,就是这条路上的里程碑,几乎所有 Java 安全面试、所有反序列化漏洞分析,都绕不开它们。

在拆解六条链之前,得先把基础打牢:反序列化到底是什么、为什么它能 RCE、CC 库又给我们提供了哪些"积木"。这篇就是那块地基。

一、序列化与反序列化是什么

序列化(serialization):把一个 Java 对象"拍扁"成一串字节流,方便存到文件、塞进网络传输。

反序列化(deserialization):把这串字节流再"还原"成一个活生生的 Java 对象。

// 序列化:对象 -> 字节流ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream("obj.ser"));oos.writeObject(someObject);// 反序列化:字节流 -> 对象ObjectInputStreamois=newObjectInputStream(newFileInputStream("obj.ser"));Objectobj=ois.readObject();

不是所有对象都能序列化–它的类必须实现java.io.Serializable接口(只是个标记接口,没有方法)。实现了它,JVM 就允许这个类的对象被"拍扁"和"还原"。

这功能本身很正常:RPC 通信、Session 持久化、缓存存储都在用。问题出在下一步。

二、readObject:那个危险的"魔术方法"

Java 的反序列化有个特殊设计:当一个对象被反序列化时,如果它的类自定义了readObject方法,JVM 会调用这个方法,而不是走默认的还原逻辑。

publicclassSomeClassimplementsSerializable{privatevoidreadObject(ObjectInputStreamin)throwsIOException,ClassNotFoundException{in.defaultReadObject();// 默认还原字段// ... 然后这个类可以"顺便干点别的"}}

注意readObject的签名是private void readObject(ObjectInputStream),它不是你主动调的,而是反序列化时由 JVM 回调的。这就是所谓的"魔术方法"。

这个设计本意是让类在反序列化时做些自定义恢复(比如校验数据、重建临时字段)。但副作用是:只要一个对象被反序列化,它所在类的readObject里的代码就会执行。

而"被反序列化"这个动作,在很多场景下是由外部数据触发的–比如服务端读取客户端传来的序列化字节流。于是攻击面就出现了:

攻击者递交一段精心构造的字节流,服务端一调用readObject,字节流里指定的那个类的readObject方法就会执行–而那个类的readObject里可能藏着危险操作。

这就是反序列化漏洞的本质:反序列化 = 攻击者能触发任意(可序列化)类的 readObject 执行。

三、漏洞利用的两步法

光让某个类的readObject执行还不够–那个类的readObject里得有"能通往命令执行"的逻辑。但显然 JDK 不会蠢到在readObject里直接Runtime.exec。

真实的利用是"搭桥":找一个readObject里会调用某个对象方法的类,再把这个方法调用一步步导到Runtime.exec或"加载字节码"上。这就是经典的两步法:

┌─────────────────────────────┐ ┌──────────────────────────┐ │ ① 入口类(反序列化入口) │ │ ② 执行体(最终干坏事) │ │ │ │ │ │ 它的 readObject 会在反序列化 │ ────> │ Runtime.exec("calc") │ │ 时调用某个对象的方法 │ 桥 │ 或 加载恶意字节码 │ │ (get / hashCode / toString / │ │ │ │ compare / setValue ...) │ │ │ └─────────────────────────────┘ └──────────────────────────┘
  • 入口类:readObject里调用了某个对象的方法(get、hashCode、toString、compare、setValue等)。因为被调用的对象是攻击者可控的(在字节流里指定),所以这个方法调用可以被"引向"攻击者放好的对象。
  • 执行体:一段能把"方法被调用"转换成"命令执行"的逻辑。

中间用各种触发器(Map、Comparator 等)把方法调用一层层传到执行体。CC 链里的各种类,要么是入口,要么是触发器,要么是执行体。

四、CommonsCollections 的核心积木

org.apache.commons.collections(CC 库)是 Apache 的集合工具库,曾经几乎是 Java 项目的标配。它恰好提供了一组完美的"积木"来拼反序列化攻击。下面这些类是 CC1-CC6 反复出场的角色,先认个脸熟。

4.1 Transformer 三件套(执行体的核心)

Transformer是一个"变换器"接口:输入一个对象,输出一个对象。

publicinterfaceTransformer{Objecttransform(Objectinput);}

CC 库提供了三个关键实现,能拼出"执行任意命令"的逻辑:

①ConstantTransformer–无论输入啥,恒返回构造时传入的常量。

newConstantTransformer(Runtime.class).transform(任意输入)// 永远返回 Runtime.class

②InvokerTransformer–反射调用输入对象的任意方法(最危险的一个)。

// transform 时等价于:input.methodName(paramTypes, args)newInvokerTransformer("exec",newClass[]{String.class},newObject[]{"calc"}).transform(runtimeInstance)// -> runtimeInstance.exec("calc")

③ChainedTransformer–把多个 Transformer 串起来,前一个的输出当后一个的输入。

newChainedTransformer(newTransformer[]{newConstantTransformer(Runtime.class),// -> Runtime.classnewInvokerTransformer("getMethod",...),// -> Method getRuntimenewInvokerTransformer("invoke",...),// -> Runtime 实例newInvokerTransformer("exec",...)// -> runtime.exec("calc")})

为什么这么绕?因为InvokerTransformer.transform是对"输入对象"调方法,而Runtime.getRuntime()是静态方法、没法当实例方法调。所以要先拿Runtime.class,再反射getMethod("getRuntime"),再invoke(null)拿到实例,最后exec。一条绕弯的反射链。

这三件套串起来 = 任意命令执行。现在只差"让ChainedTransformer.transform被调用"。

4.2 触发器:LazyMap 与 TransformedMap

CC 库有两种 Map,能在正常操作时触发Transformer:

LazyMap–get(key)时,若 key 不存在,用factory.transform(key)生成 value 并缓存。factory就是上面那个ChainedTransformer。

MaplazyMap=LazyMap.decorate(innerMap,chainedTransformer);lazyMap.get("不存在的key");// -> chainedTransformer.transform("不存在的key") -> Runtime.exec

TransformedMap–put/setValue时,对 value 跑valueTransformer.transform。

CC1/CC3/CC5/CC6 都用LazyMap作为触发器。所以剩下的问题就是:怎么让LazyMap.get()在反序列化时被调用?

4.3 桥接类:TiedMapEntry

TiedMapEntry持有一个Map和一个key,它的三个方法都指向getValue():

TiedMapEntry.hashCode()->getValue()->map.get(key)TiedMapEntry.toString()->getValue()->map.get(key)

如果map是LazyMap,那么TiedMapEntry.hashCode()或toString()一被调用,就会触发LazyMap.get()->ChainedTransformer.transform()。

这一下把"入口"拓宽了:任何在反序列化时会调用hashCode()或toString()的类,都能借TiedMapEntry桥接到LazyMap。CC5(借toString)、CC6(借hashCode)就是走这条路。

4.4 字节码执行体:TemplatesImpl

ChainedTransformer+Runtime.exec这条路有个软肋:很多防护(黑名单、RASP)会盯着Runtime、InvokerTransformer。于是有了更隐蔽的执行体:直接加载恶意字节码。

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl是 JDK 内置的 XSLT 模板类。它有个_bytecodes字段,存着一段 Java class 字节码。调用它的newTransformer()/getOutputProperties()时,会走:

newTransformer() -> getTransletInstance() -> defineTransletClasses() // defineClass 加载 _bytecodes -> newInstance() // 实例化那个恶意类

恶意类继承AbstractTranslet,在静态初始化块里执行命令:

publicclassEvilextendsAbstractTranslet{static{Runtime.getRuntime().exec("calc");// 类被加载时执行}// 两个 transform 空实现(父类抽象方法)}

这条路的好处:它加载的是任意字节码,不是固定的Runtime.exec,绕过针对 Runtime 的黑名单。CC2/CC3/CC4 都用它当执行体。

五、六条链的全景预告

把上面的积木组合起来,就得到了 CC1-CC6。它们本质上是两个维度的组合:

入口线执行体线
CC1 / CC3AnnotationInvocationHandler(受 JDK 8u71 限制)CC1=Runtime,CC3=TemplatesImpl
CC2 / CC4PriorityQueue(需 CC4 库)CC2=TemplatesImpl,CC4=TemplatesImpl
CC5 / CC6BadAttributeValueExpException/HashSet(通用,不受 8u71 限制)都是 Runtime
  • 入口线演进:从依赖 JDK 内部类(CC1/3,被 8u71 修掉)-> 借PriorityQueue(CC2/4,要 CC4 库)-> 借BadAttributeValueExpException/HashSet(CC5/6,通用)。
  • 执行体线演进:ChainedTransformer+Runtime(CC1/5/6)->TemplatesImpl字节码(CC2/3/4,绕黑名单)。

记住这两个维度,后面六条链就不是六个孤立的东西,而是 2×3 的组合。接下来我们就一条一条拆开看,每条链都配真实复现(calc 弹出 = RCE)。

参考

  • ysoserial(CC 链的出处):https://github.com/frohoff/ysoserial
  • Apache Commons Collections:https://commons.apache.org/proper/commons-collections/
  • Java 反序列化漏洞科普:https://github.com/GrrrDog/Java-Deserialization-Cheat-Sheet

相关新闻

  • 2026厦门大型集团GEO优化服务商盘点:8家靠谱推荐与选择避坑指南 - 产业观察报
  • Speech-Denoising-Wavenet源码解析:关键函数与数据流程详解
  • 2026性价比之选:无锡立式球磨机企业,高效研磨与稳定耐用的双优之选 - 优企名品

最新新闻

  • 深度剖析terminal-to-html工作原理:ANSI转HTML的底层实现揭秘
  • 计算机毕业设计之基于springboot的毕业生闲置物品交易系统
  • 我把 Codex 当“高级工程师”用了一个月:真正拉开差距的,不是写代码
  • Xenon CLI命令详解:轻松管理MySQL集群的7个实用操作
  • 单片机计算机毕设之基于单片机的水产养殖水质双参数监测系统设计 基于 STM32 的手动自动切换型水质报警装置设计(011001)
  • EIP低代码平台 应用管理-画廊视图

日新闻

  • 金融舆情监测系统:多语言情感分析与实时可视化技术解析
  • QT C++调用Python异常处理:PyBind11实战与跨语言编程指南
  • A-47双麦回音消除模块:主次麦空间分布与差分连接对ENC性能的影响

周新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号