这是在单例反序列化时窃取对单例副本的引用的类。
public class ElvisStealer implements Serializable {
static Elvis impersonator;
private Elvis payload;
private Object readResolve() {
// Save a reference to the "unresolved" Elvis instance
impersonator = payload;
// Return an object of correct type for favorites field
return new String[] { "A Fool Such as I" };
}
private static final long serialVersionUID = 0;
}
我的问题是:// Save a reference to the "unresolved" Elvis instance impersonator = payload;
换句话说,子对象的 readResolve
如何访问父对象引用?String[]
类型的单例实例字段(在这种情况下)来修改序列化数据。在书中单例包含一个非 transient
String[]
实例字段,该字段被序列化流中的 ElvisStealer 实例替换。然后,当该字段被反序列化时,ObjectInputStream
会看到该字段的类型为 ElvisStealer,并从 ElvisStealer 类调用 readResolve
。我的问题是:为什么 JVM 在解析这样的字段时不给出错误,知道应该有 String 而不是 ElvisStealer,其次为什么 JVM 从 ElvisStealer 类调用 readResolve
知道应该有 String[]
,而不是 ElvisStealer。最佳答案
反序列化总是从无中生有的新实例和来自 ObjectInputStream
的字节开始。
在该步骤之后,您将拥有所有新实例,如下所示:
Elvis(new).favoriteSongs = ElvisStealer(new)
ElvisStealer(new).payload = Elvis(new) // same elvis, circular reference
然后在步骤 2 中,反序列化使用这些实例的
readResolve
方法将初步反序列化的对象“解析”为它们的最终形式。然而,它从内部 ElvisStealer
开始。Elvis(new).favoriteSongs = ElvisStealer(new).readResolve()
=> Elvis(new).favoriteSongs = String[] { .... }
下一步是解析
Elvis
实例result = Elvis(new).readResolve()
=> result = Elvis(INSTANCE)
正确的类型(
String[]
而不是实际无效的 ElvisStealer
)只需要在 readResolve
步骤之后出现。中间有这个“无效”阶段是有用的。您可以在
writeReplace
方法中声明您希望序列化不同的对象,然后在该对象的 readResolve
方法中使用生成正确类型对象的代码。例如。当你有
class ComplexThing implements Serializable {
private Object writeReplace() throws ObjectStreamException {
return new SimpleHiddenReplacement();
}
}
private class SimpleHiddenReplacement implements Serializable {
private Object readResolve() throws ObjectStreamException {
return new ComplexThing();
}
}
您可以将
ComplexThing
传递给 ObjectOutputStream
,然后您将从 ComplexThing
取回 ObjectInputStream
,但在幕后,这些流操作的字节实际上是 SimpleHiddenReplacement
的表示。elvis 窃取攻击会在
Elvis
方法(= 未解析)有机会替换(解析)它之前窃取新创建的 readResolve
。书上说
此设置以精心制作的
byte[] serializedForm
的形式发生。它是 Elvis
对象的假序列化形式,与普通的序列化 Elvises 不同,它包含具有对 Elvis
对象的反向引用的窃取器。不,序列化不做静态变量,只做实例字段。这种攻击依赖于反序列化对变量的初始化,而反序列化之所以这样做,是因为序列化形式中有一个值。