No route to host while parsing XML?

Category: ExceptionOfTheDay

10:03 PM, Sat, Dec 29 2007

A strange error when starting a simple Seam application (Seam 2.0, JBoss Application Server 4.2, Sun Java 6 on Linux):

java.lang.RuntimeException:
  org.dom4j.DocumentException:
    No route to host Nested exception: No route to host
        at org.jboss.seam.navigation.Pages.getDocumentRoot(Pages.java:954)
        at org.jboss.seam.navigation.Pages.parse(Pages.java:912)
        at org.jboss.seam.navigation.Pages.initialize(Pages.java:108)

This upon accessing a simple Seam page, while working on my laptop at an Internet cafe here in Almaty.

What within dom4j is attempting to access the net? It's looking for a DTD reference. Doing a trace on the network shows it is trying to do a DNS resolve on jboss.org.

The problem is with our pages.xml file, of which the first line is:

<!DOCTYPE pages PUBLIC
          "-//JBoss/Seam Pages Configuration DTD 1.2//EN"
          "http://jboss.com/products/seam/pages-1.2.dtd">

dom4j needs a DTD file to process an XML file with a specified DTD. In this case, it is going over the net to get this DTD file. This is bad. The file should now be specified with an XML schema, not the troublesome DTD declaration. It should start with:

<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation=
          "http://jboss.com/products/seam/pages
           http://jboss.com/products/seam/pages-2.0.xsd"

This is running with Java 6 on Linux. It's possible that different Java environments have different dom4j behaviours, because dom4j is a component of the Java environment.

Tracking down this exception has revealed quite a few things.

First, you should switch all the DTD-style XML in your application to the more modern XML Schema style. Use a find command to make sure there are none.

Second, dom4j needs DTD files and will go over the net to get them. I think this is a serious flaw. dom4j can go ahead and parse XML without regard to a DTD. It should have a flag that says, "ignore DTD if it isn't in the classpath", and also "ignore all DTD lines and just parse." It should have another flag that says, "never go over the net to fetch resources".

Having some core bit of a system, such as an XML parser, making unexpected connections over the net is a security deficiency. Someone could, for example, put a malicious URL into the DTD string of a supplied XML document. I'm going to test a few well-known sites that let users supply information in the form of XML, and I'll put in DTDs with a URL on the chiralsoftware.com server in them, to see what happens. Seems like an obscure, unlikely attack, but still, XML parsers should parse XML and should not attempt to silently load resources over the net. I know that the question of "how do I get dom4j to parse an XML file with a DTD when the DTD is not present" is a persistent question in discussion forums. This behavior of dom4j is bad, and should at least be configurable to be disabled.

This flaw in dom4j has possible security implications for sites that use IP restrictions to secure "command URLs". This flaw is yet another reason why web site software should require POST for users to perform meaningful actions. It's a lot more difficult to convince someone else's server to do a POST than to do a GET. For example, a command URL like http://admin.example.com/cluster-command?command=shutdown&node=494 should require POST, and should not work on GET. It is common in many real-world installations to have command URLs like this which rely on IP address checks for security. That's usually fine, but you can see that if these command URLs allow GET access, and some piece of software in the cluster is using dom4j to process externally-supplied XML, that could be Bad. The command URL could be placed right in a DTD reference in a supplied XML file. For example:

<!DOCTYPE pages PUBLIC
          "-//Example/Example Configuration DTD 1.2//EN"
          "http://admin.example.com/cluster-command?command=shutdown&node=494">

If the machine processing this external-user supplied XML is using dom4j, it will hit that URL with an internal-to-cluster source IP address. Ouch.

Going back to Seam, this could be exploitable. Imagine some server has a DTD line in a pages.xml file. Unfortunately this DTD file isn't found in the classpath so dom4j goes over the net. An attacker figures this out and compromises DNS, and routes jboss.org to a malicious address. Remember, these DTD requests are totally unauthenticated! By routing to a malicious address, the attacker might be able to change the way the XML is parsed, or at the very least cause a denial-of-service attack. dom4j is, effectively, silently adding artefacts to the classpath, over non-secure unauthenticated connections. That's Bad.

Third, this is a failure of error reporting. How hard would it be to include some helpful debugging information in the exception? Something like, "dom4j attempted to parse pages.xml, which referend a DTD with the URL..., which could not be retrieved. Check the DTD declaration line." If that information had been in there, figuring this problem out would have taken a few minutes, even if I hadn't known about this strange behavior of dom4j.

Finally, this exception illustrates the effect that unusual environments can have on the test process. Had I been running this on a machine at the office, dom4j would have fetched the URL and I would never have known about this interaction, until I went to install the system at a customer site, which likely has a firewall to prevent outbound connections from the server. Then I would have had to make an embarassing plea of "it worked fine when we tested so it should be working fine on your site, but it isn't." Had I been working truly off-line, the exception might have been different and easier to figure out, because name resolution would have failed. But I'm at a cafe with a misconfigured WiFi router. This router is providing working name resolution, but is not providing a working default route. I suspect that this strangely-broken routing environment lead to this equally strange exception, and of course the dom4j developers likely never tested anything with a network environment that can resolve names but can't route. Going back to my first point, changes in network routing should not impact the way XML an parser works. That's absurd. The sanest default configuration would be for an XML parser to never attempt to fetch resources except from its own classpath. This means not over the net, and not from system configuration dependent files on the file system. It's not good when something works one way in the development environment and then works another way in the customer's live server environment.

I hope you've enjoyed today's Exception of the Day more than I have.

Update: There is now a JIRA open on this issue. I predict that this will be hard to solve because it may require changes in dom4j. Hopefully the JBoss team can get the Sun dom4j group to make these changes, because this has been a raspberry in Java's XML processing for a long long time.