12

I have the following code:

final TransformerFactory factory = TransformerFactory.newInstance();

factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");

The second line works fine in modern JDKs (I tried 1.8) with a default TransformerFactory. But when I add xalan (version 2.7.2, the most recent one) to classpath, I get the following on that second line:

Exception in thread "main" java.lang.IllegalArgumentException: Not supported: http://javax.xml.XMLConstants/property/accessExternalDTD
    at org.apache.xalan.processor.TransformerFactoryImpl.setAttribute(TransformerFactoryImpl.java:571)
    at Main.main(Main.java:11)

I guess this is because xalan's TransformerFactory does not support this attribute. Xalan's implementation gets picked up through ServiceLoader mechanism: it is specified in services/javax.xml.transform.TransfomerFactory in xalan jar.

It is possible to override the TransformerFactory implementation using javax.xml.transform.TransformerFactory system property or with $JRE/lib/jaxp.properties file, or pass class name directly in code. But to do it, I must supply a concrete class name. Right now, it is com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl, but it's a bit scary to hardcode it in a system property, as on JDK upgrade they can easily change the class name, and we will just get a runtime error.

Is there any way to instruct the TransformerFactory.newInstance() to just ignore that xalan-supplied implementation? Or tell it 'just use the system default'.

P.S. I cannot just remove xalan from classpath because a bunch of other libraries we use depend on it.

Roman Puchkovskiy
  • 11,415
  • 5
  • 36
  • 72
  • 1
    Are you trying to disable this feature? If so, take a look here: https://stackoverflow.com/questions/27128578/set-feature-accessexternaldtd-in-transformerfactory#29021326 – trappski Apr 24 '18 at 11:59
  • 1
    @trappski Yes, I am trying to disable external DTD processing, so my question looks like an X/Y problem. But `factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)` doesn't prevent Xalan from accessing an external DTD: when I feed an XML with an external DTD link (an http link), Xalan tries to load that DTD and gets a `ConnectException`. – Roman Puchkovskiy May 07 '18 at 15:55

3 Answers3

4

The only thing I could achieve here is to hardcode JDK default factory and use the normal discovery process as a fallback:

TransformerFactory factory;
try {
   //the open jdk implementation allows the disabling of the feature used for XXE
    factory = TransformerFactory.newInstance("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", SecureXmlFactories.class.getClassLoader());
} catch (Exception | TransformerFactoryConfigurationError e) {
    //this part uses the default implementation of in xalan 2.7.2
   LOGGER.error("Cannot load default TransformerFactory, le's try the usual way", e);
   //not advisable if you dont want your application to be vulnerable. If needed you can put null here.
   factory = TransformerFactory.newInstance();

}

and then configure it under try/catch

// this works everywhere, but it does not disable accessing
// external DTDs... still enabling it just in case
try {
    factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
} catch (TransformerConfigurationException e) {
    LOGGER.error("Cannot enable secure processing", e);
}

// this does not work in Xalan 2.7.2
try {
    factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
} catch (Exception e) {
    LOGGER.error("Cannot disable external DTD access", e);
}
// this does not work in Xalan 2.7.2
try {
    factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
} catch (Exception e) {
    LOGGER.error("Cannot disable external stylesheet access", e);
}

And monitor the logs to see if/when the default JDK factory class name changes.

Naveen Babu
  • 1,584
  • 1
  • 14
  • 35
Roman Puchkovskiy
  • 11,415
  • 5
  • 36
  • 72
  • 1
    Please note the implementation in catch block `factory = TransformerFactory.newInstance();` . it is not XXE safe and this logic will fail in case of attack. – Naveen Babu Aug 19 '19 at 08:43
  • Can you please let us know jar file name to use SecureXmlFactories.class file. – Paramesh Korrakuti Apr 13 '20 at 09:53
  • @ParameshKorrakuti `SecureXmlFactories` is the name of the class that includes the code shown as a proposed solution. You could just use the snippets shown here, class name is not important. – Roman Puchkovskiy Apr 13 '20 at 10:50
  • This answer matched my case 100%. However, I tried using this code `tf = TransformerFactory.newInstance("javax.xml.transform.TransformerFactory", javax.xml.transform.TransformerFactory.class.getClassLoader());` to get the instance from JDK instead from `org.apache.xalan.processor.TransformerFactoryImpl` but it's not working. I tried also `tf = TransformerFactory.newInstance("org.apache.xalan.processor.TransformerFactoryImpl", javax.xml.transform.TransformerFactory.class.getClassLoader());` and still coming from `xalan` please help! – tarekahf Mar 23 '23 at 02:00
  • Now I am able to wrap my head around this issue. Check my answer here: https://stackoverflow.com/a/75825877/4180447 – tarekahf Mar 23 '23 at 17:24
0

Another way to define which TransformerFactory implementation to load is by setting the javax.xml.transform.TransformerFactory system property (docs).

In Ant you would do something like this inside your target:

<jvmarg value="-Djavax.xml.transform.TransformerFactory=com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"/>

In the above example, I'm setting the one from OpenJDK8 as default

0

This does not work with JAVA9 onwards. pass FactoryClass and class loader to newInstance something like this

final TransformerFactory transformerFactory = TransformerFactory.newInstance(                "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl",
                CLASS_NAME.class.getClassLoader());
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
        
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");