来自 Effective Java 的 ElvisStealer
ElvisStealer from Effective Java
这里是 class 在反序列化单例时窃取对单例副本的引用。
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;
}
我的问题是:
- 在哪里以及如何准确获取 Elvis 对象引用的副本?这部分就不多说了。
// Save a reference to the "unresolved" Elvis instance impersonator = payload;
换句话说,子对象的readResolve
如何访问父对象引用?
为了使 ElvisStealer 正常工作,我必须通过用 ElvisStealer 实例替换 String[]
类型的单例实例字段(在那种情况下)来修改序列化数据。在书中,单例包含一个非 transient
String[]
实例字段,该字段在序列化流中被替换为 ElvisStealer 实例。然后,当该字段被反序列化时,ObjectInputStream
发现该字段属于 ElvisStealer 类型,并从 ElvisStealer class 调用 readResolve
。我的问题是:为什么 JVM 在知道应该有 String 而不是 ElvisStealer 的情况下不给出解析此类字段的错误,其次为什么 JVM 知道应该有 String[]
从 ElvisStealer class 调用 readResolve
],不是 ElvisStealer。
为什么ElvisStealer除了静态字段外,还包含一个Elvis类型的实例字段?静态字段还不够吗?
反序列化将始终从 ObjectInputStream
.
凭空产生的新实例和字节开始
在那一步之后,您将拥有所有新实例,如下所示:
Elvis(new).favoriteSongs = ElvisStealer(new)
ElvisStealer(new).payload = Elvis(new) // same elvis, circular reference
然后在第 2 步中,反序列化使用这些实例的 readResolve
方法将初步反序列化的对象 "resolve" 为其最终形式。但是它从内部 ElvisStealer
.
开始
Elvis(new).favoriteSongs = ElvisStealer(new).readResolve()
=> Elvis(new).favoriteSongs = String[] { .... }
下一步是解析 Elvis
实例
result = Elvis(new).readResolve()
=> result = Elvis(INSTANCE)
正确的类型(String[]
而不是实际无效的 ElvisStealer
)只需要在 readResolve
步骤之后出现。
中间有这个 "invalid" 阶段很有用。您可以在 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
,您将从 ObjectInputStream
返回 ComplexThing
,但在幕后,这些流操作的字节on on其实就是一个SimpleHiddenReplacement
.
的表示
猫王窃取攻击在 readResolve
方法(=未解决)有机会替换(解决)它之前窃取新创建的 Elvis
。
ElvisStealer(new).payload = Elvis(new)
. Where does that happen? I see only the declaration of a payload variable, but it is not initialized in any way.
书上说
First, write a “stealer” class that has [..] an instance field that refers to the serialized singleton in which the stealer “hides.” In the serialization stream, replace the singleton’s nontransient field with an instance of the stealer.
此设置以精心制作的形式出现 byte[] serializedForm
。它是一个伪造的 Elvis
对象的序列化形式,与普通的序列化猫王不同,它包含带有对 Elvis
对象的反向引用的窃取器。
Secondly, shouldn't the static impersonator
be enough?
不,序列化不做静态变量,只做实例字段。这种攻击依赖于反序列化对变量的初始化,反序列化这样做是因为在序列化形式中有一个值。
这里是 class 在反序列化单例时窃取对单例副本的引用。
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;
}
我的问题是:
- 在哪里以及如何准确获取 Elvis 对象引用的副本?这部分就不多说了。
// Save a reference to the "unresolved" Elvis instance impersonator = payload;
换句话说,子对象的readResolve
如何访问父对象引用?
为了使 ElvisStealer 正常工作,我必须通过用 ElvisStealer 实例替换
String[]
类型的单例实例字段(在那种情况下)来修改序列化数据。在书中,单例包含一个非transient
String[]
实例字段,该字段在序列化流中被替换为 ElvisStealer 实例。然后,当该字段被反序列化时,ObjectInputStream
发现该字段属于 ElvisStealer 类型,并从 ElvisStealer class 调用readResolve
。我的问题是:为什么 JVM 在知道应该有 String 而不是 ElvisStealer 的情况下不给出解析此类字段的错误,其次为什么 JVM 知道应该有String[]
从 ElvisStealer class 调用readResolve
],不是 ElvisStealer。为什么ElvisStealer除了静态字段外,还包含一个Elvis类型的实例字段?静态字段还不够吗?
反序列化将始终从 ObjectInputStream
.
在那一步之后,您将拥有所有新实例,如下所示:
Elvis(new).favoriteSongs = ElvisStealer(new)
ElvisStealer(new).payload = Elvis(new) // same elvis, circular reference
然后在第 2 步中,反序列化使用这些实例的 readResolve
方法将初步反序列化的对象 "resolve" 为其最终形式。但是它从内部 ElvisStealer
.
Elvis(new).favoriteSongs = ElvisStealer(new).readResolve()
=> Elvis(new).favoriteSongs = String[] { .... }
下一步是解析 Elvis
实例
result = Elvis(new).readResolve()
=> result = Elvis(INSTANCE)
正确的类型(String[]
而不是实际无效的 ElvisStealer
)只需要在 readResolve
步骤之后出现。
中间有这个 "invalid" 阶段很有用。您可以在 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
,您将从 ObjectInputStream
返回 ComplexThing
,但在幕后,这些流操作的字节on on其实就是一个SimpleHiddenReplacement
.
猫王窃取攻击在 readResolve
方法(=未解决)有机会替换(解决)它之前窃取新创建的 Elvis
。
ElvisStealer(new).payload = Elvis(new)
. Where does that happen? I see only the declaration of a payload variable, but it is not initialized in any way.
书上说
First, write a “stealer” class that has [..] an instance field that refers to the serialized singleton in which the stealer “hides.” In the serialization stream, replace the singleton’s nontransient field with an instance of the stealer.
此设置以精心制作的形式出现 byte[] serializedForm
。它是一个伪造的 Elvis
对象的序列化形式,与普通的序列化猫王不同,它包含带有对 Elvis
对象的反向引用的窃取器。
Secondly, shouldn't the
static impersonator
be enough?
不,序列化不做静态变量,只做实例字段。这种攻击依赖于反序列化对变量的初始化,反序列化这样做是因为在序列化形式中有一个值。