当前位置: 首页 > 知识库问答 >
问题:

可以显式删除对lambda的序列化支持

邓深
2023-03-14

众所周知,当目标接口尚未继承可序列化的时,很容易向lambda表达式添加序列化支持,就像

我要求的是一种相反的方法,即在目标接口继承Serializable时显式删除序列化支持。

由于无法从类型中删除接口,基于语言的解决方案可能看起来像是(@NotSerializable TargetInterface)()-

即使在类实现可序列化时拒绝序列化在过去是一种合法的行为,并且类在程序员的控制下,模式如下:

public class NotSupportingSerialization extends SerializableBaseClass {
    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
      throw new NotSerializableException();
    }
    private void readObject(java.io.ObjectInputStream in)
      throws IOException, ClassNotFoundException {
      throw new NotSerializableException();
    }
    private void readObjectNoData() throws ObjectStreamException {
      throw new NotSerializableException();
    }
}

但是对于lambda表达式,程序员无法控制lambda类。

为什么会有人费心删除支持?好吧,除了为包含序列化支持而生成的更大代码之外,它还会产生安全风险。考虑以下代码:

public class CreationSite {
    public static void main(String... arg) {
        TargetInterface f=CreationSite::privateMethod;
    }
    private static void privateMethod() {
        System.out.println("should be private");
    }
}

在这里,只要程序员小心,即使TargetInterfacepublic(接口方法始终是public),对私有方法的访问也不会公开,不会将实例f传递给不受信任的代码。

但是,如果TargetInterface继承Serializable,情况就会发生变化。然后,即使CreationSite从未分发实例,攻击者也可以通过反序列化手动构建的流来创建等效实例。如果上面示例的接口看起来像

public interface TargetInterface extends Runnable, Serializable {}

这非常简单:

SerializedLambda l=new SerializedLambda(CreationSite.class,
    TargetInterface.class.getName().replace('.', '/'), "run", "()V",
    MethodHandleInfo.REF_invokeStatic,
    CreationSite.class.getName().replace('.', '/'), "privateMethod",
    "()V", "()V", new Object[0]);
ByteArrayOutputStream os=new ByteArrayOutputStream();
try(ObjectOutputStream oos=new ObjectOutputStream(os)) { oos.writeObject(l);}
TargetInterface f;
try(ByteArrayInputStream is=new ByteArrayInputStream(os.toByteArray());
    ObjectInputStream ois=new ObjectInputStream(is)) {
    f=(TargetInterface) ois.readObject();
}
f.run();// invokes privateMethod

请注意,攻击代码不包含安全管理器将撤销的任何操作。

支持序列化的决定是在编译时做出的。它需要在CreationSite中添加一个合成工厂方法,并向metafactory方法传递一个标志。如果没有该标志,生成的lambda将不支持序列化,即使接口恰好继承了可序列化的。lambda类甚至会有一个writeObject方法,如上面的示例NotSupportingSerialization中所示。如果没有合成工厂方法,反序列化是不可能的。

我发现这导致了一个解决方案。您可以创建接口的副本并将其修改为不继承Serializable,然后针对该修改后的版本进行编译。因此,当运行时的真实版本恰好继承Serializable时,序列化仍然会被撤销。

另一个解决方案是,至少在目标接口继承了可序列化的代码的情况下,永远不要在安全相关代码中使用lambda表达式/方法引用,在根据接口的较新版本进行编译时,必须始终对其进行重新检查。

但我认为必须有更好的,最好是语言内的解决方案。

共有1个答案

柴嘉石
2023-03-14

如何处理可串行性是EG面临的最大挑战之一;可以说没有很好的解决方案,只有各种缺点之间的权衡。一些政党坚持所有lambdas都是自动可序列化的 (!); 另一些人坚持lambdas永远不可序列化(这有时看起来是一个有吸引力的想法,但可悲的是会严重违反用户的期望。)

您注意到:

另一个解决方案是在安全相关代码中永远不要使用lambda表达式/方法引用,

事实上,序列化规范现在正是这么说的。

但是,这里有一个相当简单的诀窍来实现您想要的。假设您有一些需要可序列化实例的库:

public interface SomeLibType extends Runnable, Serializable { }

使用期望此类型的方法:

public void gimmeLambda(SomeLibType r)

您想将lambda传递给它,但不想让它们可序列化(并承担后果。)因此,为自己编写这个助手方法:

public static SomeLibType launder(Runnable r) {
    return new SomeLibType() {
        public void run() { r.run(); }
    }
}

现在可以调用库方法:

gimmeLambda(launder(() -> myPrivateMethod()));

编译器会将您的lambda转换为不可序列化的Runnable,而洗钱包装器会将其包装为满足类型系统的实例。当您尝试序列化它时,这将失败,因为r不可序列化。更重要的是,您无法伪造对私有方法的访问权限,因为捕获类中所需的$desializeLambda$支持甚至不存在。

 类似资料:
  • 问题内容: 由于已经知道它很容易地添加 _序列化_支持lambda表达式时,目标接口已经不继承,就像。 我想要的是一种相反的方法,当目标接口 确实 继承时,显式删除对序列化的支持。 由于您无法从类型中删除接口,因此基于语言的解决方案可能看起来像。但是据我所知,还没有这样的解决方案。(如果我错了,请纠正我,这将是一个完美的答案) 即使在过去实现了类的合法行为并且在程序员控制下的类的情况下,也拒绝序列

  • 下面是一个完整的Elm脚本,它将一组元组放入HTML

  • 如果接口只是一个标记接口,用于在 java 中传递有关类的某种元数据 - 我有点困惑: 在阅读了java的序列化算法(元数据从下到上,然后从上到下的实际实例数据)的过程之后,我无法真正理解哪些数据不能通过该算法进行处理。 简而言之: 哪些数据可能导致? 我怎么知道我不应该为我的类添加子句?

  • 我的问题是,有什么方法可以让我序列化/反序列化一个名为onlinePlayers的列表,该列表引用了“John1”的实例,它也碰巧在List allPlayers中,而不重复“John1”,同时仍然引用那个对象? 我猜当我反序列化allPlayers时,它将创建不同于原始对象的对象,所以onlinePlayers在反序列化后不可能仍然引用相同的对象。我是否应该编写一个自定义方法,在反序列化后将新创

  • 我在搞lambdas的Java序列化。 我有两个完全独立的项目,其中只有一个类。 项目1: 项目二: 但是,在尝试反序列化可运行文件时,它会抛出一个异常,说它找不到 ...我有什么办法可以避免这种情况吗?

  • 我正在试验Stanford CoreNLP库,我想序列化主要的StanfordCoreNLP管道对象,尽管它抛出了一个java.io.NotSerializableException。 完整故事:每当我运行我的实现时,将管道注释器和分类器加载到内存中大约需要15秒。最终进程的内存约为600MB(很容易小到可以存储在我的机箱中)。我想在第一次创建管道后保存它,这样我就可以在以后将其读入内存。 然而,