The Cookie That Was an Object: Spotting Java Deserialization
The challenge
Pennywharf's Java app stores your session in a cookie and trusts it on the way back in. The cookie is not a signed token and it is not JSON: it is the raw session object, encoded. That matters, because if the server rebuilds an object straight from bytes a client sent, the client gets to choose what gets built. Decode the cookie in the workbench and read the signature at the very start. Name the format this cookie is really in.
What you'll learn
- Decode a base64 blob and read its leading magic bytes
- Recognise ac ed 00 05 as the Java serialization stream header
- Explain why deserializing client-controlled bytes is dangerous
- List the front-of-blob tells for Java, pickle, .NET and PHP
- State the safe alternative: opaque session ids and data-only formats
Skills tested
Prerequisites
- What base64 is
- The idea that files and blobs start with a signature
How it works
A cookie that holds state can do it two safe ways: an opaque id that points at server-side data, or a signed, data-only token. It can also do it the dangerous way, by shipping the actual object, serialized. When the server reads that cookie back it reconstructs an object directly from bytes the client sent.
Each serialization format announces itself at the front. Java streams begin ac ed 00 05 (base64 rO0AB). Decoding this cookie shows exactly that, then a class descriptor. Rebuilding such an object runs code paths chosen by the attacker, and with the right classes present that becomes remote code execution. No key is involved, because nothing was encrypted.
Fingerprinting the blob is the first move: 80 04 / gASV is a Python pickle, AAEAAAD///// is .NET, O:4: is PHP.
Common mistakes
- Treating it as a JWT. There are no dots and no header/payload/signature; it does not decode to JSON.
- Stopping at the class name. The readable
com.loomhaus.Sessionis a hint, but the signatureac ed 00 05is the proof. - Hunting for a key. The cookie is encoded, not encrypted; From Base64 is the whole decode.
- Answering
base64. Base64 is the transport wrapper, not what the bytes are.
How to defend against it
Do not rebuild objects from input a client can set.
- Keep session state on the server behind an opaque, random, unguessable id. The cookie should carry the id, never the object.
- If state must round-trip through the client, use a data-only format (JSON) parsed into known fields, and sign it so it cannot be tampered with.
- If a legacy path must deserialize, use strict allowlists of permitted classes and keep dangerous gadget libraries off the classpath.