摘要:log4j問題的餘波還在繼續,為什麼這個問題潛伏了這麼長時間,大家一直沒有發現?這裡從靜態分析的角度談下log4j問題的發現。
本文分享自華為雲社群《使用汙點分析檢查log4j問題》,作者: Uncle_Tom。
1. JNDI注入
這次log4j的問題主要是由於JNDI問題造成的,先介紹下JNDI。
1.1. JNDI
- JNDI是介面服務
Java Naming and Directory Interface(JNDI) 是一個應用程式程式設計介面 (API),它為使用 Java程式語言編寫的應用程式提供命名和目錄功能。各種目錄服務都可以以一種通用的、統一的介面方式訪問。 - JNDI架構
JNDI 架構由 API 和服務提供者介面 (service provider interface(SPI))組成。 Java 應用程式使用 JNDI API 來訪問各種命名和目錄服務。 SPI 可以透明地插入各種命名和目錄服務,從而允許使用 JNDI API 的 Java 應用程式訪問它們的服務。見下圖:
- JNDI提供的服務
JNDI 包含在 Java SE 平臺中。要使用 JNDI,必須使用JNDI類和一個或多個服務提供者。 JDK 包括以下命名/目錄服務的服務提供者: - 輕量級目錄訪問協議 (Lightweight Directory Access Protocol (LDAP));
- 域名服務 (Domain Name Service (DNS))。
- 網路資訊服務(Network Information Service(NIS));
- 名稱服務 Java 遠端方法呼叫(Java Remote Method Invocation (RMI));
- 通用物件請求代理體系結構 (Common Object Request Broker Architecture (CORBA)) 通用物件服務 (Object Services (COS))。
- JNDI的Java實現
JNDI程式包: - javax.naming:包含了訪問命名服務的類和介面。例如,它定義了Context介面,這是命名服務執行查詢的入口。
- javax.naming.directory:對命名包的擴充,提供了訪問目錄服務的類和介面。例如,它為屬性增加了新的類,提供了表示目錄上下文的DirContext介面,定義了檢查和更新目錄物件的屬性的方法。
- javax.naming.event:提供了對訪問命名和目錄服務時的事件通知的支援。例如,定義了NamingEvent類,這個類用來表示命名/目錄服務產生的事件,定義了偵聽NamingEvents的NamingListener介面。
- javax.naming.ldap:這個包提供了對LDAP 版本3擴充的操作和控制的支援,通用包javax.naming.directory沒有包含這些操作和控制。
- javax.naming.spi:這個包提供了一個方法,通過javax.naming和有關包動態增加對訪問命名和目錄服務的支援。這個包是為有興趣建立服務提供者的開發者提供的。
- JNDI上下文(javax.naming.Context)
- javax.naming 包定義了一個 Context 介面,它是查詢、繫結/解除繫結、重新命名物件以及建立和銷燬子上下文的核心介面。
- lookup: 最常用的操作是lookup()。通過lookup()查詢物件的名稱,它返回繫結到該名稱的物件。
- Bindings:listBindings() 返回名稱到物件繫結的列舉。繫結是一個元組,包含繫結物件的名稱、物件類的名稱和物件本身。
- list: list() 與 listBindings() 類似,不同之處在於它返回包含物件名稱和物件類名稱的名稱列舉。 list() 對於諸如瀏覽器之類的應用程式很有用,這些應用程式希望發現有關繫結在上下文中的物件的資訊,但不需要所有實際物件。儘管 listBindings() 提供了所有相同的資訊,但它可能是一個更昂貴的操作。
- Name: Name 是一個表示通用名稱的介面, 包含零個或多個元件的有序序列。命名系統使用此介面來定義遵循其約定的名稱。
- JNDI初始化
在JNDI中,所有命名和目錄操作都是相對於上下文執行的,沒有絕對的根路徑。因此,JNDI 定義了一個 InitialContext,它為命名和目錄操作提供了一個起點。通過初始的上下文,就可以使用它來查詢其他上下文和物件。
1.2. JNDI注入
JNDI 通過InitialContext.lookup(String name)一個字串引數進行初始化,如果該引數來自不受信任的源,則可能通過遠端類載入導致遠端程式碼執行。當請求物件的名稱由攻擊者控制時,可能會將受害者Java應用程式指向惡意RMI/LDAP/CORBA伺服器,並使用任意物件進行響應。
1.3. log4j的JDNI注入
本次log4j的JNDI注入問題,就是因為當log4j的日誌輸出為"${xxx}"的時候,log4j會認為這是個JNDI呼叫的標誌,並將xxx解析為JNDI資源後,載入執行。如果xxx為惡意服務地址的時候,危害也就發生了。
2. 汙點分析
汙點分析(Taint analysis)可以被看作是資訊流分析(Information Flow Analysis)的一種,主要是追蹤資料在程式中的走向。
在漏洞分析中,使用汙點分析技術將所感興趣的資料(通常來自程式的外部輸入)標記為汙點資料,然後通過跟蹤和汙點資料相關的資訊的流向,可以知道它們是否會影響某些關鍵的程式操作,進而挖掘程式漏洞。即將程式是否存在外部輸入導致漏洞的問題,轉化為汙點資訊是否會被 Sink 點上的操作所使用的問題。
汙點分析技術目前主要有靜態和動態分析兩種方式。本文主要講述通過靜態分析工具所採用的靜態分析方法。
2.1. 汙點分析的主要概念
通常外部輸入資料都被認為是一種可能引起安全風險的源頭,被稱為汙染源。這些資訊被程式的接收或處理函式收到後,在程式內部通過程式內部的各個功能模組被加工、處理或呼叫某些外部介面執行。這些資訊如果未做有效的檢驗就可能造成各類安全風險。
下圖是汙染分析在分析汙染傳播的過程中所經歷的主要過程,這個過程被劃分為:汙染源、汙染傳播、汙染清理、汙染爆發。
2.1.1. 汙染場景
- 資料流入系統
外部汙染通常由信任域意外傳入信任域內,會導致系統執行的問題。主要有: - 外部輸入:使用者介面的輸入、外部發來的請求電文、資料庫讀取的資訊;
- 環境和設定資訊:在不安全的環境中執行時,需要從環境中讀入的資訊。例如:環境變數、配置檔案等;
- 資料內部流轉
在程式內部,會有一些因為設計或編碼過程中引入的資料範圍不當設定或判斷,最終導致安全問題。例如除零、陣列越界等。 - 資料流出系統
信任區域內的關鍵或需要保密的資訊,被洩露到信任域外的場景一樣可以通過汙點分析技術進行分析。例如: - 系統內部資訊:系統內部的資訊往往會暴漏系統的內部結構,這些資訊給外部攻擊提供了明確的嗅探目標。例如:日誌的輸出的包含堆疊資訊的異常資訊等;
- 敏感資訊:敏感資訊是需要嚴格保護的,這些資訊的洩露也會給系統帶來巨大的損失。例如:個人身份資訊、個人財產資訊、個人健康資訊等。
2.1.2. 汙染源
汙染的資訊會通過應用軟體特定的函式被引入到應用系統中,這也是汙染分析的起點。靜態分析工具中,通常採用下面的方法完成汙染源的定義:
- 通過配置檔案完成不同外部汙染源的定義,方便靜態分析工具在分析時,可配置的載入和分析;
- 配置檔案通過正規表示式或直接指定具體函式的方式定義汙染源,通過這種方式匹配程式中使用的函式;
- 汙染源函式的匹配主要包括:名稱空間、類名、函式名;
- 汙染源通過函式向下傳播的具體變數。通常被定義為匹配到函式的某個引數或返回值;
- 對於物件導向的程式,需要考慮是否適用於這個匹配函式的繼承或派生函式;
- 通常還會給每個定義加上不同的汙染標記,以便在汙染清理時,做出不同型別汙染的清理判斷。
2.1.3. 汙染傳播
汙染傳播是汙染分析中最複雜的一個階段,主要分為顯示分析和隱式分析。
- 顯示分析:顯式分析就是分析汙點標記如何隨程式中變數之間的資料依賴關係傳播。
這個分析主要是通過依賴圖完成汙染的在賦值操作、表示式處理、陣列/結構體賦值等函式內的傳遞分析;再依託函式呼叫圖完成函式間的傳播。 - 隱式分析:是分析汙點標記如何隨程式中變數之間的控制依賴關係傳播,汙點標記如何從條件指令傳播到其所控制的語句,即汙點通過控制執行的分支,達到汙染傳播的目的。
在顯示分析和隱式分析中,都不可避免的會遇到:內建函式、第三方函式,以及應用框架的問題。
- 內建函式:由於內建函式沒有程式碼,當汙染進入某個引數是,分析程式無法知道這個函式的行為,汙染是被另一個引數傳遞出去了?還是被返回值傳遞出去了?
- 第三方函式:應用程式通常是以模組的方式完成應用系統組合,特別是目前的應用系統大量引用第三方的模組。當第三方函式是以包的方式引入的時候,就會遇到和內建函式一樣的問題,分析程式不知道這些函式的行為。
- 應用框架:為了降低應用系統開發的難度,通常我們會採用一些成熟的開發框架。這些框架為了應用的靈活性,往往也不直接實現程式碼間的呼叫,而是通過配置的方式。例如spring框架中事件的驅動方式,是通過配置完成了不同模組間的銜接,這些對於依賴原始碼的靜態分析來說,無法適配彼此間的呼叫關係。
內建函式、第三方函式,以及應用框架都會造成分析中斷的問題,從而使汙染分析不能有效的進行。為了解決這個問題,靜態分析軟體需要定義這些函式,幫助分析程式完成汙染傳播的繼續分析。
這些函式的定義方式基本和汙染源的定義方式相同,採用配置的方式匹配到這些函式,同時還需要明確這些函式汙染傳入的引數和傳出的引數。以方便靜態分析軟體在碰到這些函式時,根據傳出引數繼續分析。
2.1.4. 汙染清理
在程式設計的過程中,如果對於不安全的外部輸入,已經做了相應的處理,但如果靜態分析工具無法知道這些檢查和處理,讓然給出告警,將成為誤報,反而會增加分析的成本。所以在程式分析的過程中需要對汙染清理做出分析,以便減少不必要的誤報,同時還可以提高分析效率。
汙染清理主要分為兩種處理方式:
- 有效的條件判斷:對輸入值的範圍做了條件判斷,以便剔除不安全的輸入;
- 清理函式:對於一些複雜的判斷通過一些函式來完成。這包括:加密函式、轉義函式等;
對於清理函式的處理,同樣可以採用配置的方式完成。只要對適配到的函式,做汙染標記清楚就可以了,使靜態分析工具在經過這些函式不再帶有繼續分析的標記,終止後續的分析。
2.1.5. 汙染爆發
當汙染經過傳播仍然進入到一些特定的函式或語句時,就會引發安全漏洞的發生。這些安全漏洞主要有:
- 輸入驗證不當 - (20)
例如:輸入中指定數量的不正確驗證 - (1284); 輸入中指定索引、位置或偏移量的不正確驗證 - (1285)等。
- 可索引資源的錯誤訪問(“範圍錯誤”) - (118)
例如:不檢查輸入大小的緩衝區複製(“經典緩衝區溢位”) - (120); 整數溢位導致緩衝區溢位 - (680)等。
- 下游元件使用的輸出中特殊元素的轉義處理不恰當(“注入”) - (74)
例如:在命令中使用的特殊元素轉義處理不恰當(“命令注入”) - (77); SQL 命令中使用的特殊元素轉義處理不恰當(“SQL 注入”) - (89)等。
- 動態管理程式碼資源控制不當 - (913)
例如:使用外部控制輸入來選擇類或程式碼(“不安全反射”) - (470);不可信資料的反序列化 - (502), 這次的log4j JNDI問題被NVD定義的CWE。
- 資訊洩露
- 將敏感資訊暴露給未經授權的使用者 - (200)
- 敏感資訊的不安全儲存 - (922)
3. log4j的問題分析
3.1. 測試程式碼
- 下載程式碼
$ git clone --branch rel/2.14.1 https://gitbox.apache.org/repos/asf/logging-log4j2.git
- 一個測試程式碼
log4j的debug、info、warn、error、fatal都是汙染源,
package org.apache.logging; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class testJndi { private static final Logger LOG = LogManager.getLogger(); public static void main (String[] args){ LOG.error("${jndi:xxx}"); } }
3.2. 呼叫鏈
- 汙染分析設定:
- 汙染源: org.apache.logging.log4j.Logger.error函式,且第一個引數中包含"${" 和 “}”,加這個約束的目的是降低工具的分析難度;
- 爆發點: JNDI主要的的呼叫函式:javax.naming.Context.lookup()函式,且汙染進入這個函式的第一個引數,同時查詢繼承和派生這個函式的函式;
- 汙染傳播:java的內建函式,例如這個問題裡用到的String.substring() 等等;
- 靜態分析工具完整的分析鏈如下:
圖很長,並做了節選,拆成兩段:
圖1
圖2
4. 思考
- 從分析鏈路來看,log4j問題經過了非常複雜的鏈路才傳到最後的汙染爆發點,一共涉及了10幾個檔案之間的呼叫,這裡還沒有放入更詳細的類圖之間的關係,可見是一個非常複雜的問題;
- 汙染傳播的鏈路中,任何的呼叫(跨函式、類繼承等)或內建函式的不適配,都將導致無法檢查出這個問題;
- 通常情況下,工具不會將log做為外部輸入,將其設定成汙染源;如果從外部輸入傳入log,將會使分析鏈路更長,分析的難度也會更大;
- 第三方的軟體通常以包的形式引入開發工程,做靜態檢查的時候不會以原始碼的方式連結到檢查程式碼中。對於靜態分析,也就很難發現其中的問題;
- 這樣複雜和深度的檢查,是開源靜態檢查工具能力所無法到達的地方,即使是商用工具也都非常困難;
- 隨著大家對軟體安全的重視,比較淺層的安全問題相對比較容易發現,剩下深層次的安全問題,就需要更加強大的靜態分析工具,特別需要加大在安全檢查能力上的佈局。
5. 參考
- log4j網站: https://logging.apache.org/log4j
- Exploiting JNDI Injections in Java: https://www.veracode.com/blog/research/exploiting-jndi-injections-java
- Lesson: Overview of JNDI: https://docs.oracle.com/javase/tutorial/jndi/overview/index.html