Mayank Chaudhari
Back to Blog

Unsafe Deserialization: The Silent Killer in Modern Frameworks

Unsafe Deserialization: The Silent Killer in Modern Frameworks
Engineering
#History#Security#Deserialization#Patterns#Architecture

History doesn't repeat itself, but it does deserialize the same vulnerabilities.

CVE-2025-55182 (React2Shell) is gaining headlines, but to security veterans, it looks depressingly familiar. It belongs to the class of bugs known as "Unsafe Deserialization", a persistent nemesis of web frameworks for over two decades.

The Hall of Infamy

1. Java Serialization (The OG)

In the 2010s, Java apps were riddled with bugs where untrusted binary streams were deserialized into objects. Tools like ysoserial were created just to generate RCE payloads for these cases.

  • Result: Massive enterprise breaches (Equifax, etc.).

2. Ruby on Rails (YAML)

Rails developers loved YAML. It was clean! It was readable! It also allowed instantiating arbitrary Ruby objects.

  • The Attack: YAML.load(user_input) could create a gadget chain leading to RCE.
  • The Fix: YAML.safe_load.

3. Python (Pickle)

The documentation for Python's pickle module has a giant red warning box: "The pickle module is not secure. Only unpickle data you trust." Yet, countless ML models and web caches still use it carelessly.

4. JavaScript (The Newcomer)

For a long time, JS was safe because JSON.parse is "dumb". It only creates plain objects, arrays, strings and numbers. It cannot create functions or class instances.

Enter Modern Frameworks. We wanted more. We wanted to send Date objects, Map, Set, and Promise across the wire. So we built "Super JSON" parsers (like devalue, seroval, and React's Flight protocol).

And with that complexity, we re-introduced the bug.

Why Is It So Hard?

The core problem is Type Reconstruction.

When you send a complex object referencing other objects (circular references, prototypes), the parser has to be incredibly clever to rebuild it. "Clever" code is hard to audit.

graph TD A[Raw String Input] --> B{Parser Logic} B -- "JSON.parse (Safe)" --> C[Plain Data] B -- "Custom Deserializer (Risky)" --> D{Check Type Tags} D -- "Safe Tag" --> E[Build Object] D -- "Malicious Tag" --> F[Instantiate Dangerous Class] F --> G[RCE]

The Solution: Dumb Pipes

The best defense is to stop trying to be smart.

If you need to send data from Client to Server, stick to JSON. If you need richness, explicitly map it yourself at the application level layer.

Don't rely on a "magic" framework feature to transparently transport your class instances across the network boundary. The magic is where the monsters hide.

Did you enjoy this post?

Give it a like to let me know!

Recommended Posts