用 defusedxml 安全解析用户上传 XML:禁用 DTD 并返回 400
【摘要】 用户上传XML若直接用lxml解析,可能触发XXE与实体扩展风险。本文给出用defusedxml禁用DTD和外部实体、捕获DTDForbidden与EntitiesForbidden并返回400的最小Flask实现,附运行方法与XXE验证payload及边界说明。
问题场景
用户上传 XML 的接口常见写法是直接用 lxml 的 etree.fromstring 解析。lxml 默认允许 DTD 和外部实体,攻击者可以构造带 SYSTEM 实体的 XML,尝试读取服务器本地文件或发起外部请求;实体扩展攻击也可能消耗解析资源。对外接口应默认拒绝 DTD 和实体,而不是依赖解析器的宽松默认值。
方案思路
defusedxml 在标准 XML 解析器外包了一层安全策略。替换 lxml 时,关键点是显式传入以下参数:
forbid_dtd=True:遇到DOCTYPE立即抛出DTDForbidden。forbid_entities=True:禁止实体定义时抛出EntitiesForbidden。forbid_external=True:禁止外部实体。
同时,在应用层限制上传字节数,避免超大文件进入解析器。异常统一返回 400,不把底层异常文本、文件路径或解析栈返回给调用方。
最小 Flask 代码
from flask import Flask, request, jsonify
from defusedxml import ElementTree as DET
from defusedxml.common import DTDForbidden, EntitiesForbidden
app = Flask(__name__)
MAX_XML_BYTES = 256 * 1024
@app.post('/upload-xml')
def upload_xml():
file = request.files.get('file')
if file is None:
return jsonify(error='missing file'), 400
raw = file.read(MAX_XML_BYTES + 1)
if len(raw) > MAX_XML_BYTES:
return jsonify(error='file too large'), 413
try:
root = DET.fromstring(
raw,
forbid_dtd=True,
forbid_entities=True,
forbid_external=True,
)
except DTDForbidden:
return jsonify(error='DTD forbidden'), 400
except EntitiesForbidden:
return jsonify(error='entities forbidden'), 400
except Exception:
return jsonify(error='invalid xml'), 400
return jsonify(root_tag=root.tag)
if __name__ == '__main__':
app.run(debug=False)
说明:先捕获 DTDForbidden,再捕获 EntitiesForbidden;其余解析错误统一返回 400。生产环境还应在网关或应用内增加鉴权、限流和请求体大小限制,不要仅依赖 Flask 自身。
运行方法
python -m venv .venv
source .venv/bin/activate
pip install flask defusedxml
python app.py
服务默认监听 127.0.0.1:5000。另开终端准备上传验证。
XXE 验证 payload
创建 xxe.xml,其中外部实体指向占位路径:
<?xml version='1.0'?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM 'file:///etc/passwd'>
]>
<root>&xxe;</root>
发送请求:
curl -i -F 'file=@xxe.xml;type=application/xml' http://127.0.0.1:5000/upload-xml
预期返回 HTTP 400,响应 JSON 中的 error 为 DTD forbidden,且不会读取该路径内容。由于本方案在 DTD 阶段就拒绝,外部实体不会进入实体解析。
实体扩展验证 payload 如下,同样应返回 400:
<?xml version='1.0'?>
<!DOCTYPE lolz [
<!ENTITY lol 'lol'>
<!ENTITY lol1 '&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;'>
]>
<root>&lol1;</root>
边界与限制
- defusedxml 解决的是 XML 解析阶段的 DTD、实体和外部实体风险,不替代文件大小限制、请求频率限制、身份认证和业务字段校验。
- 如果业务确实需要 DTD,应单独进行安全评审,不建议对用户上传内容直接放开;本方案默认全部拒绝。
- 解析成功后仍应限制元素数量或深度,或将 XML 转为白名单结构,避免合法但庞大的 XML 造成业务层资源消耗。
- 错误响应保持简短,日志中记录请求标识和异常类型即可,不记录敏感文件路径或完整 XML。
- 依赖应通过包管理工具安装并锁定版本,关注安全更新;不要从不可信来源复制解析器配置。
对用户上传 XML 的接口,优先选择默认拒绝 DTD 和实体的解析方式,再叠加输入大小与业务校验,能显著降低 XXE 与实体扩展类风险。
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)