作者:啟明星辰 ADLab
原文鏈接:https://mp.weixin.qq.com/s/19oIId_Ax2nxJ00k6vFhDg

漏洞編號:CVE-2021-44228
CNVD編號:CNVD-2021-95919
發布時間:2021年12月12日

漏洞概述

Apache log4j2是一款Apache軟件基金會的開源基礎框架,用于 Java 日志記錄的工具。日志記錄主要用來監視代碼中變量的變化情況,周期性地記錄到文件中供其他應用進行統計分析工作;跟蹤代碼運行時軌跡,作為日后審計的依據;擔當集成開發環境中的調試器的作用,向文件或控制臺打印代碼的調試信息。其在JAVA生態環境中應用極其廣泛,影響巨大。

近日, Apache Log4j2 被曝存在JNDI遠程代碼執行漏洞,該漏洞一旦被攻擊者利用會造成嚴重危害。該漏洞的觸發點在于利用org.apache.logging.log4j.Logger進行log或error等記錄操作時未對日志message信息進行有效檢查,從而導致漏洞發生。

漏洞時間軸

2014年7月13日:Apache Log4j2官方發布log4j-2.0此時該漏洞已經存在,距今7年之久;

2021年11月24日:阿里云安全團隊向Apache官方報告了ApacheLog4j2遠程代碼執行漏洞(CVE-2021-44228);

2021年12月8日:Apache Log4j2官方發布log4j2-2.15.0-rc1并第一次修復CVE-2021-44228漏洞;

2021年12月9日:啟明星辰ADLab監測到Apache Log4j2官方公告并開展驗證;

2021年12月10日:啟明星辰ADLab確認漏洞存在,成功復現該漏洞并通報主管單位;

2021年12月10日:啟明星辰ADLab研究確認log4j2-2.15.0-rc1存在Bypass的漏洞;

2021年12月10日:Apache Log4j2官方發布log4j2-2.15.0-rc2修復bypass漏洞。

影響版本

Apache Log4j 2.x < =2.15.0-rc1

漏洞詳情

漏洞觸發的調用鏈如下:

在進行Logger.log()日志記錄時會采用logIfEnabled()方法進行判斷,返回為true才可以繼續進行日志操作。這里也是漏洞能否成功觸發的關鍵。

在本次漏洞分析過程中日志等級為Level.FATAL,它的intLevel()為100,而本環境中默認的日志級別為ERROR(200),如下圖所示:

此時filter方法返回true,成功進入到logMessage的方法中。這里當Level等級優先級高于或等于ERROR(200)時(等級越高數值越低)才能觸發漏洞;如WARN、INFO、DEBUG、ALL優先級低于ERROR(200),則無法觸發該漏洞。也就是說,漏洞能否成功觸發與設置的日志Level有關。

然后,來到org.apache.logging.log4j.core.pattern.MessagePatternConverter的format()這個關鍵方法中,首先要判斷noLookups這個變量的值,noLookups默認為false,意思為開啟JNDI格式化功能。如下圖所示:

我們根據之前的分析已經知道了noLookups這個變量的值為false的,!false的值為真,那么就進入到for循環里繼續對log的message event進行處理,取出${}中的數據進行后續lookup()操作。

org.apache.logging.log4j.core.lookup.Interpolator的lookup()方法包含多種處理event的途徑,根據event的prefix選擇相應的StrLookup進行處理。包括jndi、date、Java、main等。

當構造的event的prefix為jndi時,則通過org.apache.logging.log4j.core.lookup.JndiLookup的lookup()方法處理,從而觸發JNDI漏洞利用。

幾點疑問的解答

1.Log4j 1.x版本是否存在該漏洞

Log4j 1.x的最高版本為1.2.17,以下將1.x表示為小于等于1.2.17的所有版本。由于Log4j 1.x的代碼和Log4j2相差比較大,沒有Log4j2相對應的功能,所以Log4j 1.x版本不存在CVE-2021-44228漏洞。

通過深入分析源碼后發現,如果我們能夠控制配置文件log4j.properties中的內容,也可以達到JNDI注入的效果。示例如下:

成功利用如下圖所示:

2.只要用了Log4j2就會有漏洞嗎

有兩個限制條件,第一個限制條件是:默認情況下,代碼中日志等級的優先級高于或等于默認級別(ERROR(200),數值越低優先級越高)才可以成功,所以OFF、FATAL、ERROR均可被利用。如下圖所示:

但若是項目中采用配置文件的形式自定義日志級別,以Apache Struts2 為例,

Apache Struts2 默認日志級別為:info。如下圖所示:

通過調試Struts2 Showcase,部署后獲取到的初始日志級別為:info(400)。

當我們日志等級優先級高于或等于info(400)時,WARN、ERROR、FATAL、OFF、INFO這幾個級別的日志都可能被利用,Apache Struts2存在漏洞的位置如下圖所示。

第二個限制條件是:必須能夠控制日志內容。測試代碼如下:logger.log(Level.ERROR,"xxx");只有當log方法的第二個參數xxx的內容被污染的情況下才能被成功利用。

3.用了高版本JDK,漏洞就免疫了嗎

看了一些大家對這個漏洞的討論,很多人以為JDK版本高了就免疫了,某個網友評論如下圖所示:

在某些特定情況下,比如中間件用的Tomcat、Websphere,又比如存在反序列化漏洞的依賴包,攻擊者同樣可以繞過高版本JDK的限制達到RCE的目的。雖然高版本JDK對這個漏洞的防御不是那么完美,但仍強烈建議選用高版本的JDK,至少能提高黑客入侵的門檻。

4.Log4j2 2.15.0-rc1補丁安全嗎

默認配置是安全的,因為默認已經不會通過lookup來處理日志內容,但如果配置文件由于配置不當,仍然有被利用的可能。比如以下配置即存在風險:

5.Log4j2 2.15.0-rc2補丁安全嗎

rc1被繞過了,rc2是不是也能被繞過?結論是目前是安全的,因為rc2有諸多限制,如協議、白名單HOST、LDAP Attributes限制等多個限制,其中白名單HOST限制如下所示:

6.單一WAF防御策略可行嗎

有些人覺得補漏洞太麻煩了,而且升級最新版本不兼容,更換版本后錯誤百出,不如用WAF來防御,阻斷ldap、jndi這些關鍵字。目前針對繞過WAF的方法已經有很多種了,僅使用WAF攔截肯定會出現問題。

7.加固了,為何仍存在漏洞

我們看到早期文章加固方案基本按照下面這樣寫的:

(1)jvm參數 -Dlog4j2.formatMsgNoLookups=true

(2)在應用程序的classpath下添加log4j2.component.properties配置文件文件,文件內容:log4j2.formatMsgNoLookups=True

(3)設置系統環境變量FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS 設置為true

很多人按照這個方案去加固,最后看到的結果是漏洞依然存在。這里有兩點需要注意:

(1)這三個修復方案在2.10以下均處于失效狀態。如果你用的2.10以下的版本,這樣來加固肯定是不可以的。

(2)2.10版本以上,為什么也不成功。原因是設置環境變量FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS為true并不會關閉lookup,而正確的修復方案是將LOG4J_FORMAT_MSG_NO_LOOKUPS設置為true。下面從代碼層面詳細分析為什么會這樣。

通過跟蹤代碼發現,在初始化LogManager的Context環境時,會將系統環境變量加入到Log4j自己的屬性環境中。當系統環境變量的key值前綴為“LOG4J_”時,自動截取“LOG4J_”之后的部分進行Util.tokenize(key)處理后加入到Environment的tokenized中。

Util.tokenize()方法對參數key進行正則匹配處理,然后返回處理后的List結果。正則匹配的規則為:

private static final Pattern PROPERTY_TOKENIZER = Pattern.compile("(?i:^log4j2?[-./]?|^org\.apache\.logging\.log4j\.)?([A-Z]*[a-z0-9]+|[A-Z0-9]+)[-./]?");

那么,如何設置key值才能成功讓PropertiesUtil.getProperties().getBooleanProperty("log4j2.formatMsgNoLookups", false);返回值為true呢?

跟蹤代碼發現,getBooleanProperty()獲取屬性也是通過查找PropertiesUtil.Environment中的normalized、literal和tokenized是否存在該屬性判斷的。而通過tokenized查找時,Util.tokenize(key)處理后的值作為key值然后在tokenized中進行匹配。

我們再來看系統環境變量LOG4J_FORMAT_MSG_NO_LOOKUPS,去掉前綴LOG4J_后經Util.tokenize()處理后的結果如下圖所示:

因此成功將Constants.FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS = PropertiesUtil.getProperties().getBooleanProperty("log4j2.formatMsgNoLookups", false);的結果設置為true,進而達到防御效果。

這里的系統環境變量設置為LOG4J_FORMAT_MSG_NO_LOOKUPS=true

其實只要滿足正則匹配規則就可以,例如:

LOG4J_FORMAT_MSG_NO_LOOKUPS=true

LOG4J_log4j2_formatMsgNoLookups=true

LOG4J_formatMsgNoLookups=true

總結一下這三種方案只適用于 >=2.10版本,2.10以下的版本無效。FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS不符合正則規則所以無效,而環境變量LOG4J_FORMAT_MSG_NO_LOOKUPS匹配成功,可以成功關閉lookup。

總結:

總的來說這個漏洞影響面極廣,同時利用難度很低,目前啟明星辰ADLab確認受該漏洞影響的產品應用有:Ghidra、Apache James、VMware多應用、Apache Solr、Apache Druid、Apache Flink、Apache Struts2、Dubbo。其它存在該漏洞的系統或應用也會逐漸浮出水面。

注意:其中很多應用的利用代碼已經被公布出來,希望引起大家足夠重視。

最終方案:

1.升級最新版本,目前最新版本為log4j-2.16.0,相比log4j-2.15.0修復了其它安全問題,在業務許可的情況下建議升級log4j-2.16.0-rc1。 2. 棄用log4j 1.x版本,因為漏洞太多,并且無法更新升級。

臨時方案:

1.對于Log4j 1.x版本

移除JMSAppender.class文件,命令為:

zip -q -d log4j-1.x.jar org/apache/log4j/net/JMSAppender.class(使用該方案時需經過測試,避免對實際業務產生影響)

2.對于log4j2 >=2.10的版本(以下三種方案任選其一):

1)添加log4j2.component.properties配置文件,增加如下內容為:

log4j2.formatMsgNoLookups=true

2)設置 jvm 參數: -Dlog4j2.formatMsgNoLookups=true

3)設置系統環境變量:LOG4J_FORMAT_MSG_NO_LOOKUPS=true

3.對于log4j2 <2.10的版本:

可以通過移除JndiLookup類的方式,命令為:

zip -q -d log4j-core-2.x.jar org/apache/logging/log4j/core/lookup/JndiLookup.class

參考鏈接

https://www.cnvd.org.cn/webinfo/show/7116

https://github.com/apache/logging-log4j2/releases/tag/log4j-2.16.0-rc1

https://gist.github.com/SwitHak/b66db3a06c2955a9cb71a8718970c592


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1787/