来自 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;
    
}

我的问题是:

  1. 在哪里以及如何准确获取 Elvis 对象引用的副本?这部分就不多说了。

// Save a reference to the "unresolved" Elvis instance impersonator = payload;

换句话说,子对象的readResolve如何访问父对象引用?

  1. 为了使 ElvisStealer 正常工作,我必须通过用 ElvisStealer 实例替换 String[] 类型的单例实例字段(在那种情况下)来修改序列化数据。在书中,单例包含一个非 transient String[] 实例字段,该字段在序列化流中被替换为 ElvisStealer 实例。然后,当该字段被反序列化时,ObjectInputStream 发现该字段属于 ElvisStealer 类型,并从 ElvisStealer class 调用 readResolve。我的问题是:为什么 JVM 在知道应该有 String 而不是 ElvisStealer 的情况下不给出解析此类字段的错误,其次为什么 JVM 知道应该有 String[] 从 ElvisStealer class 调用 readResolve ],不是 ElvisStealer。

  2. 为什么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?

不,序列化不做静态变量,只做实例字段。这种攻击依赖于反序列化对变量的初始化,反序列化这样做是因为在序列化形式中有一个值。