How a deserialization bug becomes remote code execution, in about 20 lines
In July 2026, CISA told every US federal agency to patch Adobe ColdFusion immediately. Attackers were already using CVE-2026-48282 to run their own code on other people's servers, unauthenticated, from the outside. The name for that is remote code execution, and it is the worst thing that can happen to a web server.
The surprising part is how small the underlying bug is. It is not a clever memory-corruption exploit. It is a program that takes bytes from a stranger and turns them back into objects, and trusts that the bytes were honest. That pattern is called insecure deserialization, and it shows up in Java, PHP, Ruby, .NET, and Python. Let us build a working one in Python so the shape is unmistakable.
The one idea
Serialization turns an object into bytes you can store or send. Deserialization turns the bytes back into an object. The trap is that in most languages, rebuilding an object is not passive. It can call a constructor, run a hook, invoke a function. If the attacker controls the bytes, the attacker controls what gets called.
Python's pickle is the clean way to see it, because the mechanism is documented and named.
An honest token, round-tripping fine
Here is a server that stores a session as a pickled object, the way you might stash it in a cookie or a cache.
import pickle, os
class Session:
def __init__(self, user): self.user = user
def load_session(blob):
return pickle.loads(blob) # the vulnerable line
good = pickle.dumps(Session("alice"))
print("honest token loads:", load_session(good).user)
Run it and you get honest token loads: alice. Nothing looks wrong. The server pickles a Session on the way out and unpickles it on the way in. This is the code that ships.
The attacker writes their own object
Pickle rebuilds an object by asking it how. An object can define __reduce__, which returns a callable and the arguments to call it with. On load, pickle calls that callable. The honest use is to reconstruct a complex object. The dishonest use is to return any function you like.
class Exploit:
def __reduce__(self):
return (os.system, ("echo PWNED-by-deserialization > /tmp/_pwn.txt",))
evil = pickle.dumps(Exploit())
load_session(evil) # server unpickles attacker bytes, code runs
print("attacker file:", open("/tmp/_pwn.txt").read().strip())
Three details that matter:
- The attacker never touched the server's code. They only had to hand it bytes, and
load_sessiondid the rest. os.systemis just an example. The same trick reachessubprocess, opens sockets, writes files, or pulls down a second stage. Whatever the process can do, the attacker can do.- Nothing here is a bug in pickle. Pickle is doing exactly what it promises. The bug is calling it on input you did not create.
Run the two pieces together and the server prints:
honest token loads: alice
attacker file: PWNED-by-deserialization
That file was written by bytes that arrived from outside. Swap the harmless echo for anything and you have the ColdFusion headline, minus the Java-specific gadget chain that real exploits assemble to reach a dangerous method. The principle is identical: untrusted bytes, deserialized, run code.
The fix is a format, not a filter
The instinct is to sanitize the input. You cannot, because the malicious payload is a valid pickle. There is no bad character to strip. The fix is to stop deserializing untrusted data into live objects at all, and to move to a format that can only produce data.
import json
def load_session_safe(text):
d = json.loads(text) # json makes dicts, lists, strings, never code
return Session(d["user"])
print("safe loader:", load_session_safe('{"user": "alice"}').user)
JSON has no __reduce__. It cannot describe a function call, only values. If the object you rebuild carries authority, sign the bytes with an HMAC so you can prove you wrote them before you trust them. The rule is simple: never turn a stranger's bytes back into an object that can act.
Where this shows up
The exact same story is the Java readObject gadget chains behind years of enterprise CVEs, the PHP unserialize bugs behind countless WordPress plugin takeovers, and the ColdFusion advisory that lit up July 2026. Different language, same sentence: input became an object, and building the object ran code.
If you want to see the rest of the attack surface by building it, the cybersecurity track on IWTLP has you write the vulnerable server and the exploit, then close the hole, so the bug class stops being abstract.
Sources
- CISA advisory and reporting on active exploitation of Adobe ColdFusion (CVE-2026-48282), July 2026: eSecurity Planet weekly roundup
- Python
pickledocumentation, which states plainly that it is not secure against maliciously constructed data: docs.python.org/3/library/pickle.html