As 已经知道 https://stackoverflow.com/a/22808112/2711488很容易添加序列化当目标接口尚未继承时支持 lambda 表达式Serializable
, 就像(TargetInterface&Serializable)()->{/*code*/}
.
我要求的是一种相反的方法,即在目标接口时显式删除序列化支持does继承Serializable
.
由于您无法从类型中删除接口,基于语言的解决方案可能看起来像(@NotSerializable TargetInterface)()->{/* code */}
。但据我所知,还没有这样的解决方案。 (如果我错了请纠正我,这将是一个完美的答案)
即使类实现了,也拒绝序列化Serializable
在过去是一种合法的行为,并且在程序员控制下的类中,该模式将如下所示:
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 类。
为什么有人会费心取消支持呢?好吧,除了生成的更大的代码之外,还包括Serialization
支持,它会产生安全风险。考虑以下代码:
public class CreationSite {
public static void main(String... arg) {
TargetInterface f=CreationSite::privateMethod;
}
private static void privateMethod() {
System.out.println("should be private");
}
}
这里,即使访问私有方法也不会暴露TargetInterface
is public
(接口方法总是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
请注意,攻击代码不包含任何操作SecurityManager
会撤销。
支持序列化的决定是在编译时做出的。它需要添加一个合成工厂方法CreationSite
and a flag http://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html#FLAG_SERIALIZABLE传递给元工厂 http://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html#altMetafactory-java.lang.invoke.MethodHandles.Lookup-java.lang.String-java.lang.invoke.MethodType-java.lang.Object...-方法。如果没有该标志,即使接口碰巧继承,生成的 lambda 将不支持序列化Serializable
。 lambda 类甚至会有一个writeObject
方法就像在NotSupportingSerialization
上面的例子。如果没有合成工厂方法,反序列化是不可能的。
我发现这导致了一个解决方案。您可以创建该接口的副本并将其修改为不继承Serializable
,然后针对该修改后的版本进行编译。所以当运行时的真实版本恰好继承时Serializable
,连载仍会被撤销。
那么,另一个解决方案是永远不要在安全相关代码中使用 lambda 表达式/方法引用,至少在目标接口继承的情况下是如此Serializable
在针对较新版本的接口进行编译时,必须始终重新检查。
但我认为必须有更好的、最好是语言解决方案。