Apache Log4j2 Jndi RCE高危漏洞分析与防御
更多安全资讯和分析文章请关注启明星辰ADLab微信公众号及官方网站(adlab.venustech.com.cn)
漏洞概述
Apache log4j2是一款Apache软件基金会的开源基础框架,用于Java日志记录的工具。日志记录主要用来监视代码中变量的变化情况,周期性地记录到文件中供其他应用进行统计分析工作;跟踪代码运行时轨迹,作为日后审计的依据;担当集成开发环境中的调试器的作用,向文件或控制台打印代码的调试信息。其在JAVA生态环境中应用极其广泛,影响巨大。
漏洞时间轴
2014年7月13日:Apache Log4j2官方发布log4j-2.0此时该漏洞已经存在,距今7年之久;
2021年11月24日:阿里云安全团队向Apache官方报告了ApacheLog4j2远程代码执行漏洞(CVE-2021-44228);
2021年12月8日:Apache Log4j2官方发布log4j2-2.15.0-rc1并第一次修复CVE-2021-44228漏洞;
2021年12月9日:启明星辰ADLab监测到Apache Log4j2官方公告并开展验证;
2021年12月10日:启明星辰ADLab确认漏洞存在,成功复现该漏洞并通报主管单位;
2021年12月10日:启明星辰ADLab研究确认log4j2-2.15.0-rc1存在Bypass的漏洞;
2021年12月10日:Apache Log4j2官方发布log4j2-2.15.0-rc2修复bypass漏洞。
漏洞影响版本
ApacheLog4j 2.x < =2.15.0-rc1
漏洞详情
漏洞触发的调用链如下:
在进行Logger.log()日志记录时会采用logIfEnabled()方法进行判断,返回为true才可以继续进行log工作。这里也是漏洞能否成功触发的关键。
在本次漏洞分析过程中日志等级为Level.FATAL,它的intLevel()为100,而本环境中默认的日志级别为ERROR(200),如下图所示:
此时filter方法返回true,成功进入到logMessage的方法中。这里当Level等级优先级高于或等于ERROR(200)时(等级越高数值越低)才能触发漏洞;如WARN、INFO、DEBUG、ALL优先级低于ERROR(200),则无法触发该漏洞。也就是说,漏洞能否成功触发与设置的日志Level有关。
然后,来到org.apache.logging.log4j.core.pattern.MessagePatternConverter的format()这个关键方法中,首先要判断noLookups这个变量的值,noLookups默认为false,意思为开启JNDI格式化功能。如下图所示:
我们根据之前的分析已经知道了noLookups这个变量的值为false的,!false的值为真,那么就进入到for循环里继续对log的message event进行处理,取出${}中的数据进行后续lookup()操作。
org.apache.logging.log4j.core.lookup.Interpolator的lookup()方法包含多种处理event的途径,根据event的prefix选择相应的StrLookup进行处理。包括jndi、date、Java、main等。
当构造的event的prefix为jndi时,则通过org.apache.logging.log4j.core.lookup.JndiLookup的lookup()方法处理,从而触发JNDI漏洞利用。
几点疑问的解答
Log4j 1.x的最高版本为1.2.17,以下将1.x表示为小于等于1.2.17的所有版本。由于Log4j 1.x的代码和Log4j2相差比较大,没有Log4j2相对应的功能,所以Log4j 1.x版本不存在CVE-2021-44228漏洞。
通过深入分析源码后发现,如果我们能够控制配置文件log4j.properties中的内容,也可以达到JNDI注入的效果。示例如下:
成功利用如下图所示:
有两个限制条件,第一个限制条件是:默认情况下,代码中日志等级的优先级高于或等于默认级别(ERROR(200),数值越低优先级越高)才可以成功,所以OFF、FATAL、ERROR均可被利用。如下图所示:
但若是项目中采用配置文件的形式自定义日志级别,以Apache Struts2 为例,ApacheStruts2默认日志级别为:info。如下图所示:
通过调试Struts2Showcase,部署后获取到的初始日志级别为:info(400)。
当我们日志等级优先级高于或等于info(400)时,WARN、ERROR、FATAL、OFF、INFO这几个级别的日志都可能被利用,Apache Struts2存在漏洞的位置如下图所示。
第二个限制条件是:必须能够控制日志内容。测试代码如下:logger.log(Level.ERROR,"xxx");只有当log方法的第二个参数xxx的内容被污染的情况下才能被成功利用。
3.用了高版本JDK,漏洞就免疫了吗
看了一些大家对这个漏洞的讨论,很多人以为JDK版本高了就免疫了,某个网友评论如下图所示:
在某些特定情况下,比如中间件用的Tomcat、Websphere,又比如存在反序列化漏洞的依赖包,攻击者同样可以绕过高版本JDK的限制达到RCE的目的。虽然高版本JDK对这个漏洞的防御不是那么完美,但仍强烈建议选用高版本的JDK,至少能提高黑客入侵的门槛。
4.Log4j2 2.15.0-rc1补丁安全吗
5.Log4j2 2.15.0-rc2补丁安全吗
rc1被绕过了,rc2是不是也能被绕过?结论是目前是安全的,因为rc2有诸多限制,如协议、白名单HOST、LDAP Attributes限制等多个限制,其中白名单HOST限制如下所示:
有些人觉得补漏洞太麻烦了,而且升级最新版本不兼容,更换版本后错误百出,不如用WAF来防御,阻断ldap、jndi这些关键字。目前针对绕过WAF的方法已经有很多种了,仅使用WAF拦截肯定会出现问题。
7.加固了,为何仍存在漏洞
我们看到早期文章加固方案基本按照下面这样写的:
jvm参数 -Dlog4j2.formatMsgNoLookups=true; 在应用程序的classpath下添加log4j2.component.properties配置文件文件,文件内容:log4j2.formatMsgNoLookups=True; 设置系统环境变量FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS 设置为true。
很多人按照这个方案去加固,最后看到的结果是漏洞依然存在。这里有两点需要注意:
这三个修复方案在2.10以下均处于失效状态。如果你用的2.10以下的版本,这样来加固肯定是不可以的。
2.10版本以上,为什么也不成功。原因是设置环境变量FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS为true并不会关闭lookup,而正确的修复方案是将LOG4J_FORMAT_MSG_NO_LOOKUPS设置为true。
通过跟踪代码发现,在初始化LogManager的Context环境时,会将系统环境变量加入到Log4j自己的属性环境中。当系统环境变量的key值前缀为“LOG4J_”时,自动截取“LOG4J_”之后的部分进行Util.tokenize(key)处理后加入到Environment的tokenized中。
Util.tokenize()方法对参数key进行正则匹配处理,然后返回处理后的List结果。正则匹配的规则为:
那么,如何设置key值才能成功让PropertiesUtil.getProperties().getBooleanProperty("log4j2.formatMsgNoLookups",false);返回值为true呢?
跟踪代码发现,getBooleanProperty()获取属性也是通过查找PropertiesUtil.Environment中的normalized、literal和tokenized是否存在该属性判断的。而通过tokenized查找时,Util.tokenize(key)处理后的值作为key值然后在tokenized中进行匹配。
我们再来看系统环境变量LOG4J_FORMAT_MSG_NO_LOOKUPS,去掉前缀LOG4J_后经Util.tokenize()处理后的结果如下图所示:
因此,成功将Constants.FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS=PropertiesUtil.getProperties().getBooleanProperty("log4j2.formatMsgNoLookups",false);的结果设置为true,进而达到防御效果。
这里的系统环境变量设置为:
其实只要满足正则匹配规则就可以,例如:
总结一下这三种方案只适用于>=2.10版本,2.10以下的版本无效。FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS不符合正则规则所以无效,而环境变量LOG4J_FORMAT_MSG_NO_LOOKUPS匹配成功,可以成功关闭lookup。
总 结
注意:其中很多应用的利用代码已经被公布出来,希望引起大家足够重视。
解决方案
最终方案:
临时方案:
1.对于log4j 1.x版本移除JMSAppender.class文件,命令为:zip -q -d log4j-1.x.jar org/apache/log4j/net/JMSAppender.class(使用该方案时需要经过测试,避免对实际业务产生影响)。
2.对于log4j2 >=2.10的版本(以下三种方案任选其一)
添加log4j2.component.properties配置文件,增加如下内容为:log4j2.formatMsgNoLookups=true
设置 jvm 参数: -Dlog4j2.formatMsgNoLookups=true 设置系统环境变量:LOG4J_FORMAT_MSG_NO_LOOKUPS=true
3.对于log4j2 <2.10的版本,可以通过移除JndiLookup类的方式,命令为:zip -q -d log4j-core-2.x.jarorg/apache/logging/log4j/core/lookup/JndiLookup.class。
参考链接:
1.https://www.cnvd.org.cn/webinfo/show/7116
2.https://github.com/apache/logging-log4j2/releases/tag/log4j-2.16.0-rc1
3.https://gist.github.com/SwitHak/b66db3a06c2955a9cb71a8718970c592
启明星辰积极防御实验室(ADLab)
ADLab成立于1999年,是中国安全行业最早成立的攻防技术研究实验室之一,微软MAPP计划核心成员,“黑雀攻击”概念首推者。截止目前,ADLab已通过CVE累计发布安全漏洞近1100个,通过 CNVD/CNNVD累计发布安全漏洞1000余个,持续保持国际网络安全领域一流水准。实验室研究方向涵盖操作系统与应用系统安全研究、智能终端安全研究、物联网智能设备安全研究、Web安全研究、工控系统安全研究、云安全研究。研究成果应用于产品核心技术研究、国家重点科技项目攻关、专业安全服务等。