A full academic and practical walkthrough of a web attack chain exploiting XML External Entity injection and session cookie forgery.
Read TutorialThe initial phase of the engagement focused on information gathering to map the target's attack surface. This is a critical step to identify potential entry points, running services, and the technology stack in use.
I began with a TCP port scan using Nmap to discover active services. The flags -sC (default scripts)
and -sV (version detection) were used to extract as much detail as possible about the running daemons.
nmap -sC -sV 192.168.1.177 > nmap.txt
The scan results provided a clear picture of the target:
Figure 4.1: Nmap scan results identifying exposed services
With port 80 identified as a primary target, I proceeded to enumerate the web application structure using Dirb. This tool brute-forces URL paths to find hidden directories and endpoints that aren't explicitly linked.
dirb http://192.168.1.177
Figure 4.2: Dirb results revealing a /login endpoint
The scan discovered a /login endpoint. Accessing the web root in a browser revealed a simple interface
indicating that the user was "not logged in as admin". This confirmed that the application handles authentication,
making session management a key area for further investigation.
Figure 4.3: Web application homepage
To analyze the authentication mechanism, I intercepted the login traffic using Burp Suite. The application by default sent credentials in JSON format. However, knowing that older versions of Play Framework (specifically 2.1.3) rely on XML parsers, I tested for content-type confusion.
I modified the HTTP request, changing the Content-Type header from application/json to text/xml
and supplied a valid XML payload. The server responded with a "200 OK", confirming that the backend parser was indeed processing XML.
Initial attempts to perform a standard "in-band" XXE attack (where the file content is returned directly in the server's response) failed. The application did not reflect the parsed XML data back to the screen.
To bypass this limitation, I adopted an Out-of-Band (OOB) strategy. This technique forces the vulnerable server to fetch a malicious DTD (Document Type Definition) from a server I control. This external DTD then instructs the server to read a local file and send its content back to me via a subsequent HTTP request.
Conceptual flow of the Out-of-Band XXE attack. Image from: Medium article
I set up a Python HTTP server on port 3000 to host the malicious DTD file (exploit.dtd) and a Netcat listener on port 3001 to receive the exfiltrated data.
The Malicious DTD (exploit.dtd):
Explanation: The DTD defines a parameter entity %p1 that reads the target file.
It then defines %p2 which constructs a URL containing the file content and sends it to my listener.
Figure 4.5: The malicious DTD hosted on the attacker's server
I then injected the XXE payload into the login request, referencing my external DTD:
Figure 4.6: XML request injecting the external entity reference
The server processed the XML, fetched the DTD, executed the instructions, and successfully sent the contents of /etc/passwd to my Netcat listener.
Figure 4.7: Successful exfiltration of /etc/passwd via OOB XXE
Critical Finding: The passwd file revealed a user named play with a home directory of /opt/play-2.1.3/.
This path is crucial as it reveals where the web application is installed, allowing me to target configuration files in the next phase.
Play Framework implements stateless, client-side sessions. This design choice means that all session data is stored entirely inside a browser cookie rather than on the server.
To ensure integrity, the cookie is cryptographically signed using an
application-wide secret key defined in application.conf.
However, the session contents are not encrypted,
only signed. If the secret key is compromised, the server can no longer distinguish
between legitimate session cookies and attacker-generated ones.
Using the previously demonstrated XXE out-of-band technique,
I targeted the configuration file located at file:///opt/play-2.1.3/xxe/conf/application.conf.
The file was successfully retrieved, exposing the secret key.
Figure 4.8: application.conf revealing the application secret
With the key in hand, I needed to understand exactly how the framework constructs and signs the cookie. Since Play Framework is open source, I consulted the documentation and source code hosted on GitHub for version 2.1.x.
Reference: Play Framework 2.1.x Source Code (GitHub)
First, I analyzed the application's controller logic (which I could also read via the XXE vulnerability at Application.java)
to see how the login was handled. The code showed a call to session("user", username).
Figure 4.9: Server-side login function invoking the session object
To understand what happens inside that session() call, I examined the framework's internal Session class.
The source code confirmed that session data is stored as a simple Map of strings.
Figure 4.10: The internal Session class definition
Further digging revealed the encode method. This function iterates through the data map
and concatenates them into a URL-encoded string. For a user "admin", this results in the payload user=admin.
Figure 4.11: The encode function formatting the payload
Finally, I located the critical sign method. This function takes the encoded payload and the secret key,
then generates a signature using HMAC-SHA1.
Figure 4.12: The framework's HMAC-SHA1 signing function
Based on this analysis, I wrote a Python script to replicate the signing process.
I used the secret key extracted from application.conf and the target payload user=admin
to generate a valid signature.
Figure 4.13: Cookie signing logic recreated in Python
The script output the signature a5b8363ce748cfbb5d654edc3676d440173b33de.
I combined this with the payload to form the final cookie value:
a5b8363ce748cfbb5d654edc3676d440173b33de-user=admin.
I manually injected this forged PLAY_SESSION cookie into the browser using the Storage Inspector.
Figure 4.14: Forged cookie inserted into the browser
Upon refreshing the page, the server validated the signature, accepted the session, and granted full administrative access.
Figure 4.15: Administrative access successfully obtained
Having extracted the user list (specifically pentesterlab) from /etc/passwd, we launched a brute-force attack against the SSH service using Metasploit.
Figure 4.16: Searching for SSH login modules
msf6 > use auxiliary/scanner/ssh/ssh_login
msf6 > set RHOSTS 192.168.1.177
msf6 > set USERNAME pentesterlab
msf6 > set PASS_FILE rockyou.txt
msf6 > run
Figure 4.18: Successful brute force finding the password 'vulnerable'
After logging in via SSH, we checked the /etc/shadow file.
Figure 4.20: Contents of the /etc/shadow file
While the root account was locked, the user had sudo privileges. By executing sudo su, we gained full root access.
Figure 4.20: Gaining root privileges via sudo
This tutorial demonstrated a complete attack chain starting from a single XXE vulnerability (CVE-2014-3630) in the Play Framework. By exploiting the XML parser, we exfiltrated sensitive configuration files, forged session cookies to bypass authentication, and pivoted to a system-level compromise via SSH. This highlights the critical importance of keeping frameworks updated and disabling external entity resolution in XML parsers.