開(kāi)篇
?這篇文章簡(jiǎn)單的分析 Log4j2的臨時(shí)解決方案的思路,同時(shí)分析下 jndi攻擊的源碼代碼。臨時(shí)性解決方案的核心思路是:log4j2.formatMsgNoLookups設(shè)置為T(mén)rue。通過(guò)添加 -Dlog4j2.formatMsgNoLookups=true或創(chuàng)建 “l(fā)og4j2.component.properties” 文件并增加配置 “l(fā)og4j2.formatMsgNoLookups=true”。

- 參考JndiLookup官網(wǎng)鏈接
調(diào)用棧
2021-12-12 20:13:12,359 main WARN Error looking up JNDI resource [ldap://192.168.0.3:1389/BugFinder]. javax.naming.CommunicationException: 192.168.0.3:1389 [Root exception is java.net.ConnectException: Connection refused (Connection refused)]
at com.sun.jndi.ldap.Connection.<init>(Connection.java:238)
at com.sun.jndi.ldap.LdapClient.<init>(LdapClient.java:137)
at com.sun.jndi.ldap.LdapClient.getInstance(LdapClient.java:1615)
at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2749)
at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:319)
at com.sun.jndi.url.ldap.ldapURLContextFactory.getUsingURLIgnoreRootDN(ldapURLContextFactory.java:60)
at com.sun.jndi.url.ldap.ldapURLContext.getRootURLContext(ldapURLContext.java:61)
at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:202)
at com.sun.jndi.url.ldap.ldapURLContext.lookup(ldapURLContext.java:94)
at javax.naming.InitialContext.lookup(InitialContext.java:417)
at org.apache.logging.log4j.core.net.JndiManager.lookup(JndiManager.java:172)
at org.apache.logging.log4j.core.lookup.JndiLookup.lookup(JndiLookup.java:56)
at org.apache.logging.log4j.core.lookup.Interpolator.lookup(Interpolator.java:221)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.resolveVariable(StrSubstitutor.java:1110)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:1033)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:912)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.replace(StrSubstitutor.java:467)
at org.apache.logging.log4j.core.pattern.MessagePatternConverter.format(MessagePatternConverter.java:132)
at org.apache.logging.log4j.core.pattern.PatternFormatter.format(PatternFormatter.java:38)
at org.apache.logging.log4j.core.layout.PatternLayout$PatternSerializer.toSerializable(PatternLayout.java:344)
at org.apache.logging.log4j.core.layout.PatternLayout.toText(PatternLayout.java:244)
at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:229)
at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:59)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:197)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:190)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:181)
at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:156)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:129)
at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:120)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:84)
at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:540)
at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:498)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:481)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:456)
at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:82)
at org.apache.logging.log4j.core.Logger.log(Logger.java:161)
at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2205)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2159)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2142)
at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:2017)
at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1983)
at org.apache.logging.log4j.spi.AbstractLogger.error(AbstractLogger.java:740)
- 調(diào)用棧是最好的源碼分析工具。
- 核心的關(guān)鍵類(lèi)包括MessagePatternConverter、StrSubstitutor、JndiLookup、JndiManager。
源碼分析
MessagePatternConverter
public final class MessagePatternConverter extends LogEventPatternConverter {
private MessagePatternConverter(final Configuration config, final String[] options) {
super("Message", "message");
this.formats = options;
this.config = config;
final int noLookupsIdx = loadNoLookups(options);
// Constants.FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS
// 變量代表log4j2.formatMsgNoLookups
this.noLookups = Constants.FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS || noLookupsIdx >= 0;
this.textRenderer = loadMessageRenderer(noLookupsIdx >= 0 ? ArrayUtils.remove(options, noLookupsIdx) : options);
}
public void format(final LogEvent event, final StringBuilder toAppendTo) {
final Message msg = event.getMessage();
if (msg instanceof StringBuilderFormattable) {
final boolean doRender = textRenderer != null;
// 省略多余的代碼
// 根據(jù) noLookups 的值避免執(zhí)行該代碼分支
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);
// config.getStrSubstitutor().replace(event, value)進(jìn)行占位符替換
workingBuilder.append(config.getStrSubstitutor().replace(event, value));
}
}
}
// 省略多余的代碼
return;
}
}
}
- config.getStrSubstitutor().replace(event, value)負(fù)責(zé)執(zhí)行 jndi 命令的解析。
- 通過(guò)StrSubstitutor進(jìn)行命令的解析。
- 通過(guò)變量noLookups來(lái)判定是否走代碼分支,通過(guò)設(shè)置log4j2.formatMsgNoLookups為 true 不會(huì)走入該代碼分支。
StrSubstitutor
public class StrSubstitutor implements ConfigurationAware {
protected String resolveVariable(final LogEvent event, final String variableName, final StringBuilder buf,
final int startPos, final int endPos) {
// Interpolator
final StrLookup resolver = getVariableResolver();
if (resolver == null) {
return null;
}
return resolver.lookup(event, variableName);
}
}
public class Interpolator extends AbstractConfigurationAwareLookup {
public static final char PREFIX_SEPARATOR = ':';
private static final String LOOKUP_KEY_WEB = "web";
private static final String LOOKUP_KEY_DOCKER = "docker";
private static final String LOOKUP_KEY_KUBERNETES = "kubernetes";
private static final String LOOKUP_KEY_SPRING = "spring";
private static final String LOOKUP_KEY_JNDI = "jndi";
private static final String LOOKUP_KEY_JVMRUNARGS = "jvmrunargs";
private static final Logger LOGGER = StatusLogger.getLogger();
private final Map<String, StrLookup> strLookupMap = new HashMap<>();
private final StrLookup defaultLookup;
@Override
public String lookup(final LogEvent event, String var) {
final int prefixPos = var.indexOf(PREFIX_SEPARATOR);
if (prefixPos >= 0) {
final String prefix = var.substring(0, prefixPos).toLowerCase(Locale.US);
final String name = var.substring(prefixPos + 1);
// 查找對(duì)應(yīng)的StrLookup對(duì)象,這里返回的是 jndiLookup
final StrLookup lookup = strLookupMap.get(prefix);
if (lookup instanceof ConfigurationAware) {
((ConfigurationAware) lookup).setConfiguration(configuration);
}
String value = null;
if (lookup != null) {
// 執(zhí)行jndiLookup的 lookup 方法
value = event == null ? lookup.lookup(name) : lookup.lookup(event, name);
}
if (value != null) {
return value;
}
}
}
}
- Interpolator#lookup內(nèi)部 ladp 關(guān)鍵字從strLookupMap查找JndiLookup對(duì)象。
- 執(zhí)行JndiLookup的 lookup 去解析 jndi 命令。
JndiLookup
public interface StrLookup {
String CATEGORY = "Lookup";
String lookup(String key);
String lookup(LogEvent event, String key);
}
public abstract class AbstractLookup implements StrLookup {
@Override
public String lookup(final String key) {
return lookup(null, key);
}
}
@Plugin(name = "jndi", category = StrLookup.CATEGORY)
public class JndiLookup extends AbstractLookup {
private static final Logger LOGGER = StatusLogger.getLogger();
private static final Marker LOOKUP = MarkerManager.getMarker("LOOKUP");
static final String CONTAINER_JNDI_RESOURCE_PATH_PREFIX = "java:comp/env/";
@Override
public String lookup(final LogEvent event, final String key) {
if (key == null) {
return null;
}
final String jndiName = convertJndiName(key);
try (final JndiManager jndiManager = JndiManager.getDefaultManager()) {
// 調(diào)用jndiManager的lookup方法
return Objects.toString(jndiManager.lookup(jndiName), null);
} catch (final NamingException e) {
return null;
}
}
}
- JndiLookup的 lookup 執(zhí)行的是JndiManager的lookup。
JndiManager
public class JndiManager extends AbstractManager {
public <T> T lookup(final String name) throws NamingException {
// 這里的context為InitialContext
return (T) this.context.lookup(name);
}
}
public class InitialContext implements Context {
public Object lookup(String name) throws NamingException {
// 返回的ldapURLContext對(duì)象并執(zhí)行 lookup操作。
return getURLOrDefaultInitCtx(name).lookup(name);
}
protected Context getURLOrDefaultInitCtx(String name)
throws NamingException {
if (NamingManager.hasInitialContextFactoryBuilder()) {
return getDefaultInitCtx();
}
// 返回ldapURLContext的對(duì)象
String scheme = getURLScheme(name);
if (scheme != null) {
Context ctx = NamingManager.getURLContext(scheme, myProps);
if (ctx != null) {
return ctx;
}
}
return getDefaultInitCtx();
}
}
- JndiManager的lookup會(huì)通過(guò)getURLOrDefaultInitCtx的返回ldapURLContext對(duì)象。
ldapURLContext
public final class ldapURLContext extends GenericURLDirContext {
public Object lookup(String var1) throws NamingException {
if (LdapURL.hasQueryComponents(var1)) {
throw new InvalidNameException(var1);
} else {
// 執(zhí)行l(wèi)dapURLContext的 lookup 方法
return super.lookup(var1);
}
}
}
- com.sun.jndi.url.ldap.ldapURLContext已經(jīng)是 jdk 的源碼,說(shuō)明已經(jīng)進(jìn)入到 jdk 的執(zhí)行邏輯。
- 整體的流程也就分析到此為止。