验证JAXB反序列化禁用Schema时未加载XSD的方法及安全问询
前置信息
以下内容关联Stack Overflow相关问题,给定的Schema文件customer.xsd内容如下:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:simpleType name="stringMaxSize5"> <xs:restriction base="xs:string"> <xs:maxLength value="5"/> </xs:restriction> </xs:simpleType> <xs:element name="customer"> <xs:complexType> <xs:sequence> <xs:element name="name" type="stringMaxSize5"/> <xs:element ref="phone-number" maxOccurs="2"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="phone-number"> <xs:complexType> <xs:sequence/> </xs:complexType> </xs:element> </xs:schema>
待反序列化的XML文档input.xml内容:
<customer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="./customer.xsd"> <name>Jane Doe</name> <phone-number/> <phone-number/> <phone-number/> </customer>
JAXB反序列化代码:
import java.io.File; import java.util.ArrayList; import java.util.List; import javax.xml.bind.JAXBContext; import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlRootElement; public class Unmarshal { @XmlRootElement(name = "customer") public static class Customer { public String name; @XmlElement(name="phone-number") public List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>(); } public static class PhoneNumber {} public static void main(String[] args) throws Exception { JAXBContext jc = JAXBContext.newInstance(Customer.class); Unmarshaller unmarshaller = jc.createUnmarshaller(); unmarshaller.setSchema(null); Customer customer = (Customer) unmarshaller.unmarshal(new File("input.xml")); System.out.println(customer.name); } }
上述代码中,XML存在两处验证错误:
cvc-maxLength-valid: Value 'Jane Doe' with length = '8' is not facet-valid with respect to maxLength '5' for type 'stringMaxSize5'.xml(cvc-maxLength-valid)
cvc-type.3.1.3: The value 'Jane Doe' of element 'name' is not valid.xml(cvc-type.3.1.3)
以及:
cvc-complex-type.2.4.f: 'phone-number' can occur a maximum of '2' times in the current sequence. This limit was exceeded. No child element is expected at this point.xml(cvc-complex-type.2.4.f)
但因为代码中通过unmarshaller.setSchema(null);禁用了Schema验证,XML仍能被正常反序列化为Customer实例。现在需要明确:如何证明JVM在反序列化过程中未访问customer.xsd文件?避免盲目信任JVM不会加载XML中的XSD引用,以此评估XXE攻击风险。
验证方法
1. 移除/重命名XSD文件测试
直接删除或者重命名customer.xsd文件,然后运行原反序列化代码。如果代码能正常执行,输出Jane Doe且无文件找不到的异常,说明JVM完全没有尝试加载该XSD文件——若存在访问行为,会抛出FileNotFoundException或XML解析相关错误。
2. 监控文件系统访问
- Windows平台:使用Process Monitor工具,过滤当前Java进程对
customer.xsd的访问请求,若没有相关记录则证明未访问; - Linux/macOS平台:用
strace(Linux)或dtrace(macOS)跟踪Java进程的文件操作系统调用,查看是否存在打开customer.xsd的行为,无相关调用则说明未访问。
3. 配置XML解析器,强制禁用外部引用
即使禁用了Schema验证,XML解析器仍可能解析外部实体。可以通过配置JAXB使用的解析器,从根源上杜绝外部文件加载,同时防范XXE攻击:
import javax.xml.XMLConstants; import javax.xml.bind.Unmarshaller; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.XMLReader; // 在创建Unmarshaller后添加以下配置 SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); spf.setFeature("http://xml.org/sax/features/external-general-entities", false); spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); XMLReader xmlReader = spf.newSAXParser().getXMLReader(); unmarshaller.setUnmarshallerHandler(xmlReader.getContentHandler());
配置后,解析器会直接忽略XML中的XSD引用及其他外部实体,彻底消除风险。
4. 启用JVM调试日志
添加JVM启动参数开启XML组件调试日志:
-Djavax.xml.debug=all
运行程序后查看日志输出,若没有任何关于加载customer.xsd的条目,即可证明JVM未尝试访问该文件。
内容的提问来源于stack exchange,提问作者Jesús Zazueta

