当前教程
利用 JDK 工具和更新来帮助保护 Java 应用程序
这是本系列的最后一篇!

利用 JDK 工具和更新来帮助保护 Java 应用程序

JDK 带有一组内置工具和功能,可以帮助系统管理员维护 Java 安装的安全性。虽然这些工具和功能通常为经验丰富的 Java 开发人员所熟知,但对于被要求保护 Java 应用程序的管理员来说,它们可能并不总是熟悉。

在这篇文章中,我们将介绍一些内置工具和功能,并提供指向更多资源的指针。管理员可以探索这些信息,为可能需要重新评估和应用措施以提高 Java 应用程序安全态势的情况做好准备。

简而言之,本文提供了有关如何保持 Java 应用程序安全的通用建议,而不是有关如何缓解特定漏洞的建议。

 

用于识别应用程序正在使用的类的 JDK 命令行工具

在保护 Java 应用程序时,通常首先要确定是否正在使用特定类。JDK 包含可以帮助完成此任务的工具。

jcmd 工具

jcmd 工具可用于通过运行 jcmd $pid VM.system_properties 来检索正在运行的应用程序的系统属性,其中 $pid 代表要检查的 Java 应用程序的进程 ID。如果使用 jcmd,您会在类路径上的某个位置找到一个潜在的有趣库名称,那么可以进一步检查该库,例如,确定其版本。

Figure 1: jcmd VM.system_properties output example (highlighted class path for emphasis)

图 1:jcmd VM.system_properties 输出示例(突出显示的类路径以示强调)

在 JDK 9 及更高版本中,jcmd 可以使用 VM.class_hierarchy 子命令打印在同一台机器上运行的 Java 应用程序加载的所有类的分层列表,方法是调用 jcmd $pid VM.class_hierarchy

Figure 2: jcmd VM.class_hierarchy output example

图 2:jcmd VM.class_hierarchy 输出示例

在 JDK 7 及更高版本中,可以使用 GC.class_histogram 子命令来代替,以获取所有实例化类的列表(及其内存使用情况),方法是调用 jcmd $pid GC.class_histogram

Figure 3: jcmd GC.class_histogram output example (highlighted for emphasis)

图 3:jcmd GC.class_histogram 输出示例(突出显示以示强调)

管理员可以检查 jcmd 的输出以查找感兴趣的包,例如 com.example.foo.bar,以确定正在运行的 Java 应用程序是否已加载或实例化了来自具有这些名称的包的类。

尚未加载或实例化的包和类不会显示在 jcmd 的输出中。在这种情况下,它们在 jcmd 输出中的缺失不应被视为表明它们不能在以后的时间点被应用程序加载,前提是其配置和依赖项允许这样做。

使用 JDK 9 及更高版本时,无需检查 GC 类直方图以查找可疑类,因为类必须先加载才能实例化。您应该使用与用于运行您正在检查的进程的 JDK 相匹配的 JDK 中的 jcmd 版本。有关使用 jcmd 工具的更多信息,请参见 JDK 故障排除指南

摘要

命令 支持版本 描述 操作
jcmd <pid> VM.system_properties JDK 7 打印为 JDK 设置的所有系统属性 检查类路径以查找特定库
jcmd <pid> GC.class_histogram JDK 7 创建并打印类直方图 检查实例化类
jcmd <pid> VM.class_hierarchy JDK 9 打印类层次结构 检查已加载的类

jdeps

类和包可能在运行时作为依赖项加载。从 JDK 8 开始,开发人员和系统管理员可以使用 jdeps 静态分析 Java 库和类,以了解有关其包级或类级依赖项的更多信息。

开发人员可以使用 jdeps 检查单个 JAR 库,使用 -p 选项搜索对特定包的依赖项,例如 jdeps -p com.example.foo.bar some.jar 将列出 some.jar 中依赖于 -p 指定的包的包。jdeps 还可以使用正则表达式过滤包依赖项。这允许开发人员和管理员搜索部分包名称,例如,查找阴影库。

有关使用 jdeps 的更多信息,请参见 相应的联机帮助页

摘要

命令 支持版本 描述 操作
jdeps -package <package-name> <jar> JDK 8 静态分析 Java 库和类 检查包级或类级依赖项

 

运行时配置选项

系统属性、环境变量和命令行选项

保持 Java 运行时更新的另一个重要原因是,甲骨文不断改进运行时功能,并根据最佳实践提供缓解措施,如 JDK 发行说明 中所述。

最佳安全实践鼓励用户禁用应用程序不需要的功能,并配置其系统以尽可能限制所需的功能。

例如,自 2017 年 1 月(在 8u121、7u131 和 6u141 中)以来,已引入了一些禁用或限制 Java 命名和目录接口 (JNDI) 使用的选项,当时 通过 JNDI 对象工厂的远程类加载默认情况下被禁用。从 2018 年 10 月(11.0.1、8u191、7u201 和 6u211)开始,默认情况下启用了更多限制。

如果应用程序正在使用 JNDI 并且它不需要工厂来创建任何 Java LDAP 对象,则管理员应在启动时使用 JAVA_TOOL_OPTIONS 环境变量传递 Java 运行时命令行选项:-Djdk.jndi.object.factoriesFilter=!* and -Dcom.sun.jndi.ldap.object.trustSerialData=false。这些属性在 Oracle Java 17、11.0.11、8u291 和 7u301 及更高版本(自 2021 年 4 月起)上可用。

这些属性的详细说明可在 Java SE API 文档 中找到

  • jdk.jndi.object.factoriesFilter:此系统和安全属性允许指定一个序列化过滤器,该过滤器控制允许从命名/目录系统返回的对象引用实例化对象的工厂类集。在远程引用重建期间,引用实例指定的工厂类与该过滤器匹配。过滤器属性支持模式匹配过滤器语法,其格式由 JEP 290:过滤传入的序列化数据 指定。此属性适用于 JNDI/RMI 和 JNDI/LDAP 内置提供程序实现。默认值允许引用中指定的任何工厂类重新创建引用的对象。
  • com.sun.jndi.ldap.object.trustSerialData:此系统属性允许控制从 javaSerializedData LDAP 属性反序列化 java 对象。为了防止从属性反序列化 java 对象,可以将系统属性设置为 false 值。默认情况下,允许从 javaSerializedData 属性反序列化 java 对象。

有关使用 JAVA_TOOL_OPTIONS 环境变量的更多信息,请参见 相应的故障排除指南

如果您的应用程序使用序列化,甲骨文建议使用 JEP 290:过滤传入的序列化数据JEP 415:特定于上下文的反序列化过滤器 中所述的序列化过滤器,以限制可以反序列化的类。过滤传入的对象序列化数据流可以显著提高应用程序的安全性 and 鲁棒性。

配置选项摘要

命令 支持版本 描述 操作
jdk.jndi.object.factoriesFilter 17、11.0.11、8u291 和 7u301 控制允许从命名/目录系统返回的对象引用实例化对象的工厂类集。 -Djdk.jndi.object.factoriesFilter=!* 禁止对 JNDI 使用任何对象工厂
com.sun.jndi.ldap.object.trustSerialData 17、11.0.11、8u291 和 7u301 允许控制从 javaSerializedData LDAP 属性反序列化 java 对象。 -Dcom.sun.jndi.ldap.object.trustSerialData=false 阻止从 LDAP 属性反序列化
jdk.serialFilter 9、8u121 和 7u131 过滤传入的对象序列化流,以提高安全性 and 鲁棒性。 JDK 21 指南和示例
JDK 17 指南和示例
JDK 11 指南和示例
JDK 7 和 8 指南和示例

从 JDK 9 开始,开发人员可以使用 jlink 工具为其应用程序生成自定义 JDK 运行时镜像,该镜像仅提供其代码及其依赖库所需的 JDK 功能。例如,如果应用程序没有 GUI,因此不需要桌面 API 及其实现,则可以生成不包含 java.desktop 模块的 JDK 运行时镜像,从而减少组合下载大小以及应用程序的潜在攻击面。

继续我们之前的示例,如果想要创建一个不使用 JNDI 的运行时,可以创建一个不包含 java.naming 模块的自定义运行时,该模块位于该功能所在的位置。可以使用 java --list-modules 命令找到 JDK 运行时镜像中的所有模块集。

Figure 4: java --list-modules partial output example using jdk 11.0.13

图 4:使用 jdk 11.0.13 的 java --list-modules 部分输出示例

开发人员可以通过向 jlink 提供要包含在运行时镜像中的模块的显式列表来创建自定义 JDK 运行时镜像。要包含在镜像中的模块集取决于为其创建镜像的应用程序的要求和 JDK 模块依赖项。后者可以使用 jdeps 确定。

从 JDK 11 开始,jdeps 提供了 –print-module-deps 选项,该选项提供了一个逗号分隔的 JDK(和其他)模块列表,可以将其与 –add-modules 选项一起传递给 jlink,以创建一个仅包含这些模块及其传递依赖项的自定义运行时镜像。

下面是一个使用 jlink 生成仅包含几个模块的自定义运行时镜像的简单示例。其他自定义 JDK 运行时可能包含 JDK 模块的更大子集,以及其他文件,例如对不同语言环境的支持或不同的服务提供者实现,这些文件最终取决于其代码。并非所有此类 JDK 依赖项都可以通过 jdeps 独自执行的字节码静态分析来确定。开发人员应仔细检查其自定义 JDK 运行时,以确保从生成的自定义运行时镜像运行 java –list-modules 时存在应用程序所需的所有 JDK 功能。

$ jlink --module-path . --add-modules java.base,java.logging,java.net.http,java.xml --output my_runtime

$ my_runtime/bin/java --list-modules
java.base@11.0.13
java.logging@11.0.13
java.net.http@11.0.13
java.xml@11.0.13

通过 jlink 创建的自定义 JDK 运行时镜像是 JDK 的定义明确的子集。系统管理员应将与常规 JDK 升级相同的集成测试应用于自定义 JDK 运行时镜像。

您可以在其 手册页 上了解有关使用 jlink 命令创建自定义 JDK 运行时镜像的更多信息。

如果无法创建自定义 JDK 运行时镜像,系统管理员可以在运行时使用 –limit-modules Java 选项来限制应用程序可以观察到的 JDK 模块集。JDK 9 将此 Java 选项作为 JEP 261:模块系统 的一部分引入。它允许指定 JDK 模块的精确集(包括其依赖项),这些模块将在运行时提供给应用程序,而不是完整的 Java SE API。

运行期望能够在不提供 API 的运行时上使用 API 的代码可能会导致运行时异常。当使用 jlink 生成的自定义运行时镜像时,或者如果使用标志来限制对 JDK 模块的可观察性时,重要的是开发人员和管理员在应用 JDK 升级时运行集成测试。


上次审查: 2024 年 6 月 10 日


当前教程
利用 JDK 工具和更新来帮助保护 Java 应用程序
这是本系列的最后一篇!