Log4j2漏洞簡單分析
之前在實習寫的文章都投到了公司平臺,沒有在這邊更新,后面會在freebuf上重新更新記錄。這個也是去年剛出這個漏洞的時候分析的,沒有發(fā)布在外面平臺。
一、環(huán)境搭建
1.引入jar包
使用idea新建項目并且引入jar包,這里需要兩個包log4j-core和log4j-api。在https://logging.apache.org/log4j/2.x/download.html下載編譯好的Apache Log4j jar包 或者網(wǎng)盤下載 鏈接:https://www.aliyundrive.com/s/NQY9Lj5Nbfq。
在file處點擊Project Structure來添加下載的jar包。

完成后可以在這里看到添加的包

2.添加漏洞代碼
二、漏洞復現(xiàn)
1.使用jndi注入工具起一個服務,這里我使用https://github.com/welk1n/JNDI-Injection-Exploit這個工具,在本地執(zhí)行開啟Typora的命令

因為我這里是1.8的環(huán)境所以選擇框出來的這個路徑

運行代碼即可執(zhí)行命令

服務接受到連接信息

根據(jù)這個思路我們可以不使用dnslog類平臺來檢測,github有一個burp的插件項目依靠查看ldap服務是否有連接進來來判斷是否存在漏洞,使用這種方法就可以在本機或vps上起一個服務來判斷這樣會快很多,而且內(nèi)網(wǎng)環(huán)境搭在本機也可以使用。網(wǎng)盤鏈接:https://www.aliyundrive.com/s/3kDp5HELGxo
三、漏洞分析
我們知道這個漏洞是個jndi的注入那么就可以在lookup處打斷點來查看調用棧(也可以在執(zhí)行命令的地方java.lang.Runtime的exec方法下斷點),全局搜索InitialContext類在lookup處下斷點

debug下可以看到調用棧,整個還是很深的。

漏洞觸發(fā)的入口函數(shù)在logIfEnabled,這里需要知道的是為什么我們的payload${jndi:JNDIContent}能夠被傳入,原因是在log4j2中有8個日志級別,error()
和fatal()
方法可默認觸發(fā)漏洞,其余的方法需要配置日志級別才可以觸發(fā)漏洞。因為在logIfEnabled
方法中,對當前日志等級進行了一次判斷,當isEnabled方法為true才能進入到logMessage:

跟進下去可以看到進行判斷的方法為filter,return返回的值是需要現(xiàn)在的日志等級大于等于默認等級的

然后才可以執(zhí)行到logMessage這個方法中,將payload傳遞下去。

后面的調用方法都是關于包裝和調用的了,不關鍵的調用如下
這里需要說明的是在前面的PatternSerializer這個類中會格式化我們傳遞過來的payload,這里是會調用converter.format()的,而convertrt是屬于MessagePatternConverter這個類的,然后到了MessagePatternConverter中的format方法中

根據(jù)代碼可以看到傳過來的參數(shù)會進行for循環(huán)遍歷,然后每一位進行判斷是否有"${",有就會使用substring進行分割,將payload賦值給value,然后將value傳遞到getStrSubstitutor.replace()中。

這里又將值傳遞到了substitute這個方法中,然后又會到int型的substitute方法中

跟進這個方法,可以看到一個叫varName的變量是已經(jīng)將傳遞過來的payload中的${}去除掉了,然后將varName變量傳遞到resolveVariable方法中。

可以看一下上面的代碼是怎樣實現(xiàn)去除的
首先看第一個while循環(huán),這里使用prefixMatcher.isMatch來對pos進行自增

然后跟進看看這個isMatch的代碼,可以知道是實現(xiàn)了${的定位,利用chars數(shù)組循環(huán)匹配來確定payload中${結束的位置,返回長度len為2

所以startMatchLen的值為2,跳轉到else條件中


這段代碼和前面匹配${的代碼類似,跟進isMatch發(fā)現(xiàn)chars是},所以suffixMacher.isMatch是用相同辦法匹配了}的位置

這樣就從${}截取出了值,然后賦值給了varNameExpr,再賦值給了varName傳遞到resolveVariable方法,這個方法調用interpolator的lookup方法
可以看到第一個框中代碼是取到了前綴:jndi,第二個框中代碼獲取:ldap://127.0.0.1:1389/ufydj4,第三個框中根據(jù)prefix來獲取JndiLookup類,最后就會在JndiLookup類中調用JndiManager.lookup方法
后面就和jndi注入一樣了,通過jndiManager類進行調用,成功執(zhí)行到javax/naming/InitialContext.java 原生lookup解析函數(shù)。
?