If you are familiar with PentesterLab, you may have looked into our Play XML Entities exercise. Recently, we decided to create an online version as part of the Yellow Badge for our PRO subscribers.
To put an exercise online, the main task consists in rebuilding it based on the ISO. It was pretty easy to get it done, unfortunately the vulnerability wasn’t fully exploitable anymore. This article will walk you through the debugging process and the changes introduces by different versions of Java.
To get started, we need some vulnerable code. Instead of re-inventing the wheel, you just need to get some example from the web:
import java.io.File;
import java.io.FileInputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
public class Test {
public static void main(String[] args) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
// use the factory to create a documentbuilder
DocumentBuilder builder = factory.newDocumentBuilder();
// create a new document from input source
FileInputStream fis = new FileInputStream("test.xml");
InputSource is = new InputSource(fis);
Document doc = builder.parse(is);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
The important part in this code is that it will print the exception details. The Play framework silently swallows the exception making debugging non-trivial.
If you want more details on the full-exploitation, make sure you check our free exercise on PLAY XXE.
First, we can try with openjdk:7u131 (the option -v is used to mount the current local directory as /srv/ in the container):
$ docker run -it --rm -v `pwd`:/srv/:rw openjdk:7u131 /bin/bash
root@aaf82368ee16:/# java -version
java version "1.7.0_131"
OpenJDK Runtime Environment (IcedTea 2.6.9) (7u131-2.6.9-2~deb8u1)
OpenJDK 64-Bit Server VM (build 24.131-b00, mixed mode)
root@aaf82368ee16:/# cd /srv/
root@aaf82368ee16:/srv# ls
test.xml Test.java
root@aaf82368ee16:/srv# javac Test.java && java Test
java.net.MalformedURLException: Illegal character in URL
at sun.net.www.http.HttpClient.getURLFile(HttpClient.java:600)
at sun.net.www.protocol.http.HttpURLConnection.getRequestURI(HttpURLConnection.java:2367)
at ...
Now, let’s try the same thing with Java 6:
$ docker run -it --rm -v `pwd`:/srv/:rw openjdk:6b38 /bin/bash
root@ecec815af9b1:/# cd /srv/
root@ecec815af9b1:/srv# java -version
java version "1.6.0_38"
OpenJDK Runtime Environment (IcedTea6 1.13.10) (6b38-1.13.10-1~deb7u1)
OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode)
# javac Test.java && java Test
java.net.MalformedURLException: Illegal character in URL
at sun.net.www.http.HttpClient.getURLFile(HttpClient.java:592)
at sun.net.www.protocol.http.HttpURLConnection.writeRequests(HttpURLConnection.java:479)
at ...
It looks like the fact that there is some kind of special characters breaks the XML parsing. By looking at the web server logs, we can see that the file test.dtd is retrieved:
XXXXX- - [24/Jun/2017:19:51:32 EDT] "GET /test.dtd HTTP/1.1" 200 118
So the issue probably happens when we send the content of the file.
After some more testing, this was caused by the newline character. You can still easily exfiltrate a file as long as it only contains one line.
To test older versions of Java, we can just build our own container:
FROM debian:7
RUN apt-get update && apt-get install -y unzip gzip && rm -rf /var/lib/apt/lists/*
ADD jdk-7-linux-x64.tar.gz /opt
The file “jdk-7-linux-x64.tar.gz” can be downloaded from the page http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase7-521261.html along with any other versions. If you want to avoid creating an account, http://bugmenot.com/ is your friend. N.B.: Docker will automatically extract the tar.gz file for you.
From there, we can just build and run our container:
$ docker build -t jdk-7-0 .
$ docker run -it --rm -v `pwd`:/srv/:rw jdk-7-0 /bin/bash
Once in the container, we can compile and run our Test.java file with this version of Java.
With a bit of automation, it becomes really easy to run the same code across all versions of Java to find out when the change was introduced
With docker, it’s really easy to put together small containers that will allow you to test behaviours across multiple versions of the same languages or applications. Doing the same thing on your system or a VM would be a total nightmare. Hopefully, this article convinced you to use docker for this use case.