BurpJDSer-ng: Decoding Java Serialization Traffic in Burp Suite with the Montoya API
Hook
When you intercept traffic from a legacy Java Web Start application and see nothing but hexadecimal gibberish, you're staring at serialized Java objects—binary data that could contain critical vulnerabilities, if only you could read it.
Context
Java serialization has been a cornerstone of enterprise applications for decades, particularly in thick client architectures, RMI services, and JMX implementations. Unlike modern REST APIs that transmit human-readable JSON, Java serialization converts object graphs into binary streams—efficient for machines, opaque for security researchers. During penetration tests, this presents a significant challenge: how do you inspect, modify, and replay requests when the payload looks like aced0005737200 instead of structured data?
Historically, security testers resorted to writing custom deserializers, using hex editors with serialization knowledge, or relying on Burp extensions like the original BurpJDSer. However, Burp Suite's architecture evolved significantly with the introduction of the Montoya API, deprecating the older Extender API. BurpJDSer-ng emerges as a modernized solution, rebuilt from the ground up to leverage Montoya's capabilities while maintaining the core value proposition: transforming binary serialized objects into editable XML representations in real-time within Burp's interface.
Technical Insight
BurpJDSer-ng operates as a custom message editor tab that integrates seamlessly into Burp's HTTP message viewer. The architecture centers on the Montoya API's HttpRequestEditor and HttpResponseEditor interfaces, which allow the extension to register itself as an available editor when specific content patterns are detected. When Burp intercepts traffic, the plugin scans the raw bytes for Java serialization magic numbers (0xaced0005), indicating the presence of a serialized object stream.
The core transformation relies on XStream, a mature Java library that can convert object graphs to XML and back. Here's a simplified representation of how the deserialization flow works:
public class JDSerEditor implements HttpRequestEditor {
private XStream xstream;
private ByteArrayOutputStream rawBytes;
public EditorOptions provideEditorOptions(RequestEditorProvidedHttpRequest request) {
byte[] content = request.body().getBytes();
if (detectSerialization(content)) {
return EditorOptions.editorEnabled("JDSer-ng");
}
return EditorOptions.editorDisabled();
}
public ByteArray getRequest() {
try {
// Convert XML back to serialized Java object
Object obj = xstream.fromXML(xmlEditor.getText());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return ByteArray.byteArray(baos.toByteArray());
} catch (Exception e) {
// Handle serialization errors
}
}
}
The bidirectional nature is crucial for penetration testing workflows. When you modify the XML representation—perhaps changing a username field in a serialized User object or manipulating privilege flags—the plugin automatically re-serializes the modified object graph before sending the request to the server. This allows for surgical manipulation of complex nested objects without manually crafting binary streams.
One of the plugin's most practical features is its dynamic JAR loader. Real-world Java applications often serialize custom classes that aren't part of the standard JDK. Without these class definitions, XStream cannot properly deserialize the objects. BurpJDSer-ng addresses this by allowing you to load application JARs at runtime:
public void loadJar(File jarFile) throws Exception {
URLClassLoader classLoader = new URLClassLoader(
new URL[]{jarFile.toURI().toURL()},
this.getClass().getClassLoader()
);
xstream.setClassLoader(classLoader);
}
This capability is essential when testing thick client applications where you can extract JAR files from Java Web Start cache directories or decompile application bundles. Once loaded, the plugin can properly handle custom types like com.acme.PaymentTransaction or com.company.internal.AuthToken.
The Montoya API migration brings tangible benefits beyond future-proofing. The newer API provides better threading models, clearer separation of concerns, and improved HTTP message handling. Unlike the legacy Extender API where you manually parsed bytes and managed UI components with Swing directly, Montoya abstracts these concerns with cleaner interfaces. The MontoyaApi instance gives you structured access to HTTP handlers, UI components, logging, and persistence—making the codebase more maintainable and less prone to concurrency bugs.
The test application included in the repository demonstrates practical usage. It's a Gradle-based server that serves endpoints expecting serialized objects, allowing you to validate the plugin's functionality before deploying it against real targets. This is particularly valuable for understanding how different serialization patterns appear in the XML output and for testing edge cases like circular references or transient fields.
Gotcha
The most significant limitation is the manual JAR discovery process. In complex enterprise applications with dozens or hundreds of dependencies, identifying which JARs contain the necessary class definitions becomes archaeological work. You'll often encounter ClassNotFoundException errors when deserializing objects, forcing you to hunt through application directories, classpaths, and even decompile bytecode to understand dependencies. There's no automated discovery mechanism—you must gather and load JARs yourself, which can be time-consuming when testing large applications with opaque deployment structures.
XStream itself introduces constraints. While powerful, it has known security considerations (ironic for a security tool) and may struggle with heavily obfuscated classes, exotic serialization customizations via readObject/writeObject methods, or classes that implement Externalizable. Some serialization patterns simply won't translate cleanly to XML, resulting in deserialization failures or information loss. Additionally, the plugin only handles standard Java serialization—if your target uses Kryo, FST, Hessian, or protocol buffers, you're out of luck. Modern microservices architectures rarely use Java serialization anymore, preferring JSON or gRPC, which limits the tool's applicability to legacy systems and specific protocols like RMI or JMX.
Verdict
Use if: You're penetration testing legacy Java applications—especially thick clients, RMI services, JMX implementations, or Java Web Start applications that communicate via serialized objects. The tool excels when you need to understand and manipulate complex object graphs during security assessments, and you have access to application JARs or can reverse-engineer class structures. It's particularly valuable for discovering logical flaws in serialized business objects or testing deserialization vulnerabilities. Skip if: Your targets use modern REST/JSON APIs, non-Java serialization formats, or you're working purely with web applications that don't employ Java serialization. Also skip if you need automated vulnerability detection rather than manual inspection—tools like Java Deserialization Scanner or Freddy are better suited for automated scanning workflows. The upfront effort of gathering JARs only pays off when serialization is a significant part of your attack surface.