Log4Shell和JNDI注入的當前狀態

最新爆發的Log4j2安全遠端漏洞,又稱“Log4Shell”,讓整個網際網路陷入了威脅之中,大量企業和Java專案都在緊鑼密鼓的升級更新補丁,還有很多安全研究人員在研究復現和利用以及防範方法,我們今天就來說說相關的常識和進展。

Log4Shell和JNDI注入的當前狀態

Log4Shell漏洞(正式編號CVE-2021-44228) 歸根結底是一個非常簡單的JNDI注入漏洞。Log4J在擴充套件佔位符時在記錄訊息時候(或間接作為格式化訊息的引數)執行JNDI lookup()操作。在預設配置中,JNDI支援兩種協議:RMI和LDAP。在這兩種情況下,lookup()的呼叫實際上是為了返回一個Java物件。這通常為序列化的Java物件,但是還有一個通過於間接構造的JNDI引用。這個物件和引用位元組碼可以透過遠端URL載入代(java類。class)。

關於JNDI和JNDI注入

JNDI,全稱Java Naming and Directory Interface(Java命名和目錄介面) 是Java中引入一種Java API,它允許客戶端透過名稱發現查詢和共享Java資料和物件。這些物件可以儲存在不同的命名或目錄服務中,例如遠端方法呼叫(RMI)、通用物件請求代理架構(CORBA)、輕量級目錄訪問協議(LDAP)或域名服務(DNS)等。

Log4Shell和JNDI注入的當前狀態

換句話說,JNDI是一個簡單的Java API,例如:InitialContext。lookup(String name)

它只接受一個字串引數,如果該引數來自不信任的來源,則可能會有遠端程式碼的載入和執行。

當請求物件的名稱被攻擊者控制時,有可能將存在問題的Java應用程式指向惡意的rmi/ldap/corba 伺服器並使用任意物件進行響應。如果該物件是“javax。naming。Reference”類的例項,則JNDI客戶端會嘗試解析此物件的“classFactory”和“classFactoryLocation”屬性。如果目標Java應用程式不知道“classFactory”值,Java將使用Java URLClassLoader 從“classFactoryLocation”位置獲取Factory位元組碼。

Log4Shell和JNDI注入的當前狀態

由於它的簡單性,即使“InitialContext。lookup”方法沒有直接暴露到受汙染的資料,它對於利用Java漏洞也非常有用。在某些情況下,它仍然可以透過反序列化或不安全反射攻擊來訪問。

一段易受攻擊的應用程式示例如下:

Log4Shell和JNDI注入的當前狀態

Java 8u191之前JNDI注入

透過請求“/lookup/?name=ldap://127。0。0。1:1389/Object”的連結,可以讓易受攻擊的伺服器連線到控制的地址,並觸發遠端類載入。

一個惡意RMI例項服務示例如下:

由於目標伺服器不知道“ExploitObject”,它的位元組碼將從_attacke_指定的服務載入並執行觸發RCE遠端執行。

該方法在Java 8u121 中可良好執行,在Java 8u191 更新中,Oracle對LDAP添加了的限制,併發布了CVE-2018-3149補丁,關閉了JNDI遠端類載入。然而,仍然有可能透過JNDI注入觸發不可信資料的反序列化,但其利用高度依賴於現有的小工具。

Java 8u191+中利用JNDI注入

從Java 8u191開始,當JNDI客戶端接收到一個Reference物件時,無論是在RMI還是在LDAP中,都不會使用其“classFactoryLocation”值。但是,仍然可以在“javaFactory”屬性中指定任意工廠類。

Log4Shell和JNDI注入的當前狀態

public interface ObjectFactory {

public Object getObjectInstance(Object obj, Name name, Context nameCtx,

Hashtable environment)

throws Exception;

}

Log4Shell和JNDI注入的當前狀態

Log4Shell和JNDI注入的當前狀態

Log4Shell和JNDI注入的當前狀態

“BeanFactory”類建立任意bean的例項併為所有屬性呼叫其設定器。目標bean 類名、屬性和屬性值都來自被攻擊者控制的Reference物件。

目標類應該有一個公共的無引數建構函式和只有一個“字串”引數的公共設定器。事實上,這些 setter 可能不一定從 ‘set。。’ 開始,因為“BeanFactory”包含一些為任何引數指定任意setter名稱的邏輯。

Log4Shell和JNDI注入的當前狀態

這裡使用的魔法屬性是“forceString”。例如,透過將其設定為“x=eval”,我們可以對屬性“x”進行名稱為“eval”而不是“setX”的方法呼叫。

因此,透過利用“BeanFactory”類,我們可以使用預設建構函式建立任意類的例項,並使用一個“String”引數呼叫任何公共方法。

此處可能有用的類之一是“javax。el。ELProcessor”。在它的“eval”方法中,可以指定一個字串來表示要執行的Java表示式語言模板。

Log4Shell和JNDI注入的當前狀態

一個在評估時執行任意命令的惡意表示式:

編寫一個示例的RMI伺服器,它以精心製作的“ResourceRef”物件進行響應:

然後在受害Java程序上觸發JNDI解析:

總結

實際問題不在JDK或Apache Tomcat=中,而是由於使用者將不可控資料傳遞給“InitialContext。lookup()”函式的自定義應用程式中。即使使用最新的漏洞完全修補的JDK中,它存在潛在的安全風險。

在許多情況下,其他漏洞(例如“不受信任資料的反序列化”)也可能導致JNDI解析。透過使用原始碼審查來防止這些漏洞始終是一個好主意。

長期以來,對於RMI和LDAP,的引用沒有做任何限制。這樣對攻擊者指定的JNDI RMI或LDA 名稱的lookup呼叫就會導致直接的遠端程式碼執行。

自Java 8u121開始,RMI協議(但不是LDAP)預設不再允許遠端程式碼庫。

LDAP之前有一個補丁(CVE-2009-1094),但這完全是對引用物件無效。因此,LDAP仍然允許直接遠端執行程式碼。直到Java 8u191的 CVE-2018-3149漏洞補丁中才解決。

Java 8u191版本

之前,都存在從受控JNDI lookup遠端類載入任意程式碼執行的風險。

但是新版本中RMI引用和工廠構造物件仍未被去除,只是禁止遠端程式碼庫。可以透過Apache XBean BeanFactory 返回的引用來實現遠端程式碼執行。只要該類在目標系統上本地可用既可以,例如被包含到Tomcat或者 WebSphere中則仍然有利用的可能。

另外,RMI本質上是基於Java序列化的,而LDAP支援一個特殊的物件類,從目錄中反序列化Java物件從lookup()返回。在這兩種情況下,除非應用了全域性反序列化過濾器,否則JNDI注入將會導致反序列化不受信任的攻擊者提供的資料。雖然有一定的攻擊的複雜性,在許多情況下,仍然可用於遠端程式碼執行。

總之,不要依賴當前Java版本來解決這個問題,需要及時更新Log4j(或刪除JNDI lookup),或者禁用JNDI擴展才是完全的解決方案(可能不太現實)。