仅设置 FEATURE_SECURE_PROCESSING 不足以防御 XXE,因其不默认禁用 DTD 声明;必须显式调用 setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) 才能彻底拦截。

Java SAXParserFactory 设置 FEATURE_SECURE_PROCESSING 为什么不够
只开 FEATURE_SECURE_PROCESSING 不能防 XXE,它只是启用基础安全策略,不默认禁用外部实体。JDK 7+ 默认仍允许 DTD 解析,setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) 才是关键动作。
常见错误现象:SAXParseException 报 “DOCTYPE is disallowed” 却仍能加载外部 DTD;或看似没报错,但 http://example.com/xxe 被实际请求 —— 说明只设了 FEATURE_SECURE_PROCESSING,漏掉真正拦截点。
-
setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)必须显式调用,否则 DTD 声明(哪怕空的)都会被解析 - 若业务真需要 DTD(极少见),改用
setFeature("http://xml.org/sax/features/external-general-entities", false)+setFeature("http://xml.org/sax/features/external-parameter-entities", false) - 注意:Apache Xerces 2.9.1+ 才完全支持
disallow-doctype-decl,旧版本需升级或换方案
DocumentBuilder 用 setFeature 禁用外部实体的实际写法
很多人直接 new DocumentBuilderFactory 就 setFeature,却忘了 factory 创建的 builder 才是真正干活的。feature 必须在 newDocumentBuilder() 之前设置,且 builder 实例不继承 factory 的 feature 状态 —— 每次都要重设。
使用场景:Spring Boot 里用 DocumentBuilder 解析用户上传的 XML 配置文件。
立即学习“Java免费学习笔记(深入)”;
- 必须在
DocumentBuilder builder = factory.newDocumentBuilder()前调用factory.setFeature(...) - 推荐一步到位:用
DocumentBuilderFactory.newInstance().setFeature(...).newDocumentBuilder()链式调用,避免中间状态污染 - 别信“已全局禁用”的错觉 —— Tomcat、WebLogic 等容器可能重置 factory,默认行为不可靠
javax.xml.stream.XMLInputFactory 的 XXE 防护盲区
XMLInputFactory 是 StAX 解析器入口,它不走 setFeature 路线,而是用 setProperty 控制实体解析。设错属性名或值类型(比如传字符串 "false" 而非布尔 false)会导致静默失效。
错误现象:代码里写了 factory.setProperty("javax.xml.stream.isReplacingEntityReferences", "false"),但依然触发外连 —— 因为该属性只控制是否替换已定义实体,不阻止 DTD 加载本身。
- 真正起效的是:
factory.setProperty("javax.xml.stream.supportDTD", false) - 配合禁用外部参数实体:
factory.setProperty("javax.xml.stream.isValidating", false)(虽然名字像校验,实则影响 DTD 处理逻辑) - 注意:JDK 自带 StAX 实现(
com.sun.xml.internal.stream.XMLInputFactoryImpl)才完全支持这些属性;第三方实现(如 Woodstox)行为可能不同
测试 XXE 是否真被堵住的最小验证方式
别依赖“没报错就安全”。用最简 payload 直接测网络行为,比看日志更可靠。
可复用的测试 XML 片段:
<?xml version="1.0"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://localhost:8000/test"> ]> <data>&xxe;</data>
- 启动本地监听:
python3 -m http.server 8000,然后解析该 XML - 如果监听端口收到 GET 请求,说明 XXE 未被拦截 —— 无论抛不抛异常
- 生产环境禁用
http协议还不够,攻击者会用file:///etc/passwd或ftp://,所以必须禁用 DTD 声明本身
复杂点在于:某些老系统依赖 DTD 定义的实体别名(如 ),强行禁用 DTD 会导致解析失败。这时候得权衡 —— 是改业务逻辑适配无 DTD 方案,还是隔离解析上下文并严格限制协议白名单。事情说清了就结束










