Insecure Deserialization: From Serialized Blob to Remote Code Execution

Identifying Insecure Deserialization

How to spot deserialization: format signatures for PHP, Java, .NET, and pickle, the carriers they ride in, and flipping a byte to confirm the server parses it.

Published

You can only exploit a deserializer you can find, and serialized blobs give themselves away by their format and by where they ride. This page covers recognizing the signatures, the carriers, and confirming the server parses your input. Practice on a free live target.

Step 1: recognize the format signature

Decode the suspicious value (usually base64 or URL-encoding) and match the result:

PHP        O:4:"User":2:{...}        text, length-prefixed; O:=object, a:=array, s:=string
Java       base64 rO0AB...           raw magic bytes AC ED 00 05
.NET       base64 AAEAAAD...         BinaryFormatter header; or a __VIEWSTATE field
Python     pickle opcodes            \x80 proto byte, \x93/c (global), R (reduce), . (stop)
Ruby       base64 BAh... (commonly)  Marshal version \x04\x08
Node       {"x":"_$$ND_FUNC$$_..."}  node-serialize function trick in JSON

The PHP format is human-readable and the easiest to start with. The binary formats you identify by their leading bytes.

Step 2: flip a byte to confirm parsing

Recognition is a guess until the server reacts. Tamper one byte of the blob and resend:

GET /account HTTP/1.1
Host: target.example
Cookie: session=Tzo0OiJVc2VyIjoyOntzOjQ6Im5hbWUiO- (one byte changed) -
HTTP/1.1 500 Internal Server Error
Content-Type: text/html
 
PHP Notice: unserialize(): Error at offset 12 of 53 bytes

A deserialization or parse error (or a new 500 the original did not cause) confirms the server is deserializing your input. That error message also leaks the format, here PHP unserialize().

Step 3: map the deserializer and escalate

Now pin down the exact stack, because the exploit depends on it:

PHP O: + unserialize() error     -> PHP object injection, look for __wakeup/__destruct gadgets
Java rO0AB                        -> ysoserial, pick a chain for the classpath
.NET AAEAAAD / __VIEWSTATE        -> ysoserial.net / viewgen, check the MAC and machine key
pickle opcodes                    -> pickle __reduce__ payload

Carriers worth checking

Serialized state hides in predictable places:

Cookies and session tokens        Hidden form fields (__VIEWSTATE)
Query and body parameters         Custom headers
API bodies                        Message-queue payloads, cache entries

Edge variants

double-encoded        base64 of base64, or URL-encoded base64; decode twice
gzip+base64           deflate/gzip then base64 is common for Java/.NET blobs
truncation test       cutting the blob short should also throw a parse error
tiny valid object     for Java/.NET, send a minimal valid object before the gadget

Fixing it

  • Do not accept serialized objects from clients. If a cookie or field round-trips object state, that is the finding.
  • Integrity-check before parsing so a tampered blob is rejected before deserialization runs.
  • Return generic errors so a parse failure does not advertise the deserializer and offset.

Full guidance is in preventing insecure deserialization. Next, forge your first object in PHP object injection, or try it on a free lab.

← Back to Insecure Deserialization: From Serialized Blob to Remote Code Execution