转载

Java序列化的状态

关键要点

  • Java序列化在很多库中引入了安全漏洞。
  • 对序列化进行模块化处于开放讨论状态。
  • 如果序列化能够成为模块,开发人员将能够将其从攻击表面上移除。
  • 移除其他模块可以消除它们所带来的风险。
  • 插桩提供了一种编织安全控制的方法,提供现代化的防御机制。

多年来,Java的序列化功能饱受 安全漏洞 和zero-day攻击,为此赢得了“ 持续奉献的礼物 ”和“ 第四个不可饶恕的诅咒 ”的绰号。

作为回应,OpenJDK贡献者团队讨论了一些用于限制序列化访问的方法,例如将其 提取到可以被移除的jigsaw模块中 ,让黑客无法攻击那些不存在的东西。

一些文章(例如“ 序列化必须死 ”)提出了这样的建议,将有助于防止 某些流行软件(如VCenter 6.5)的漏洞被利用 。

什么是序列化?

自从1997年发布 JDK 1.1 以来,序列化已经存在于Java平台中。

它用于在套接字之间共享对象表示,或者将对象及其状态保存起来以供将来使用(反序列化)。

在JDK 10及更低版本中,序列化作为java.base包和java.io.Serializable方法的一部分存在于所有的系统中。

GeeksForGeeks对 序列化的工作原理 进行了详细的描述。

有关更多如何使用序列化的代码示例,可以参看Baeldung对 Java序列化的介绍 。

序列化的挑战和局限

序列化的局限主要表现在以下两个方面:

  1. 出现了新的对象传输策略,例如JSON、XML、Apache Avro、Protocol Buffers等。
  2. 1997年的序列化策略无法预见现代互联网服务的构建和攻击方式。

进行序列化漏洞攻击的基本前提是找到对反序列化的数据执行特权操作的类,然后传给它们恶意的代码。为了理解完整的攻击过程,可以参看Matthias Kaiser在2015年发表的“ Exploiting Deserialization Vulnerabilities in Java ”一文,其中幻灯片第14页开始提供了相关示例。

其他大部分与序列号有关的安全研究 都是基于Chris Frohoff、Gabriel Lawrence和Alvaro Munoz的工作成果。

序列化在哪里?如何知道我的应用程序是否用到了序列化?

要移除序列化,需要从java.io包开始,这个包是java.base模块的一部分。最常见的使用场景是:

  • 实现Serializable接口和(可选)serialversionuid长整型字段。
  • 使用ObjectInputStream或ObjectOutputStream。
  • 使用 严重依赖序列化 的库,例如:Xstream、Kryo、BlazeDS和 大多数应用程序服务器 。

使用这些方法的开发人员应考虑使用其他存储和读回数据的替代方法。Eishay Smith发布了 几个不同序列化库的性能指标 。在评估性能时,需要在基准度量指标中包含安全方面的考虑。默认的Java序列化“更快”一些,但漏洞也会以同样的速度找上门来。

我们该如何降低序列化缺陷的影响?

项目Amber 包含了一个关于将序列化API隔离出来的讨论。我们的想法是将序列化从java.base移动到单独的模块,这样应用程序就可以完全移除它。在确定 JDK 11功能集 时并没有针对该提议得出任何结果,但可能会在未来的Java版本中继续进行讨论。

通过运行时保护来减少序列化暴露

一个可以监控风险并自动化可重复安全专业知识的系统对于很多企业来说都是很有用的。Java应用程序可以将JVMTI工具嵌入到安全监控系统中,通过插桩的方式将传感器植入到应用程序中。Contrast Security是这个领域的一个免费产品,它是JavaOne大会的 Duke's Choice大奖得主 。与其他软件项目(如MySQL或GraalVM)类似, Contrast Security的社区版 对开发人员是免费的。

将运行时插桩应用在Java安全性上的好处是它不需要修改代码,并且可以直接集成到JRE中。

它有点类似于面向切面编程,将非侵入式字节码嵌入到源端(远程数据进入应用程序的入口)、接收端(以不安全的方式使用数据)和转移(安全跟踪需要从一个对象移动到另一个对象)。

通过集成每个“接收端”(如ObjectInputStream),运行时保护机制可以添加额外的功能。在从JDK 9移植反序列化过滤器之前,这个功能对序列化和其他攻击的类型(如SQL注入)来说至关重要。

集成这个运行时保护机制只需要修改启动标志,将javaagent添加到启动选项中。例如,在Tomcat中,可以在bin/setenv.sh中添加这个标志:

CATALINA_OPTS=-javaagent:/Users/ecostlow/Downloads/Contrast/contrast.jar

启动后,Tomcat将会初始化运行时保护机制,并将其注入到应用程序中。关注点的分离让应用程序可以专注在业务逻辑上,而安全分析器可以在正确的位置处理安全性。

其他有用的安全技术

在进行维护时,可以不需要手动列出一长串东西,而是使用像 OWASP Dependency-Check 这样的系统,它可以识别出已知安全漏洞的依赖关系,并提示进行升级。也可以考虑通过像 DependABot 这样的系统进行库的自动更新。

虽然用意很好,但默认的 Oracle序列化过滤器 存在与SecurityManager和相关沙箱漏洞相同的设计缺陷。因为需要混淆角色权限并要求提前了解不可知的事物,限制了这个功能的大规模采用:系统管理员不知道代码的内容,所以无法列出类文件,而开发人员不了解环境,甚至DevOps团队通常也不知道系统其他部分(如应用程序服务器)的需求。

移除未使用模块的安全隐患

Java 9的模块化JDK能够 创建自定义运行时镜像 ,移除不必要的模块,可以使用名为jlink的工具将其移除。这种方法的好处是黑客无法攻击那些不存在的东西。

从提出模块化序列化到应用程序能够实际使用以及使用其他序列化的新功能需要一段时间,但正如一句谚语所说:“种树的最佳时间是二十年前,其次是现在”。

剥离Java的原生序列化功能还应该为大多数应用程序和微服务提供更好的互操作性。通过使用标准格式(如JSON或XML),开发人员可以更轻松地在使用不同语言开发的服务之间进行通信——与Java 7的二进制blob相比,python微服务通常具有更好的读取JSON文档的集成能力。不过,虽然JSON格式简化了对象共享,针对Java和.NET解析器的“ Friday the 13th JSON attacks ”证明了银弹是不存在的( 白皮书 )。

在进行剥离之前,序列化让然保留在java.base中。这些技术可以降低与其他模块相关的风险,在序列化被模块化之后,仍然可以使用这些技术。

为Apache Tomcat 8.5.31模块化JDK 10的示例

在这个示例中,我们将使用模块化的JRE来运行Apache Tomcat,并移除任何不需要的JDK模块。我们将得到一个自定义的JRE,它具有更小的攻击表面,仍然能够用于运行应用程序。

确定需要用到哪些模块

第一步是检查应用程序实际使用的模块。OpenJDK工具jdeps可以对JAR文件的字节码执行扫描,并列出这些模块。像大多数用户一样,对于那些不是自己编写的代码,我们根本就不知道它们需要哪些依赖项或模块。因此,我使用扫描器来检测并生成报告。

列出单个JAR文件所需模块的命令是:

jdeps -s JarFile.jar

它将列出模块信息:

tomcat-coyote.jar -> java.base
tomcat-coyote.jar -> java.management
tomcat-coyote.jar -> not found

最后,每个模块(右边的部分)都应该被加入到一个模块文件中,成为应用程序的基本模块。这个文件叫作module-info.java,文件名带有连字符,表示不遵循标准的Java约定,需要进行特殊处理。

下面的命令组合将所有模块列在一个可用的文件中,在Tomcat根目录运行这组命令:

find . -name *.jar ! -path "./webapps/*" ! -path "./temp/*" -exec jdeps -s {} /; | sed -En "s/.* -/> (.*)/  requires /1;/p" | sort | uniq | grep -v "not found" | xargs -0 printf "module com.infoq.jdk.TomcatModuleExample{/n%s}/n"

这组命令的输出将被写入lib/module-info.java文件,如下所示:

module com.infoq.jdk.TomcatModuleExample{
  requires java.base;
  requires java.compiler;
  requires java.desktop;
  requires java.instrument;
  requires java.logging;
  requires java.management;
  requires java.naming;
  requires java.security.jgss;
  requires java.sql;
  requires java.xml.ws.annotation;
  requires java.xml.ws;
  requires java.xml;
}

这个列表比整个Java模块列表要短得多。

下一步是将这个文件放入JAR中:

javac lib/module-info.java
jar -cf lib/Tomcat.jar lib/module-info.class

最后,为应用程序创建一个JRE:

jlink --module-path lib:$JAVA_HOME/jmods --add-modules ThanksInfoQ_Costlow --output dist

这个命令的输出是一个运行时,包含了运行应用程序所需的恰到好处的模块,没有任何性能开销,也没有了未使用模块中可能存在的安全风险。

与基础JDK 10相比,只用了98个核心模块中的19个。

java --list-modules

com.infoq.jdk.TomcatModuleExample
java.activation@10.0.1
java.base@10.0.1
java.compiler@10.0.1
java.datatransfer@10.0.1
java.desktop@10.0.1
java.instrument@10.0.1
java.logging@10.0.1
java.management@10.0.1
java.naming@10.0.1
java.prefs@10.0.1
java.security.jgss@10.0.1
java.security.sasl@10.0.1
java.sql@10.0.1
java.xml@10.0.1
java.xml.bind@10.0.1
java.xml.ws@10.0.1
java.xml.ws.annotation@10.0.1
jdk.httpserver@10.0.1
jdk.unsupported@10.0.1

运行这个命令后,就可以使用dist文件夹中的运行时来运行应用程序。

看看这个列表:部署插件(applet)消失了,JDBC(SQL)消失了,JavaFX也不见了,很多其他模块也消失了。从性能角度来看,这些模块不再产生任何影响。从安全角度来看,黑客无法攻击那些不存在的东西。保留应用程序所需的模块非常重要,因为如果缺少这些模块,应用程序也无法正常运行。

关于作者

Java序列化的状态 Erik Costlow 是甲骨文的Java 8和9产品经理,专注于安全性和性能。他的安全专业知识涉及威胁建模、代码分析和安全传感器增强。在进入技术领域之前,Erik是一位马戏团演员,可以在三轮垂直独轮车上玩火。

查看英文原文: The State of Java Serialization

原文  http://www.infoq.com/cn/articles/java-serialization-aug18
正文到此结束
Loading...