[备份]Apache Log4j2拒绝服务漏洞分析

0x00 介绍

Log4j2爆出RCE漏洞后,官方给出了RC1RC2的修复,在之前的文章中有详细分析

RC2的修复之前,其实就存在DOS的可能,但我在RC2的修复后,发现仍然可以造成拒绝服务漏洞

于是在RC2修复补丁发布后几小时内向Apache Logging PMC报告了该问题

img

得到了官方的认可和致谢

img

其实当时没有想过申请CVE等步骤,但在今天早上看到了Log4j2发布了CVE-2021-45046漏洞报告,这个CVE正是拒绝服务相关,不过漏洞credit信息并不是我,而是国外某团队

img

具体链接参考:

https://logging.apache.org/log4j/2.x/security.html

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-45046

大致阅读CVE-2021-45046相关的信息后,发现和我提交的DOS漏洞略有不同,但核心部分是一致的

2.15.0版本利用的前提:该漏洞必须在开启lookup功能的情况下触发

一种常见的开启姿势是在log4j2.xml中:

这篇文章就从三个方面来谈一谈这个拒绝服务漏洞

0x01 挖掘过程

回顾RC1RC2的修复:如果存在JndiLookup那么会判断其中的的host是否合法

allowedHosts中一定包含有localhost127.0.0.1

这说明如果LDAP服务端在127.0.0.1可以成功lookup

然而黑客不可能凭空在服务端本地开启一个恶意的LDAP Server

我想到lookup本质是网络相关的操作,会有阻塞的可能。可以构造出Payload使程序lookup本地,而本地不可能开LDAP Server,于是发生超时等待,也许会有拒绝服务漏洞的可能

于是修改了RC2的源码,加入了统计时间代码,分析lookup的超时情况

(下文分析为什么阻塞的方法不是looup而是context.getAttributes

测试以上打印时间的代码会发现总是打印2000左右,说明超时时间为2

深入getAttributes可以看到这样的方法

new LdapCtx方法中存在connect操作导致阻塞

(其实connect方法还有几步才会到达最底层的阻塞,不过没有必要继续分析了)

回到之前的问题:为什么阻塞的不是lookup而是getAttributes方法

当前代码在连接超时后会抛出异常,走不到lookup方法

img

其实在lookup方法中应该也会造成阻塞,简单往里面跟一下会发现类似的代码

现在发现了能让程序阻塞的办法,那么怎样构造Payload以达成更长时间的阻塞呢

Log4j2在处理${}是递归解析,也就是说会处理一个字符串中的所有${}并分别处理对应的值,每一次的处理都会造成2秒的等待,所以只需简单的拼接即可

例如我拼接三个会阻塞更长的时间

(这里是针对本地80端口,实际上可以用大概率关闭的高位端口)

这时候会有师傅产生疑问:

在一个web请求中,这样的payload只能让我当前的请求阻塞住,如何实现真正的拒绝服务攻击,让目标网站无法正常处理别人的请求呢?我将在后文给大家展示

0x02 利用场景

造一个SpringBoot项目,在resources下添加配置文件开启lookup功能

为了制造场景所以要移除了SpringBoot自带的日志依赖,而选用Log4j2

另外引入starter-web以编写Controller模拟真实的接口供测试

模拟一个接口:接受message参数并Base64解码后打印日志

使用Python编写EXP打自己的靶机

启动SpringBoot项目后,可以用这个Python脚本成功造成拒绝服务漏洞

0x03 CVE分析

接下来分析这个CVE,其实我不确定对于这个CVE的解读是否正确

Log4j2.xml中支持一种配置从上下文中取值:例如这个例子可以取到loginId

如果程序这样写

将会打印

如果代码这样写将会导致类似的拒绝服务

xml中有另一种效果相同的配置方式,但这种写法反而不会触发${}解析

issue中也有人证实了这一点

img

关于拒绝服务的分析上文已有,重点看一下ContextMapLookup

这里的contextData正是一个简单的Map

img

resolveVariable方法返回

取出的payload在下一次的递归中成功被lookup

img

不难发现lookup时是从event中取Map那么该Map是如何保存到event中的呢

定位到创建LogEvent的方法ReusableLogEventFactory.createEvent

跟入ThreadContextDataInjector.injectContextData方法

进入ThreadContextDataProvider.supplyStringMap方法

getReadOnlyContextData中获得这个Map

img

再没有必要做进一步的分析了,这个拒绝服务漏洞原理已经清晰了

0x04 CVE利用场景

CVE中提到的利用场景应该更为广泛

通常情况下,记录登录用户的身份等信息是常见的操作

如果程序员选择了Log4j2这种ctx记录的方式而不是手动拼接字符串,将会导致该漏洞

正常情况下:http://localhost:8080/test?userId=MQ==

将会记录

如果打Payload则报错并成功阻塞

改写下Python脚本即可成功拒绝服务