XXE & Cookie Forgery on Play Framework

A full academic and practical walkthrough of a web attack chain exploiting XML External Entity injection and session cookie forgery.

Read Tutorial

1. Reconnaissance

The 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.

Service Discovery

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:

Nmap results

Figure 4.1: Nmap scan results identifying exposed services

Web Application Enumeration

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
Dirb results

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.

Web login page

Figure 4.3: Web application homepage

2. XXE Exploitation (Out-of-Band)

Vulnerability Identification

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.

Exploitation Strategy: Out-of-Band (OOB)

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.

XXE OOB behavior

Conceptual flow of the Out-of-Band XXE attack. Image from: Medium article

Execution

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.

Malicious DTD

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:

Modified XML

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.

passwd file

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.

3. Session Cookie Forgery

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.

Step 1: Stealing the Secret Key

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.

application.conf

Figure 4.8: application.conf revealing the application secret

Step 2: Reverse Engineering the Signing Logic

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).

Login logic

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.

Class Session

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.

Encode function

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.

Sign function

Figure 4.12: The framework's HMAC-SHA1 signing function

Step 3: Forging the Cookie

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.

Python recreation

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.

Cookie insertion

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.

Admin access

Figure 4.15: Administrative access successfully obtained

4. SSH Brute Force & Root Privilege Escalation

Having extracted the user list (specifically pentesterlab) from /etc/passwd, we launched a brute-force attack against the SSH service using Metasploit.

Metasploit modules

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
        
Brute-force success

Figure 4.18: Successful brute force finding the password 'vulnerable'

After logging in via SSH, we checked the /etc/shadow file.

shadow contents

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.

Root access

Figure 4.20: Gaining root privileges via sudo

Tools Used

Conclusion

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.