Introduction
In this lab, you will learn about XML External Entity (XXE) injection, a web security vulnerability that allows an attacker to interfere with an application's processing of XML data.
XXE is fundamentally about parser behavior. The application thinks it is parsing data, but the XML parser is also processing extra instructions embedded in that data format.
When a web application parses XML input without properly disabling external entity resolution, attackers can define custom entities that point to local files on the server. By injecting these malicious entities into the XML payload, the attacker can force the server to read sensitive files (like /etc/passwd) and return their contents in the HTTP response. You will step into the shoes of a security tester to identify an XML endpoint, craft a malicious payload, and successfully extract system files.
Identify XML Data Processing
In this step, we will interact with a local web application that has been set up for this lab. The application is running on 127.0.0.1 at port 5000 and has an endpoint at /process that accepts XML data.
XML (eXtensible Markup Language) is a popular format used for structuring and transporting data between a client and a server. Before we can exploit an XXE vulnerability, we first need to confirm that the application actually processes XML input and reflects the parsed data back to us.
That first confirmation matters because XXE is only possible when XML parsing is genuinely happening. Many endpoints accept text, but not all of them treat the body as XML.
We will use the curl command-line tool to send an HTTP POST request to the server. A POST request sends data to the application, and the Content-Type: application/xml header tells the server to interpret that data as XML instead of plain text or JSON.
Run the following command in your terminal to send a basic XML payload:
curl -X POST -H "Content-Type: application/xml" -d '<root><name>LabEx User</name></root>' http://127.0.0.1:5000/process
You should see an output similar to this:
Hello, LabEx User!
This output confirms two important things. First, the application successfully parsed our XML data. Second, the application extracted the text inside the <name> tag and reflected it back to us in the response. This reflection is crucial because it gives us a way to see the results of our injected entities later.
Intercept XML Request
In real-world penetration testing, security researchers often use proxy tools like Burp Suite to intercept and modify web requests. Since we are working entirely in the terminal, we will simulate this process by saving our XML payload into a local file. This makes it much easier to edit and inject complex multi-line XML structures without dealing with messy command-line escaping.
Moving the payload into a file is not just a convenience. It makes complex structured input easier to reason about and revise, which is important when the syntax itself affects exploitation.
Let's create a file named payload.xml in our current directory (~/project). We will use the nano text editor to create and edit this file. If you have not used nano before, it is a simple terminal-based text editor that is well suited for small files like this payload.
Open the editor by running:
nano payload.xml
Type or paste the following standard XML structure into the file:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<name>Security Tester</name>
</root>
Save the file and exit the editor (in nano, press Ctrl+O, then Enter to save, and Ctrl+X to exit).
Now, let's send this file to the server using curl. The @ symbol tells curl to read the data from the specified file instead of treating the string literally.
Run the following command:
curl -X POST -H "Content-Type: application/xml" -d @payload.xml http://127.0.0.1:5000/process
You should see the following output:
Hello, Security Tester!
By moving our payload into a file, we have prepared a clean workspace to start crafting our XXE injection in the next steps.
Define External Entity (DOCTYPE)
Now that we have a working XML file, we can start manipulating its structure. XML allows developers to define custom variables called "Entities" within a Document Type Definition (DOCTYPE). A DOCTYPE is an extra section near the top of an XML document that describes rules or reusable values for the parser.
An Entity acts like a shortcut. If you define an entity named test with the value Injected Data, anytime you write &test; in your XML document, the XML parser will replace it with Injected Data. If the parser is vulnerable, it will process these custom entities without any security checks.
This harmless example is important because it proves entity expansion is enabled before you move on to file-based external entities.
Let's modify our payload.xml file to include a DOCTYPE declaration and a custom entity.
Open the file again:
nano payload.xml
Update the contents of the file to match the following code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY test "Injected Entity Data">
]>
<root>
<name>&test;</name>
</root>
Save the file and exit nano (Ctrl+O, Enter, Ctrl+X).
In this payload, we defined an entity named test. Inside the <name> tag, we referenced this entity using &test;. Let's send this updated payload to the server to see if it resolves our custom entity.
Run the curl command again:
curl -X POST -H "Content-Type: application/xml" -d @payload.xml http://127.0.0.1:5000/process
You should see the following output:
Hello, Injected Entity Data!
The server successfully replaced &test; with our custom string. This proves that the XML parser is actively resolving entities, which means it is highly likely vulnerable to XML External Entity (XXE) injection.
Extract Local Files (/etc/passwd)
In the previous step, we defined an internal entity. The true danger of XXE arises when we define an External Entity. By using the SYSTEM keyword, we can instruct the XML parser to fetch the value of the entity from an external source, such as a URL or a local file path.
If the web server's XML parser is poorly configured, it will read the contents of the file we specify and embed it into the XML document. Since the application reflects the <name> tag back to us, we will be able to see the contents of the file in the HTTP response. This is why reflection is so valuable to an attacker: it turns an invisible server-side file read into visible output in the browser or terminal.
We will attempt to read /etc/passwd, a standard Linux file that contains user account information. This file is readable by all users on the system, making it a common target for testing file read vulnerabilities.
Open your payload file one last time:
nano payload.xml
Modify the entity definition to use the SYSTEM keyword and point to the local file using the file:// protocol:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
<name>&xxe;</name>
</root>
Save the file and exit nano (Ctrl+O, Enter, Ctrl+X).
Now, send the malicious payload to the server:
curl -X POST -H "Content-Type: application/xml" -d @payload.xml http://127.0.0.1:5000/process
If the exploit is successful, the server will read the /etc/passwd file and return its contents in the response. You should see a large block of text similar to this:
Hello, root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
labex:x:1000:1000::/home/labex:/bin/zsh
!
Congratulations! You have successfully exploited an XML External Entity vulnerability to extract sensitive local files from the web server.
Summary
In this lab, you learned how to identify and exploit XML External Entity (XXE) vulnerabilities. You started by verifying that a web application processes and reflects XML data. You then learned how to define custom XML entities using a DOCTYPE declaration. Finally, you escalated the attack by using the SYSTEM keyword to create an external entity, forcing the server's XML parser to read the local /etc/passwd file and return its contents.
To prevent XXE vulnerabilities, developers must ensure that their XML parsers are configured to disable the resolution of external entities and DTDs (Document Type Definitions) entirely.



