Part of the Bug Bounty 60-Day Course
I’ve reached a point in this course where I want you to start thinking beyond automated scans and familiar payloads. We’re halfway through our 60-day Bug Bounty Course, and this is a good time to develop that mindset. In this lesson, I’ll show you how to recognise potential deserialization issues, investigate them in a controlled lab, understand the risks, and write a report that a security team can actually use. Welcome to Day 30: Deserialization Bug Bounty. Let’s learn to approach it like a responsible security researcher.
🗺️ What You’ll Learn in Day 30
Before we start testing, let me show you the path we’re going to follow. I’ll take you from understanding the vulnerability to investigating it in a lab and preparing a professional bug bounty report. By the end, you’ll know what evidence matters and how to explain the real security impact without making unsupported claims.
- Understand Unsafe Deserialization — Learn what serialization and deserialization mean, why applications use them, and where security problems begin.
- Recognise the Warning Signs — Identify suspicious request parameters, serialized objects, application behaviour, and error messages that deserve closer investigation.
- Explore Java, PHP and Python — Understand Java object deserialization, PHP’s
unserialize(), and Python pickle risks, including how their attack surfaces differ. - Think Like a Bug Bounty Hunter — Work through a realistic scenario to decide which clues to investigate and which conclusions the available evidence supports.
- Investigate with Burp Suite — Examine requests and responses in an intentionally vulnerable lab, compare application behaviour, and learn where security testing extensions can help.
- Understand Potential Impact — Learn how unsafe object handling can lead to data exposure, privilege abuse, denial of service, or remote code execution under specific conditions.
- Write a Professional Report — Document the affected endpoint, reproducible lab evidence, impact, severity rationale, remediation advice, and responsible disclosure details.
- Complete the Day 30 Challenge — Apply what you’ve learned to a controlled scenario and check whether you can distinguish a genuine vulnerability from a suspicious but inconclusive response.
My goal for you: Don’t just learn to recognise a payload. Learn to explain why a behaviour is dangerous, demonstrate it safely, and give developers enough evidence to fix the underlying problem.
Before we jump into deserialization testing, I want you to have a few basics in place. Don’t worry if you’re not an expert yet, but make sure you’re comfortable with the following:
- Burp Suite — Day 5: Have Burp Suite running and your browser configured to send traffic through its proxy.
- HTTP Basics — Day 3: Understand requests, responses, headers, cookies, and request bodies. We’ll use these concepts throughout the lesson.
- A local testing lab: Set up an intentionally vulnerable application such as WebGoat or a suitable Docker-based lab. We’ll keep all practical testing inside an environment you control.
- Day 29 — Web Cache Poisoning: Reuse your existing lab setup where possible so you can focus on the new vulnerability rather than spending time configuring tools.
- Day 9 — SQL Injection and Day 10 — SSRF: If you’ve completed these lessons, you’ll already be familiar with investigating application behaviour and tracing potential attack paths. That experience will help you approach deserialization testing more confidently.
My advice: Spend a few minutes checking your setup before continuing. A working proxy and a reliable local lab make it much easier to understand what’s happening when we inspect requests and responses. Remember, only test systems you own or are explicitly authorised to assess.
📋 Deserialization Bug Bounty – Table of Contents
Let me put Day 30 into perspective. Over the first 29 days, we’ve explored different ways to identify weaknesses in web applications, from SQL injection and SSRF to web cache poisoning. Each lesson has helped you develop a better eye for things that don’t look right. Today, I want to build on that experience with a vulnerability that’s a little harder to spot.
Here’s the challenge with deserialization: the clues aren’t always obvious. You might see a Base64-encoded value in a cookie, an unusual binary parameter, or a block of data that looks meaningless at first glance. The real question is what the application does with that data after it receives it. Under the right conditions, unsafe deserialization can lead to serious consequences, including remote code execution.
That’s why I want you to approach today’s lesson with curiosity rather than simply hunting for a ready-made payload. We’ll learn to recognise the clues, investigate application behaviour in a controlled lab, and determine whether the evidence supports a genuine vulnerability. We’re at the halfway point of our 60-day journey, and my goal is to help you think less like someone running tools and more like a security researcher who understands what they’re looking for. Let’s build that instinct together.
What Serialization Is and Why It Matters
Let’s start with the basics, because once you understand how serialization works, the security risk becomes much easier to recognise. Imagine a Java application has a user object in memory containing a username, account settings, and session information. The application may need to save that information or send it to another service. Serialization converts the object’s data into a format that can be stored or transmitted. Deserialization does the opposite: it reads that data and reconstructs an object the application can use.
You’ll find this process in many applications. A Java service might serialize objects for storage or communication between components. A Python application might use a serialization format to send background tasks through a message queue. A PHP application might serialize data before storing it or passing it between parts of a system. The exact format and behaviour depend on the language, library, and implementation.
Now, here’s where I want you to start thinking like a security researcher. What happens when an application accepts serialized data from a source an attacker can influence? That data might arrive through a cookie, a request parameter, a POST body, or a message queue. If the application reconstructs objects without appropriate safeguards, it may allow unexpected object states or behaviours. In some vulnerable implementations, object reconstruction can trigger existing methods in an unsafe sequence, potentially leading to serious security consequences.
One important distinction: an attacker doesn’t necessarily need to inject an entirely new program. In certain deserialization vulnerabilities, the danger comes from manipulating data to make existing application code or library functionality behave in an unintended way. Depending on the available classes, execution paths, and security controls, the impact can range from application errors to denial of service or remote code execution. Simply spotting serialized data, however, does not prove that a vulnerability exists.
Note: You may see older resources refer to this issue as Insecure Deserialization or OWASP Top 10 A8:2017. OWASP’s current Top 10 uses different categories, so don’t treat A8 as the current category number. The underlying vulnerability remains important to understand and test for.
A Simple Serialization Example
Let me show you what serialization looks like in practice. Imagine we’re building a small web application that stores a user’s profile. In Python, we might represent that profile as a dictionary:
user = {
"username": "elite_hunter",
"role": "student",
"active": True
}This is ordinary Python data stored in memory. If we want to convert it into a JSON string so it can be saved or transmitted, we can use Python’s built-in json module.
import json
# Original object
user = {
"username": "elite_hunter",
"role": "student",
"active": True
}
# Serialization: Python object to JSON text
serialized_data = json.dumps(user)
print(serialized_data)Output:
{"username": "elite_hunter", "role": "student", "active": true}Notice what happened? Our Python dictionary has been converted into a JSON string. That’s serialization. The data can now be stored in a file or sent to another service that understands JSON.
Now let’s reverse the process. When the application receives that JSON string, it can convert it back into a Python object using json.loads().
# Deserialization: JSON text back to a Python object
restored_user = json.loads(serialized_data)
print(restored_user["username"])
print(restored_user["role"])Output:
elite_hunter
studentjson.dumps() serializes data, while json.loads() deserializes it. JSON is generally safer to process than formats that reconstruct arbitrary programming-language objects, but applications must still validate the data and enforce authorisation rules. Python’s pickle, Java native serialization, and certain PHP object-deserialization patterns have different security risks that we’ll explore in this lesson.As a bug bounty hunter, I don’t report an application simply because it accepts serialized data. I investigate how that data is processed, whether an attacker can influence it, and whether I can demonstrate a real security impact in an authorised testing environment. That’s the difference between spotting a format and identifying a vulnerability.
🧪 Safe Practice Exercise: Serialize, Restore and Validate Data
Now it’s your turn. I want you to run a small experiment on your own machine. We’ll serialize a user profile, restore it, and then deliberately change the input to see why applications must validate data they receive.
Objective: Understand the difference between converting data and trusting data. We’ll use Python’s JSON module because it provides a straightforward way to practise these concepts safely.
Step 1 — Create a practice file
Save the following code as serialization_practice.py and run it using Python 3.
import json
user = {
"username": "elite_hunter",
"role": "student",
"active": True
}
# Serialize the Python dictionary
serialized = json.dumps(user)
print("Serialized data:", serialized)
# Restore the dictionary
restored = json.loads(serialized)
print("Restored username:", restored["username"])
print("Restored role:", restored["role"])Step 2 — Test unexpected input
Next, replace the original dictionary with the JSON string below. Notice that the role has changed from student to admin.
incoming_data = '{"username": "guest", "role": "admin", "active": true}'
restored = json.loads(incoming_data)
print("Username:", restored["username"])
print("Requested role:", restored["role"])Run it and observe the output. JSON parsing succeeds, even though the role is unexpected. This is an important lesson: valid serialized data is not necessarily trustworthy data. Deserialization alone doesn’t establish that the user is entitled to the requested role.
Step 3 — Add a validation check
Let’s enforce a simple rule that accepts only the fields and role values our application expects.
import json
incoming_data = (
'{"username": "guest", '
'"role": "admin", '
'"active": true}'
)
data = json.loads(incoming_data)
allowed_fields = {"username", "role", "active"}
allowed_roles = {"student", "researcher"}
if (
isinstance(data, dict)
and set(data.keys()) == allowed_fields
and isinstance(data.get("username"), str)
and isinstance(data.get("active"), bool)
and data.get("role") in allowed_roles
):
print("Input passed basic validation.")
else:
print("Input rejected: unexpected data or role.")The modified input should be rejected because admin isn’t in our allowed role set. This is a basic demonstration, not a complete authentication system. In a real application, permissions must come from trusted server-side identity and authorisation logic, not a role supplied by the client.
Your challenge
- Change the role to
student. Does the input pass? - Change
activefromtrueto the string"true". What happens? - Add an unexpected field called
is_admin. Does the validation reject it? - Explain why successfully parsing JSON does not prove that an application is secure.
Completion checklist
Before moving on, make sure you can explain these three points in your own words:
- Serialization converts an object into a transferable or storable representation.
- Deserialization reconstructs data from that representation.
- Applications must validate untrusted input and enforce permissions independently of user-controlled data.
Next step: With these basics in place, we’re ready to explore why native object deserialization in Java, PHP, and Python can create more serious security risks under specific conditions.
Recognising the Surface: Serialization Markers by Language
Now that we’ve covered the basics, let’s talk about how I start looking for potential deserialization surfaces during an authorised security assessment. My first step is to understand how the application handles data between requests. I inspect cookies, POST bodies, query parameters, and other places where an application might store or transmit state. Sometimes the values look readable; other times, they’re encoded or appear to be binary data.
Certain serialization formats have recognisable signatures, headers, or structural patterns. These can give us clues about how data was produced. For example, Java’s native serialization stream commonly starts with the bytes AC ED 00 05. Base64-encoded data may conceal the original representation, while PHP serialized values often contain readable type and length markers. Python’s serialization formats vary, so there isn’t one universal prefix that identifies every pickle payload.
Here’s the important distinction I want you to remember: a recognisable marker is a clue, not proof of a vulnerability. The application might never deserialize that value, the format might be used safely, or the data might be protected by integrity checks. My goal at this stage is to form a hypothesis that I can verify in a controlled lab or within the scope of an authorised bug bounty programme.
What I look for
- Java: The native serialization stream header
AC ED 00 05is a useful recognition clue when inspecting raw binary data. - PHP: Serialized values may contain patterns such as
s:for strings,i:for integers, ora:for arrays. The context and complete structure matter. - Python: Pickle is a binary-oriented format with protocol-dependent structures. Its representation varies by protocol, so don’t rely on a single byte sequence to identify every instance.
- Encoded values: A Base64-looking string may contain serialized data, but Base64 is only an encoding scheme and can represent many unrelated kinds of content.
For the practical part of this lesson, keep your testing within an intentionally vulnerable local application or another explicitly authorised environment. We’ll use the clues above to understand the attack surface without treating every encoded cookie or unusual request parameter as a security bug.

securityelites.com
That rO0AB at the beginning of a Base64-encoded value is one of the most useful patterns I want you to recognise in this lesson. It commonly corresponds to Java’s serialization stream header, whose magic bytes are AC ED 00 05. When I spot this pattern in a cookie or request parameter in Burp Suite, I flag it for investigation. It gives me a starting point, but I still need to establish whether the application actually deserializes the value and whether that behaviour creates a security risk.
There’s one more signal I always check: the Content-Type header. Some Java applications use Content-Type: application/x-java-serialized-object to identify Java serialized data. If you see this header in Burp, record it alongside any serialization markers you find. It provides useful context about the intended data format, although the header alone doesn’t prove that the application deserializes the input or that the implementation is vulnerable.
Replace BASE64_VALUE with the complete Base64-encoded value from your lab. If the value is URL-encoded, decode that representation first. The original sample string is abbreviated and shouldn’t be relied on to reproduce the displayed HashMap bytes; use a complete sample when verifying the output.
java.util.HashMap, record it as an observation about the serialized data. It may help you understand the object structure, but it doesn’t independently prove which endpoint reconstructs the object or whether a dangerous method executes. Use that information to guide safe investigation in an authorised lab, and distinguish observed facts from confirmed impact in your report.Here’s the workflow I follow: recognise the possible serialization marker, inspect the relevant headers, decode a copy of the value, and replay the original request in Burp Repeater to establish a baseline. From there, I investigate how the application handles the data in a controlled environment. I only report unsafe deserialization when the evidence supports the vulnerable behaviour and its security impact.
🎯 Lesson Takeaway: Recognise First, Verify Next
If there’s one thing I want you to remember from this section, it’s this: finding a serialization marker is the beginning of an investigation, not proof of a vulnerability.
- Recognise: The Base64 prefix
rO0ABcan indicate Java serialization data, whileAC ED 00 05is the corresponding stream header. - Inspect: Check the request parameters, cookies, and
Content-Typeheaders in Burp Suite for clues about how the application handles the data. - Verify: Replay the original request and investigate the application’s behaviour in an authorised lab. Don’t confuse an unusual response with a confirmed vulnerability.
- Document: Record the evidence you observed, explain what remains unverified, and establish the security impact before assigning severity.
My advice is simple: don’t chase every encoded value you see. Learn to recognise useful clues, ask the right questions, and follow the evidence. That’s how you move from spotting interesting traffic to producing a credible bug bounty finding.
🧠 EXERCISE 1 — THINK LIKE A HACKER (10 MIN · NO TOOLS)
Before we open Burp Suite, I want you to practise recognising clues and asking the right questions. Read each scenario carefully and try to answer in your own words before revealing the explanations.
- The Java cookie: During an authorised assessment, you notice a cookie named
sessionwith the valuerO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcA==. What does this value suggest? What would you need to establish before concluding that the application is vulnerable? - The PHP preferences: A PHP application stores a
prefscookie. After decoding it, you finda:2:{s:5:"theme";s:4:"dark";s:4:"lang";s:2:"en";}. Does this resemble PHP serialized data? Does that automatically mean it’s exploitable? Explain the difference between identifying a format and proving a vulnerability. - The Python pickle endpoint: An ML inference API accepts Python pickle data from clients. Why can this design be dangerous, even if you haven’t identified a gadget chain? What factors would you investigate before assigning severity?
1. Java: The value begins with a Base64 representation consistent with a Java serialization stream header. That’s a useful clue, but it doesn’t prove that the endpoint deserializes the cookie. I’d inspect the request context and investigate the server’s handling of the value in an authorised lab before making a vulnerability claim.
2. PHP: Yes, the structure resembles a serialized PHP array containing two string values. That alone doesn’t establish a vulnerability. I’d investigate whether the application uses unsafe deserialization, whether attacker-controlled data can influence object reconstruction, and whether a relevant code path creates a demonstrable security impact. PHP object magic methods can be relevant to certain exploit paths, but an ordinary serialized array doesn’t prove that such a path exists.
3. Python: Python’s pickle format can invoke functions during deserialization, so malicious pickle data can potentially execute code without requiring a separately discovered gadget chain. Accepting untrusted pickle data is therefore a serious design concern. However, the final severity depends on the actual exposure, trust boundaries, reachable processing path, privileges, and demonstrated impact. I wouldn’t label every pickle endpoint Critical without assessing those factors.
Gadget Chains — What Makes a Surface Exploitable?
Finding a possible deserialization surface is only the first part of the investigation. The next question I ask is: what can the application actually do with the data it reconstructs? This is where gadget chains become important. A serialization marker might tell me where to investigate, but it doesn’t tell me whether an attacker can turn that behaviour into a meaningful security impact.
So, what exactly is a gadget chain? Think of it as a sequence of existing classes and methods that can be triggered through a particular object structure. Under the right conditions, those calls can produce an unintended result, potentially including remote code execution. In Java applications, researchers have studied gadget chains involving libraries such as Apache Commons Collections, Apache Commons BeanUtils, and components of the Spring Framework.
Here’s an important detail: these libraries are not inherently malicious. They’re legitimate software packages used in real applications. The risk depends on the classes and methods available, how they interact, whether the relevant execution path can be reached during deserialization, and what security controls are in place. A library appearing in a dependency list is a reason to investigate, not proof that the application is exploitable.
When I’m assessing a Java application, I look for evidence about its dependencies and configuration where that information is legitimately available. I also consider whether the suspected deserialization path is reachable and whether protective controls prevent unsafe object reconstruction. A dependency inventory can help me form a hypothesis, but I need additional evidence before claiming that a particular gadget chain works.
You don’t need to memorise every historical gadget chain to become a capable bug bounty hunter. What matters is understanding the relationship between the input, the deserialization process, the available application code, and the resulting impact. That’s how you distinguish a potentially interesting surface from a substantiated security finding.
💡 A Simple Gadget-Chain Analogy
Let me explain gadget chains with an everyday example. Imagine a row of dominoes standing next to each other. Pushing the first domino triggers the next one, which triggers another, and eventually the whole row falls. Each domino works exactly as designed; the unexpected result comes from the way the pieces interact.
A gadget chain follows a similar idea. An application already contains classes and methods that perform legitimate tasks. During unsafe deserialization, a carefully structured object may cause certain methods to run in an unintended sequence. If that sequence reaches a dangerous operation, the result could be a serious security issue, potentially including remote code execution.
Here’s the distinction I want you to remember: having the dominoes doesn’t mean an attack will succeed. The pieces must be present, the sequence must be reachable, and the application must allow the relevant behaviour. In the same way, finding a library associated with a known gadget chain does not automatically prove that an application is exploitable.
When I investigate a deserialization finding, I ask three questions:
- What pieces are present? Which relevant classes and libraries are available?
- Can the sequence be triggered? Can untrusted input reach the required execution path?
- What is the actual outcome? Does the behaviour create a demonstrable security impact?
Once you understand these three questions, gadget chains become much less mysterious. You’re no longer just looking for a library name; you’re reasoning about how existing code can interact in an unsafe way.
Finding Deserialization Clues with Burp Suite
Now let’s bring the concepts into Burp Suite. When I’m assessing a web application, I start with the traffic the application naturally generates. Burp’s HTTP history gives me a convenient place to review requests, responses, cookies, parameters, and headers that might contain serialized data. If Burp Scanner is available in my setup, I can also use its findings as additional leads, although automated detection won’t identify every deserialization issue.
Once I find something suspicious, I send the original request to Repeater. This lets me examine the request and response carefully and establish a baseline before making any changes. I can also review available extensions, including deserialization-focused research tools, to understand what they detect and what their results actually mean. I always check an extension’s documentation and keep active testing within the programme’s authorised scope.
You may encounter deserialization research tools that use out-of-band interactions, including DNS callbacks, to investigate certain behaviours. These tests can be useful in an appropriately authorised environment, but their results need careful interpretation. A callback might demonstrate that a particular operation was triggered; it does not automatically prove that unsafe deserialization occurred, nor does it establish remote code execution. You need to understand exactly what the test sent and what the observed callback demonstrates.
For this lesson, I want you to focus on building a reliable evidence trail. Record the suspicious parameter, the format indicators you observed, the original response, any scanner findings, and the limitations of your conclusions. If you need to demonstrate potentially dangerous behaviour, reproduce it in your own lab rather than escalating tests against a live service without explicit permission.
🔎 Beginner-Friendly Serialization Marker Checklist
When I review traffic in Burp Suite, I don’t try to memorise every possible payload. I start with a few recognisable patterns and use them as clues for further investigation. Here’s the checklist I’d recommend keeping beside you during Day 30.
1. Java serialization
- Raw bytes:
AC ED 00 05 - Common Base64 prefix:
rO0AB - What I do: Inspect a copy of the value and check whether the decoded bytes match the Java serialization header.
2. PHP serialized data
- Common patterns:
s:for strings,i:for integers,a:for arrays, andO:for objects. - What I do: Check whether the value follows PHP serialization syntax and investigate how the application processes it.
3. Python pickle
- Possible indicator:
80 04at the beginning of a protocol 4 pickle stream. - What I do: Remember that pickle formats vary by protocol. I don’t rely on one prefix to identify every pickle stream.
4. HTTP headers and request locations
- Cookies and session-related values.
- POST request bodies and query parameters.
- Headers such as
Content-Type: application/x-java-serialized-object. - Encoded values that become more recognisable after decoding a copy.
5. Before you report anything
- Identify exactly where the suspicious value appears.
- Record the original request and response.
- Determine whether the application actually deserializes the data.
- Verify the behaviour safely in an authorised environment.
- Document the demonstrated impact instead of assuming exploitability.
My rule: A marker tells me where to look next, not what to report. An encoded cookie isn’t automatically vulnerable, and a scanner alert isn’t a substitute for evidence.
🔎 Safe Decoding Workflow: Inspect Before You Trust
When I investigate a possible deserialization issue, I never execute an unknown serialized object just to see what happens. I start by decoding the data as text or bytes, identifying its format, and documenting what the evidence actually proves.
Follow these five steps in your authorised lab or an explicitly approved test environment.
Step 1 — Capture the Request
In Burp Suite, open Proxy → HTTP history and select the relevant request. Look for encoded or structured data in cookies, request bodies, hidden fields, or custom headers.
Record the endpoint, parameter name, HTTP method, and response. Keep a copy of the original value so you can compare it with any decoded representation.
Step 2 — Decode Without Executing
If the value looks like Base64, use Burp Suite’s Decoder to decode it. Choose a text or hexadecimal view where appropriate. Decoding changes the representation of the data; it does not validate the data or prove that the application deserializes it.
For a local sample that you are permitted to inspect, you can decode Base64 into a file without running the resulting content:
# Decode a Base64 sample into raw bytes
printf '%s' 'BASE64_SAMPLE_HERE' | base64 --decode > decoded.bin
# Inspect the first 32 bytes as hexadecimal
xxd -l 32 decoded.bin
# Identify the file format using local file-signature hints
file decoded.binImportant: Replace the placeholder only with a sample you are authorised to inspect. The commands above write bytes to a file and inspect them; they do not deserialize or execute the content. Avoid uploading sensitive application data to public decoding websites.
Step 3 — Check for Format Indicators
- Java: The byte sequence
AC ED 00 05is a common Java serialization stream header. Base64-encoded Java streams may begin withrO0AB. - PHP: Strings such as
s:,i:,a:, andO:can appear in PHP’s serialized representation. - Python: Pickle data can contain protocol markers. For example,
80 04is the protocol-4 header, but pickle formats and protocols vary.
These indicators are clues, not verdicts. A marker may be incomplete, embedded inside another format, or unrelated to the application’s actual processing path.
Step 4 — Do Not Load or Execute Unknown Objects
Never test an unknown sample by calling Java deserialization routines, PHP unserialize(), or Python pickle.loads() on your everyday machine. Deserialization can trigger unsafe behavior, and Python pickle data in particular must be treated as executable-risk input.
If deeper analysis is required, use an approved, isolated lab with no sensitive credentials, restricted network access, and disposable test data. Prefer static inspection and vendor-supported analysis tools. Do not assume that a container or virtual machine makes every sample safe.
Step 5 — Verify the Application’s Behavior
Return to Burp Repeater and compare the original request with a harmless, controlled variation that is allowed by your test scope. Observe status codes, response differences, and server-side evidence available to you.
A successful decode, a recognizable marker, or an error message alone does not prove unsafe deserialization. Stronger evidence connects attacker-controlled input to an unsafe deserialization operation and demonstrates a reproducible security impact using a safe test case.
My rule of thumb: Capture → Decode → Identify → Inspect safely → Verify. Decode the bytes, never blindly execute the object.
Lab: Confirming Behaviour Safely in WebGoat
WebGoat is OWASP’s deliberately vulnerable web application, designed to help learners understand common web security weaknesses in a controlled environment. In this lab, I’ll use a compatible WebGoat release to study insecure deserialization and learn how to distinguish a recognizable serialization format from evidence of unsafe application behaviour.
Safety first: Run this lab only on your own machine or in an environment you are explicitly authorized to test. The commands below publish ports on the Docker host, so use a trusted machine and ensure those ports are not exposed to an untrusted network.
What I Verify in the Lab
I start by capturing a normal request in Burp Suite and identifying where the application receives the relevant input. If the lab provides a serialized value, I inspect its representation using Burp Decoder or a hexadecimal viewer without executing unknown data.
- Identify: Record the input location and any format indicators.
- Establish a baseline: Send the normal lab request and note the response.
- Compare safely: Follow the lesson’s instructions and use only its intended test cases.
- Observe: Record response changes, validation errors, and other reproducible evidence.
- Conclude carefully: A serialization marker or an error by itself does not establish an exploitable vulnerability.
When finished, stop and remove the lab container:
⚡ EXERCISE 2 — BURP LAB (20 MIN)
Investigate a possible deserialization surface in your own WebGoat instance. The goal is to identify the input, observe how the application handles it, and record reproducible evidence without executing unsafe payloads.
- With Burp proxying your WebGoat traffic, navigate through the relevant lesson on insecure deserialization. Review HTTP History for structured or encoded parameters. A value containing
rO0ABmay indicate Base64-encoded Java serialization data, but treat it as a clue rather than proof. - Select a relevant request and send it to Repeater. First, resend the original request to establish a baseline. Then, following the lab’s instructions, make a harmless change to a test value and compare the status code, response body, and any validation message. Do not assume an error proves unsafe deserialization; malformed input can fail during decoding or validation before deserialization occurs.
- Record the parameter name, its location in the request, any format indicators you observed, the baseline response, and the response to your controlled change. If the lab explicitly provides a safe way to inspect a decoded object, document any visible class information without executing the object or assuming that a class name alone establishes exploitability.
Writing the Report That Gets Paid
A deserialization finding reported as “I found Base64 in a cookie; it might be deserializing” gives triage very little to verify. A strong report identifies the affected input, explains the observed behaviour, provides reproducible evidence, and separates confirmed impact from potential impact. Report quality can make a finding much easier to validate, but no wording guarantees acceptance or a particular bounty. Let me show you a structure that works.
Severity: Pending triage — assess using demonstrated impact and the program’s severity policy
OWASP: A08:2021 — Software and Data Integrity Failures (relevant context; not a severity determination)
CWE: CWE-502: Deserialization of Untrusted Data
Summary: The [endpoint or feature] accepts a value in the [parameter or cookie name] field. The value contains [observed format indicator, if applicable], which may be consistent with serialized data. Controlled testing in the authorised environment produced [specific observed behaviour]. These observations suggest a possible deserialization issue, but the marker or error response alone does not establish that untrusted data reaches an unsafe deserialization operation or that the issue is exploitable.
Steps to Reproduce:
1. Access the application using an account and test environment permitted by the program.
2. Capture the relevant request in Burp Suite and identify the input under investigation.
3. Record the original request and baseline response.
4. Follow the program’s rules and use a harmless, controlled input variation or the program’s designated test case.
5. Compare the responses and document the exact, reproducible behaviour. State whether the evidence comes from a local lab or the in-scope target.
Evidence: [Attach sanitised baseline and comparison requests/responses, relevant application logs if available, and the exact observed error. Remove session tokens, credentials, personal data, and other secrets.]
Impact: The current evidence demonstrates [state only the confirmed behaviour]. The security impact remains [unconfirmed / partially confirmed / confirmed]. Remote code execution, authentication bypass, and unauthenticated exploitability must not be claimed unless supported by authorised, reproducible evidence. The presence of a serialization marker or a parsing exception alone does not establish Critical severity.
Remediation: Avoid deserializing untrusted data where possible. Prefer constrained formats such as JSON when object-graph serialization is unnecessary. Where deserialization is required, enforce strict type and input constraints before unsafe object construction, use supported class-filtering or allowlisting mechanisms, and apply integrity protection where appropriate. Signing or HMAC protection can help detect tampering, but it does not replace safe deserialization controls or prevent misuse of valid signed data.
Testing Scope: [Identify the authorised target, environment, test account, and any limitations. Clearly label any reproduction performed exclusively in WebGoat or another local lab.]
Suggested Severity: [Provide a severity rationale based on the program’s policy and demonstrated impact, or leave severity to triage when evidence is insufficient.]
Notice the distinction: the report documents what was observed, explains why it may indicate a security weakness, and identifies what remains unproven. A lab reproduction demonstrates the technique in that lab; it does not establish that a production target behaves the same way. Likewise, a known gadget library being present does not by itself prove that a usable gadget chain is reachable.
⚡ EXERCISE 3 — WRITE THE REPORT (15 MIN)
Turn your WebGoat observations into a clear, evidence-based vulnerability report. Your goal is to demonstrate what you verified, distinguish it from what remains uncertain, and justify your severity assessment.
- Using the template above as a guide, write a complete report for the behaviour you investigated in the WebGoat lesson during Exercise 2. Clearly identify WebGoat as your local lab target, not a production bug bounty target.
- Replace the placeholders with details from your own lab run: the actual endpoint, parameter name, baseline response, controlled test input, and observed result. Attach sanitised Burp evidence. If you observed only a format marker or parsing error, state that explicitly rather than claiming confirmed unsafe deserialization or remote code execution.
- Assess severity using the applicable CVSS version and explain your rationale. What impact have you actually demonstrated? What evidence would be needed to establish a more serious impact? A gadget chain is not a scoring switch: its presence, reachability, prerequisites, and demonstrated consequences all matter. Do not assign a universal score based only on whether a chain has been confirmed.
Severity, CVSS and What to Claim
One question I hear often about deserialization findings is: “Can I claim Critical without proving remote code execution?” My answer is: you can report a potentially serious weakness without demonstrating RCE, but your severity must match the evidence. A deserialization surface, a library that may support a gadget chain, or a DNS callback does not automatically establish Critical impact or a CVSS score of 9.8.
CVSS is based on defined metrics, not on the vulnerability name or the worst-case outcome someone can imagine. The score depends on the demonstrated attack path, network reachability, required privileges, user interaction, and the impact on confidentiality, integrity, and availability. A library appearing in a dependency manifest does not prove that a usable gadget chain is reachable, and a callback does not by itself prove RCE or even establish that deserialization caused the callback.
Where researchers get into trouble is claiming impact they have not demonstrated. If I report RCE, I need authorised, reproducible evidence supporting that claim. If I have confirmed that user-controlled input reaches an unsafe deserialization operation but have not demonstrated code execution, I state that limitation clearly. For example: “Testing confirmed that user-controlled input reaches the application’s deserialization path. The available evidence does not establish remote code execution. The dependency information suggests a potential avenue for further assessment, subject to reachability, configuration, and applicable mitigations.” That is a defensible technical conclusion; triage will determine severity under the programme’s policy.
Reserve Critical for evidence and a defensible impact assessment that meet the programme’s criteria. A marker, a suspected gadget library, or a DNS callback alone does not establish CVSS 9.8.
💰 Quick check: you find rO0AB in a cookie. The server is running a modern Java 17 LTS with no Commons Collections on the classpath. What’s the most accurate severity claim?
Frequently Asked Questions
What is insecure deserialization in simple terms?
Do I need to find a working gadget chain to report this?
What is a gadget chain?
Why is Python pickle especially dangerous?
How do I practice safely?
What severity should I claim?
📚 Further reading
- Web Cache Poisoning — Day 29 — another high-payout class that rewards precise reporting.
- Burp Suite Deep Dive — Day 5 — the tool driving the identification workflow here.
- SQL Injection Lab — complementary injection-class mindset.
- External: OWASP A8 Insecure Deserialization — authoritative definition and remediation guidance.
- External: PortSwigger Web Security Academy — Deserialization — the best free hands-on lab series for this topic.

