java安全漫谈-RMI-3
classAnnotations
我们来彻底解释清楚 classAnnotations 是什么。
首先,请区分两个概念:
- Java代码中的注解(Annotation):比如
@Override,@RestController。这是你写在源代码里的。 - Java序列化协议中的
classAnnotations:这是序列化数据流中的一个数据块,与上面的注解没有直接关系。
核心定义
classAnnotations 是 Java 对象序列化后,在二进制数据流中,紧跟在类描述符(ClassDesc)之后的一个可选数据段。它的设计目的是为了让序列化框架能够在序列化一个类时,为这个类“额外附带”一些自定义信息。
你可以把它理解成序列化数据中的一个 “自定义备注字段”。
它在序列化数据流中的位置
一个完整的对象在序列化流中的结构大致如下:
1 | TC_OBJECT (0x73) |
它是如何被使用的?
它的存在,归功于 ObjectOutputStream 中的一个受保护的方法:
1 | public class ObjectOutputStream extends OutputStream { |
工作机制如下:
- 默认情况:当你使用
ObjectOutputStream序列化一个对象时,annotateClass方法什么都不做,classAnnotations区域就是空的。 - 扩展情况:你可以创建
ObjectOutputStream的子类,并重写annotateClass方法。 - 写入备注:在你重写的
annotateClass方法里,你可以使用writeObject,writeUTF等任何方法,向序列化流中写入你想要附加的数据。 - 数据归宿:你写入的这些数据,正好就被放在了序列化数据流的
classAnnotations位置。
RMI 是如何利用 classAnnotations 的?
RMI 框架正是上述机制的典型使用者。
- 自定义流:RMI 使用
MarshalOutputStream(它是ObjectOutputStream的子类)。 - 重写方法:它重写了
annotateClass方法。 - 写入 Codebase:当 RMI 客户端序列化一个服务端可能没有的类时,它会在
annotateClass方法中,将当前的codebase值(即那个 URL)写入流中。
对应的反序列化端:
- 反序列化时,对应的
ObjectInputStream的子类(如MarshalInputStream)会重写resolveClass方法。 - 在
resolveClass中,它会去读取序列化流中classAnnotations区域里存放的codebase信息。 - 如果在本地找不到这个类,就利用这个
codebase去远程加载。
一个极简的代码示例
假设我们想序列化时给每个类附带一个版本号:
1 | // 自定义 ObjectOutputStream,用于写入 classAnnotations |
现在,serializedData 这个字节数组中,MyClass 的类描述符后面,就会包含一个字符串 "v1.0",它就是 classAnnotations 的内容。
总结
classAnnotations是序列化协议里的一个“字段”,不是源代码注解。- 它是一个预留的“扩展槽”,默认为空。
- 通过继承和重写
annotateClass方法,可以向这个“扩展槽”里塞入任何自定义的序列化数据(比如 RMI 塞入的codebaseURL)。 - 攻击的根源:因为
classAnnotations是客户端发送的序列化数据的一部分,所以攻击者可以伪造它,将codebase指向恶意地址,从而利用服务端信任此数据的机制实现攻击。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 X1ly?S!
评论


