这是在单例反序列化时窃取对单例副本的引用的类。

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 类调用 readResolve。我的问题是:为什么 JVM 在解析这样的字段时不给出错误,知道应该有 String 而不是 ElvisStealer,其次为什么 JVM 从 ElvisStealer 类调用 readResolve 知道应该有 String[] ,而不是 ElvisStealer。
  • 为什么 ElvisStealer 除了静态字段之外还包含一个 Elvis 类型的实例字段?不应该是静态字段吗?
  • 最佳答案

    反序列化总是从无中生有的新实例和来自 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 对象的反向引用的窃取器。



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

    10-07 15:19