bigywater.tech
Java · 216

Java XML 与 XXE:解析器安全开关要成套设置

解析 XML 时的外部实体(XXE)是老问题。JDK 21 的解析器默认到底会不会读本地文件、请求远程 DTD?每个 API 的安全开关该怎么设?这篇用自己创建的测试文件与本机 HTTP 服务做防御性验证,逐个确认每个开关实际挡住了什么。

4 / 4
默认的 DOM / SAX / StAX / Transformer 中读到本地文件实体的有 4 个
64000
JDK 默认的实体展开次数上限
755 ms
关闭 secure processing 后展开 10^6 个 lol 的耗时

默认配置:读文件、取远程 DTD

实测 · 本机执行输出
  DOM 默认配置,本地文件实体                                      → 解析成功,文本 = "SECRET-MARKER-12345"  ← 读到了本地文件
  SAX 默认配置,本地文件实体                                      → 解析成功,文本 = "SECRET-MARKER-12345"  ← 读到了本地文件
  StAX 默认配置,本地文件实体                                     → 解析成功,文本 = "SECRET-MARKER-12345"  ← 读到了本地文件
  Transformer(XSLT 身份转换)默认,本地文件实体                      → 解析成功,文本 = "<r>SECRET-MARKER-12345</r>"  ← 读到了本地文件
  DOM 默认配置,远程外部 DTD(http://127.0.0.1)                  → 解析成功,文本 = "REMOTE-DTD-VALUE"  ← 取了远程 DTD
  (这一段里测试 HTTP 服务器共收到 1 次请求:['/ext.dtd'])

DOM 上的各种开关

实测 · 本机执行输出
  DOM + FEATURE_SECURE_PROCESSING(true)                → SAXParseException: External Entity: Failed to read external document 'secret.txt', because 'file' access is not allowed due to restriction set by the accessExternalDTD p…
  DOM + disallow-doctype-decl                          → SAXParseException: DOCTYPE is disallowed when the feature "http://apache.org/xml/features/disallow-doctype-decl" set to true.
  DOM + external-general-entities=false                → 解析成功,文本 = ""
  DOM + setExpandEntityReferences(false)               → 解析成功,文本 = ""
  DOM + ACCESS_EXTERNAL_DTD=""(只关 DTD)                 → SAXParseException: External Entity: Failed to read external document 'secret.txt', because 'file' access is not allowed due to restriction set by the accessExternalDTD p…
  DOM + ACCESS_EXTERNAL_DTD="" + ACCESS_EXTERNAL_SCHEMA="" → SAXParseException: External Entity: Failed to read external document 'secret.txt', because 'file' access is not allowed due to restriction set by the accessExternalDTD p…
  DOM + ACCESS_EXTERNAL_DTD="",远程 DTD                  → SAXParseException: External DTD: Failed to read external DTD 'ext.dtd', because 'http' access is not allowed due to restriction set by the accessExternalDTD property.
  DOM + external-parameter-entities=false 与 load-external-dtd=false,远程 DTD → 解析成功,文本 = ""
[Fatal Error] :1:202: External Entity: Failed to read external document 'secret.txt', because 'file' access is not allowed due to restriction set by the accessExternalDTD property.
[Fatal Error] :1:31: DOCTYPE is disallowed when the feature "http://apache.org/xml/features/disallow-doctype-decl" set to true.
[Fatal Error] :1:202: External Entity: Failed to read external document 'secret.txt', because 'file' access is not allowed due to restriction set by the accessExternalDTD property.
[Fatal Error] :1:202: External Entity: Failed to read external document 'secret.txt', because 'file' access is not allowed due to restriction set by the accessExternalDTD property.
[Fatal Error] :1:74: External DTD: Failed to read external DTD 'ext.dtd', because 'http' access is not allowed due to restriction set by the accessExternalDTD property.
  (这一段里测试 HTTP 服务器共收到 0 次请求:[])

StAX 与 Transformer

实测 · 本机执行输出
  StAX + IS_SUPPORTING_EXTERNAL_ENTITIES=false         → 解析成功,文本 = ""
  StAX + SUPPORT_DTD=false                             → XMLStreamException: ParseError at [row,col]:[1,203] Message: The entity "x" was referenced, but not declared.
  Transformer + ACCESS_EXTERNAL_DTD=""(输入侧未限制)         → TransformerException: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 202; External Entity: Failed to read external document 'secret.txt', because 'file' access…
  Transformer + FEATURE_SECURE_PROCESSING              → TransformerException: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 202; External Entity: Failed to read external document 'secret.txt', because 'file' access…
[Fatal Error] :1:202: External Entity: Failed to read external document 'secret.txt', because 'file' access is not allowed due to restriction set by the accessExternalDTD property.
[Fatal Error] :1:202: External Entity: Failed to read external document 'secret.txt', because 'file' access is not allowed due to restriction set by the accessExternalDTD property.

嵌套实体与展开限制

实测 · 本机执行输出
  DOM 默认配置,10 层每层 10 倍的嵌套实体(共 10^10 个 lol)             → SAXParseException: JAXP00010001: The parser has encountered more than "64000" entity expansions in this document; this is the limit imposed by the JDK.
  DOM + disallow-doctype-decl                          → SAXParseException: DOCTYPE is disallowed when the feature "http://apache.org/xml/features/disallow-doctype-decl" set to true.
  DOM + 关闭 secure processing                           → 解析成功,文本 = "3000000 个字符 [755 ms]"
  设置 -Djdk.xml.entityExpansionLimit=100 后的小实体链         → SAXParseException: JAXP00010001: The parser has encountered more than "100" entity expansions in this document; this is the limit imposed by the JDK.
[Fatal Error] :1:1: JAXP00010001: The parser has encountered more than "64000" entity expansions in this document; this is the limit imposed by the JDK.
[Fatal Error] :1:31: DOCTYPE is disallowed when the feature "http://apache.org/xml/features/disallow-doctype-decl" set to true.
[Fatal Error] :1:1: JAXP00010001: The parser has encountered more than "100" entity expansions in this document; this is the limit imposed by the JDK.

分步演示

XML 与 XXE
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;测试的 secret.txt 与 HTTP 服务器都是本机自己创建,没有访问任何外部地址
    • 没有验证:XMLReader、SchemaFactory、XPath、Validator、DocumentBuilder 之外的第三方解析器(Xerces、Woodstox);OOB(带外)XXE 与参数实体外泄的变体、XInclude;JDK 17 与 27 的默认值是否一致;SAX 解析器的 disallow-doctype-decl 之外的开关组合
    京ICP备2026067502号-1京公网安备11010502063487号