Est.

Insecure Deserialization Vulnerabilities in SaaS API Layers

Attackers exploit deserialization before your validation code even runs.

Contributing Editor · · 10 min read
Cover illustration for “Insecure Deserialization Vulnerabilities in SaaS API Layers”
Verified Vulnerabilities & Exploits · August 28, 2026 · 10 min read · 2,281 words

Insecure deserialization finishes its job before your code even gets a vote. The payload runs during object reconstruction, before any validation logic wakes up, so the usual playbook of "check the input, then act on it" just doesn't apply here.

Serialization sounds dry, but the idea is simple. An app takes an object in memory, an order record, a user session, a config blob, and flattens it into something you can send over a wire: JSON, XML, a binary stream. Deserialization is the reverse: the receiving end reads that data and rebuilds the object. Here's the catch: a lot of language runtimes call methods automatically as part of that rebuild. Constructors, finalizers, "magic methods" in languages like PHP and Python. Those calls fire before your app has looked at the result. So if an attacker controls what's inside that serialized blob, they control what code runs, and they control it before you get a chance to look at anything.

That's what sets deserialization apart from something like SQL injection or cross-site scripting. With SQL injection, a bad string has to travel through your code and reach a query before it does damage, and you can catch it at a dozen points along the way. With deserialization, the dangerous code runs at the parsing layer itself. There's no later checkpoint, because the "later" you were counting on already happened.

This hits APIs hard, because APIs are built around taking structured, serialized payloads. That's the whole design: JSON bodies, XML documents, binary formats moving between services. Most API layers assume whatever's hitting them already got cleaned upstream, by a gateway, another service, some earlier check. Attackers know that assumption is sitting there, and they build straight for it.

I've come to sort the damage into three piles. Remote code execution is the headline case: the attacker controls the object your server rebuilds and uses it to run whatever commands they want. Denial of service is quieter but just as real. Deserialization is expensive to run, and a deeply nested or oversized payload can eat through server resources fast enough to take a service down. Privilege escalation shows up when a deserialized object carries permissions higher than the caller should have, and the app just trusts it, no identity check required.

How attackers actually exploit deserialization in API layers

Any endpoint that takes serialized input from a client is fair game. That includes the obvious spots, request bodies, but also cookies, hidden form fields, and headers passed between internal services that nobody bothered to lock down.

Most of these attacks don't involve writing a single line of new malicious code. Attackers build what's called a gadget chain: a sequence of classes already sitting in your app's dependencies, strung together so that, when reconstructed in a specific order, they produce a dangerous side effect. The "exploit" is really just your own libraries, rearranged against you.

Type confusion is a close cousin. The attacker swaps the class the app expects for a different one sitting in the same runtime, and the app, trusting the payload, deserializes it without ever checking it got what it asked for. If that substitute class has permissions or side-effecting methods the original didn't, the app acts on it anyway.

Microservice setups make this worse. Internal service-to-service calls often skip authentication entirely, on the assumption that anything inside the network can be trusted. A single compromised edge service can then pass a malicious payload straight into internal endpoints that were never built to handle attacker-controlled data, because nobody expected an attacker to get that far.

CVE-2017-9805, in the Apache Struts REST plugin, is the textbook case here. Crafted XML payloads sent through XStreamHandler got remote code execution with zero authentication required. The Equifax breach, 147 million records exposed, traces back to an unpatched Struts flaw from that same codebase and era. The exposed surface was a plain REST endpoint doing what REST endpoints do: processing XML. The fix wasn't a patch buried in some business logic path; it was architectural, because the deserializer itself was the vulnerability.

Which language runtimes and data formats carry the most risk

Table: Deserialization Risk by Language Runtime. Compares Primary Risk Vector, Key Danger, Notable CVE and Relative Risk Level by Java, Python, PHP and .NET.

Java carries the longest track record here. Native Java serialization is a binary format that ships with full class metadata attached, which hands an attacker everything needed to build a gadget chain. CVE-2015-4852, in Oracle WebLogic's T3 protocol, deserialized Java objects straight off the network without checking them; attackers used gadget chains through Apache Commons Collections to get remote code execution, and kept exploiting it for years after the fix went public. Libraries like Commons Collections, Spring, and Hibernate all carry known chains, which means an app can be written carefully and still be exposed, just because of what's sitting in its dependency tree.

Python's risk lives mostly in pickle. The pickle module can run arbitrary Python code during deserialization, and Python's own docs say it plainly: don't unpickle data you don't trust. The tricky part is where pickle shows up. It's common in ML model loading, in job queues like Celery, in caching layers, places engineers don't usually think of as user-facing. Research has found that a large share of popular HuggingFace repos still carry pickle-backed models, including releases from major tech companies. Deserialization risk has crawled its way into the ML supply chain, and most teams pulling down a pretrained model aren't thinking about it that way at all.

PHP has its own long-running hazard in unserialize(). It pulls class names and properties straight from attacker-controlled input, and magic methods like __wakeup and __destruct fire automatically during reconstruction. Legacy PHP APIs and CMS-backed SaaS products still commonly expose unserialize() on request parameters or cookies, sometimes without anyone on the current team even knowing it's there.

Ruby and Node.js sit lower on the risk scale, at least by default. Node's default JSON parsing is safe, and Ruby's core language doesn't hand out the same footguns Java and PHP do. But YAML libraries in Ruby have a history of deserializing arbitrary objects, so library choice still matters, and any Node service handling binary or custom serialization formats picks up the same risk through its dependencies.

.NET has BinaryFormatter, which Microsoft has deprecated, but plenty of legacy SaaS codebases still run it in production today. CVE-2025-53690, in Sitecore, paired insecure deserialization with a hardcoded ASP.NET machine key to allow unauthenticated remote code execution; CISA ordered federal agencies to patch by September 25, 2025. That deadline says this isn't some historical curiosity. It's live, in modern.NET products, right now.

And the format matters on its own, apart from the language. XML parsers carry their own baggage around XXE-style issues, YAML parsers have their own history, and binary formats like MessagePack or Protocol Buffers each carry assumptions that can quietly drift from what the API layer thinks it's getting.

Why standard scanning tools miss most deserialization vulnerabilities

DAST scanners work by throwing known bad strings at an endpoint and watching for predictable error patterns. Deserialization exploits don't play along with that model, because the payload that works depends entirely on which libraries are loaded in the target's runtime. A generic scanner has no way to know that, so it has no way to even build the payload.

SAST tools can flag the dangerous function calls, pickle.loads, Java's ObjectInputStream, but flagging a function call isn't proof that attacker-controlled data ever reaches it. That gap floods teams with findings that look alarming and mostly aren't, and it trains engineers to tune the noise out.

Here's the real cost: a finding that just says "ObjectInputStream detected" isn't a working exploit. Without proof the flaw is reachable, teams reasonably shove it behind issues that come with evidence attached. I don't blame them for that.

Internal endpoints get missed even more. DAST tools point at the external-facing API surface almost by default, so deserialization sitting inside service-to-service calls within a VPC rarely makes it into scope at all. These vulnerabilities can sit undetected in production for months, sometimes years, and WebLogic's CVE-2015-4852 stayed exploitable for years after disclosure, with organizations continuing to run vulnerable configurations long after the fix was available.

What an effective pen test for deserialization actually requires

Blackbox testing undersells this vulnerability class almost by design. A tester who can't see the source code is guessing at which serialization library is in play and which gadget chains might fit, and most deserialization flaws never surface under those limits. Blackbox engagements can return clean results even when a target is running a known-vulnerable Commons Collections version.

Whitebox access changes the math completely. Source review shows exactly which deserialization calls exist, what data can reach them, and which library versions sit in the dependency tree, so a tester builds a targeted payload instead of throwing guesses at a wall.

A skilled tester does four things a scanner never will. They trace data flow from the API input all the way to the deserialization call, confirming attacker-controlled data actually gets there. They pin down the specific gadget chain that fits the target's exact dependency versions. They build a working exploit and confirm remote code execution or privilege escalation before writing anything down. And they chain the flaw with adjacent weaknesses, an SSRF that reaches an internal endpoint, a missing auth check on a service call, to show what the business impact looks like in practice, not on a whiteboard.

Exploit proof has to be the bar, because a finding without a working exploit can't really be weighed for risk and just gets waved off as a maybe. Testing scope needs to reach past the external API into internal services, job queue consumers, cache layer readers, and ML model loading pipelines, because that's exactly where this risk hides.

Certifications matter more here than in most security disciplines. OSCP and OSWE make candidates build working exploits under exam conditions; a certification built on multiple-choice questions doesn't prove someone can construct a gadget chain from scratch. And the report itself needs to be reproducible: the payload, the affected endpoint, the observed impact, spelled out clearly enough that someone can retest it once a fix goes in.

Remediating deserialization vulnerabilities without breaking the API

The cleanest fix is to stop taking serialized objects from untrusted sources at all. Where the architecture allows it, swap the format for something data-only, JSON without embedded class metadata, so there's nothing left for a gadget chain to grab onto.

Full replacement isn't always realistic on a short timeline, so a layered defense fills the gap in the meantime. Allowlist the classes permitted before deserialization runs; Java's ObjectInputFilter and similar tools let you reject anything outside the expected set outright. Run deserialization inside a sandboxed context with the fewest permissions possible, so a successful exploit still has nowhere useful to go. Cap the size and complexity of incoming serialized payloads to shut down the denial-of-service path. Sign serialized data at the source and check that signature before deserializing anything, so tampering gets caught before the dangerous call fires.

Dependency hygiene closes a real chunk of this on its own. Gadget chains rely on specific vulnerable versions of libraries like Apache Commons Collections and Spring; keeping those current wipes out entire categories of known chains, though it doesn't touch the structural risk underneath.

For Python specifically, moving off pickle in favor of JSON or MessagePack with schema validation removes the code-execution risk from job queues, caches, and ML pipelines in one move. For the ML model supply chain, that means checking where a model actually came from, favoring safe-loading formats, and treating any model file pulled from an external repo with the same suspicion you'd give a raw API payload.

None of this counts as done until it's retested. A fix that moves the deserialization call behind an allowlist but misconfigures that allowlist is still exploitable, just less obviously so. The only real confirmation is running the exact exploit that proved the original finding and watching it fail. Fix, retest with the original payload, confirm it no longer runs, log the remediation date. That's exactly the cycle auditors and enterprise customers ask to see.

Integrating deserialization checks into the development lifecycle before the next pen test

A pen test is a snapshot. Finding and fixing a deserialization flaw today does nothing to stop the same class of bug from showing up six weeks later in a new microservice or a routine dependency bump. Per the 2025 Verizon DBIR, vulnerability exploitation rose 34% year-over-year and now accounts for 20% of all breaches, making continuous vigilance more important than ever.

CI/CD is where this has to live day to day. Automated dependency scanning on every pull request should flag any newly introduced library with a known deserialization gadget chain attached. SAST rules should alert whenever ObjectInputStream, pickle.loads, unserialize(), or BinaryFormatter show up in new code, not as an automatic block, but as a mandatory human review gate. And any new API endpoint taking serialized input should need explicit sign-off on the serialization format and class allowlist before it ever merges.

None of that replaces a human reviewer, and it never will. Automated gates catch known patterns well but don't reason about a novel gadget chain in a fresh library version, or about deserialization tucked into some code path nobody thought to flag, cache hydration, webhook consumers, that kind of thing.

Walking into the next pen test with a list ready makes the whole process sharper: every endpoint and background process that takes serialized input, the serialization libraries in use and their versions, and the findings from the last round. That lets testers spend their time confirming the fixes actually held and hunting for the adjacent paths the first engagement never had time to cover.

Sources

  1. startupdefense.io
  2. owasp.org
  3. apisec.ai
  4. chs.us
  5. cyserch.com

More in Verified Vulnerabilities & Exploits