What is insecure deserialization?

What is insecure deserialization?

Most web apps need to save an object now and rebuild it later. Saving it as bytes is called serialization. Turning those bytes back into a live object is deserialization. Insecure deserialization is what happens when an app rebuilds objects from bytes it does not trust, like a cookie or a request body a user controls, and treats the result as if the app itself had created it.

What serialization and deserialization actually are

Serialization turns a structure in memory into a flat string of bytes you can store on disk or send over the network. Deserialization is the reverse step. Your app reads the bytes and reconstructs the object so it can call methods on it and read its fields.

Here is the part that trips people up. The bytes are not just data. In many languages they carry type information, field values, and sometimes instructions about which classes to build and which methods to run while building them. When the bytes come from a place the user can change, you are letting the user influence what your program constructs.

If untrusted bytes can decide which objects your code builds, the user is no longer sending you data. The user is sending you a program.

Why insecure deserialization is dangerous

An app usually treats a freshly deserialized object as trustworthy. It assumes the fields are sane and the types are the ones it expected. An attacker who controls the input breaks both assumptions. There are two distinct levels of damage, and it helps to keep them separate.

Level one: data tampering

The simpler attack just edits values inside the serialized blob. Say a session cookie stores a small object describing the logged in user. If the app serializes it in a readable format and does not sign it, the user can decode it, change one field, and send it back. A field that said role=user becomes role=admin. No code execution, no exotic tricks. The app deserializes the edited object and grants access it never meant to grant.

Level two: remote code execution through gadget chains

The serious version uses the deserialization step itself to run code. Some languages run special methods automatically while rebuilding an object, for example a setup or cleanup hook. A gadget is an existing class already on the app’s classpath whose automatic method does something useful to an attacker, like reading a file or calling another method. A gadget chain stitches several of these together so that simply rebuilding a crafted object kicks off a sequence that ends in command execution.

The important high level point: the attacker is not uploading new code. They are arranging classes the app already ships so that the act of deserializing runs them in an order the authors never intended. That is why this class of bug can jump from “changed a value” to “ran a shell command” with the same root cause. (No working chain is shown here on purpose. The defense is the same either way.)

Where insecure deserialization shows up

This is a language wide problem, not a single bad function. It appears anywhere an app turns attacker reachable bytes back into objects:

  • Java: ObjectInputStream.readObject() on data from a request, cookie, or message queue.
  • PHP: unserialize() on a value the client controls, which can trigger magic methods like __wakeup and __destruct.
  • Python: pickle.loads() on untrusted input. Pickle is documented as unsafe for this and can run arbitrary code by design.
  • .NET: formatters such as BinaryFormatter and some configurations of Json.NET that resolve types from the payload.
  • JSON with type hints: a $type or _class field that tells the parser which concrete class to build. Plain JSON is just data, but type aware deserialization brings the same risks back.
  • Cookies and sessions: any session token that stores a serialized object instead of an opaque id pointing at server side state.

A tampered session, and a safer design

Below is a readable, unsigned session value, the edited version an attacker would send, and an opaque token that removes the whole problem. This is intentionally generic and shows the shape, not a payload.

# Original session cookie (readable, not signed)
session = {"uid": 4181, "role": "user", "plan": "free"}
encoded = base64("{\"uid\":4181,\"role\":\"user\",\"plan\":\"free\"}")

# Attacker decodes, edits one field, encodes again, sends it back
tampered = base64("{\"uid\":4181,\"role\":\"admin\",\"plan\":\"free\"}")
# Server deserializes and now believes the user is an admin

# Safer: opaque id, real data stays on the server
set_cookie("sid", random_256_bit_id())     # nothing meaningful to edit
session = store.lookup(sid)                 # role comes from the database

# If a token must carry claims, sign it and verify before trusting it
token  = sign(payload, server_secret)
claims = verify(token, server_secret)       # reject on bad signature

# Never feed untrusted bytes to a native object builder
pickle.loads(request.body)                  # unsafe by design
data = json.loads(request.body)             # plain data, no type hints, then validate

The opaque id works because there is nothing inside the cookie worth editing. The signed token works because any edit breaks the signature and the server refuses it. The last line works because plain JSON parsing returns a dictionary, not a reconstructed class with hidden behavior.

How to detect it

  • Grep for the dangerous calls. Search the codebase for readObject, unserialize, pickle.loads, BinaryFormatter, and type aware JSON settings. Then ask where each input comes from.
  • Trace the source of the bytes. A deserialization call on a config file you ship is fine. The same call on a cookie, header, upload, or queue message is the risk.
  • Watch for telltale prefixes. Java serialized data often starts with the bytes ac ed 00 05, and base64 of that begins with rO0. Seeing that in a cookie is a strong hint. When you have an unknown blob in hand, our free file entropy and magic byte analyzer reads its leading bytes and entropy so you can tell a serialized object from plain compressed or encrypted data.
  • Test assumptions, not just signatures. Flip a role field or swap a declared type and see whether the app still trusts the object. That is exactly the kind of assumption a careful review checks.

How to prevent it

  • Do not deserialize untrusted input into native objects. This is the core fix. Use plain data formats like JSON or a strict schema, then validate fields by hand.
  • Use opaque session ids. Keep user role and permissions on the server, keyed by a random id, so the client holds nothing worth tampering with.
  • Sign anything the client carries. If a token must hold claims, sign it and verify the signature before reading a single field.
  • Allowlist types if you truly need typed deserialization. Restrict which classes the deserializer is allowed to build, and reject everything else by default.
  • Add integrity and isolation. Authenticate and encrypt stored objects, run parsers with low privileges, and keep dependencies patched so known gadget classes are gone.

For more on input that crosses a trust boundary, see our injection and input category, which groups the bugs that share this root cause.

Why this bug rewards understanding the app

Insecure deserialization is rarely found by throwing a fixed list of payloads at a target. It depends on which format the app uses, which classes it ships, and which fields it trusts after rebuilding an object. You find it by understanding what the app assumes and then testing whether those assumptions hold.

That is the kind of bug an autonomous researcher built to test assumptions is meant to catch. In early work, a frontier model drove that full method on its own and identified and verified real access control and injection issues in test apps it had not seen before. That is an encouraging early signal, not a benchmark. You can read more about the approach on our about page.

Frequently asked questions

What is insecure deserialization?

Insecure deserialization is what happens when an app rebuilds objects from bytes it does not trust, like a cookie or a request body a user controls, and treats the result as if the app itself had created it. In many languages those bytes carry type information and instructions about which classes to build, so the user is influencing what the program constructs. See the MITRE CWE 502 entry for the formal definition.

Can insecure deserialization lead to remote code execution?

Yes. Some languages run special methods automatically while rebuilding an object, and a gadget chain stitches together classes already on the app’s classpath so that simply deserializing a crafted object kicks off a sequence ending in command execution. The attacker uploads no new code, they just arrange classes the app already ships in an order the authors never intended.

How do you prevent insecure deserialization?

Do not deserialize untrusted input into native objects. Use plain data formats like JSON and validate fields by hand, keep user role and permissions on the server behind an opaque session id, and sign anything the client carries so any edit breaks the signature. If you truly need typed deserialization, allowlist which classes may be built and reject the rest.

How do you spot a Java serialized object in a request?

Java serialized data often starts with the bytes ac ed 00 05, and the base64 form of that begins with rO0. Seeing that prefix in a cookie or header is a strong hint that the app is feeding client controlled bytes to an object builder.