Lion Cipher Lab
Exploit and fix insecure deserialization (Python pickle)
- AppSec / DevSecOps
- Intermediate
- ~60 min
- Free · no account required
Why it matters
Insecure deserialization is OWASP Top 10 A08:2025 (Software or Data Integrity Failures) and CWE-502, and it is one of the highest-impact web bugs precisely because it so rarely LOOKS dangerous. Serialization is turning an in-memory object into bytes you can store or send; deserialization is turning those bytes back into an object. It feels like plumbing. The trap is that some deserializers can also invoke code while they rebuild the object — and Python's pickle is the clearest example: it is not a data format, it is a tiny stack-based virtual machine, and an object's __reduce__ method tells…
What you'll learn
- explaining insecure deserialization: taking bytes from an untrusted source and rebuilding them into in-memory objects with a deserializer that can also invoke code, so attacker-controlled input becomes attacker-controlled execution
- understanding why Python's pickle is unsafe for untrusted input — it is a stack-based virtual machine whose REDUCE opcode calls an arbitrary callable, driven by an object's __reduce__ method, so 'load this saved object' means 'run this code'
- building a tiny vulnerable Flask app on your own machine that trusts a base64/pickle 'remember me' cookie and calls pickle.loads() on it
- writing a malicious pickle with a __reduce__ that returns (os.system, ("whoami > PWNED",)) and using it to make the app run a command and create a marker file — arbitrary code execution you can see
- fixing it properly by refusing to deserialize untrusted data with pickle at all and switching to a safe, data-only format (JSON), which carries data and cannot carry code or callables
- knowing the fallback controls when you truly must serialize objects — HMAC-sign the payload so only your own server-produced blobs are trusted, and/or restrict classes with a custom Unpickler.find_class allowlist — and why an allowlist is fragile and JSON is the better default
- re-sending the identical malicious cookie against the fixed app and confirming it is rejected with no marker file created, while a legitimate JSON token still works
- recognizing that this is a language-wide bug class (Java ObjectInputStream + ysoserial gadget chains, PHP unserialize + POP chains, .NET BinaryFormatter, Ruby Marshal, YAML yaml.load) and where it hides — cookies, caches, message queues, API bodies, view state
Steps in this lab
- What insecure deserialization is — the vocabulary and the ground rules
- Set up: confirm Python, make a folder, install Flask
- Build the VULNERABLE app (a 'remember me' cookie it trusts)
- Prove the code execution (a cookie that runs a command)
- Why pickle is dangerous by design — and why this bug is everywhere
- Fix it: don't deserialize untrusted data with pickle — use JSON
- What you did — and the reflex to keep