Property oriented programming is the technique behind almost every deserialization exploit you will ever read about. The name sounds academic, but the idea is simple. Instead of injecting new code, the attacker builds a payload out of object properties and the methods those properties trigger, reusing code that already ships with the app. This post explains what a POP chain is, where the idea came from, and walks a small invented chain so you can see the moving parts.
What property oriented programming means
In a normal program, you control behavior by writing and calling functions. In property oriented programming, the attacker controls behavior by setting the properties of objects and letting the language do the calling. When a serialized object is rebuilt, the runtime assigns its fields and, in many languages, runs certain methods automatically. If the attacker chooses the fields carefully, those automatic methods call into other objects whose fields are also attacker chosen, and the control flows from property to property until it reaches a method that does real damage.
So the unit of the attack is not an instruction. It is a property. You are programming with data, and the existing methods become your instruction set.
In a POP chain the payload is not code. It is a set of object properties, and the program is written in whatever methods those properties happen to trigger.
Where the idea came from
The term was coined in the PHP world, where object injection made it easy to demonstrate. PHP objects run cleanup and wakeup methods automatically when they are rebuilt, so a serialized string that describes an object would fire those methods the moment it was deserialized. Researchers noticed you could chain these automatic methods across several classes to reach something dangerous. The same pattern then showed up in Java, .NET, Python, and Ruby. The details differ, but the shape is identical, which is why it is worth learning once. If the underlying flaw is new to you, start with our primer on insecure deserialization, then see the full cross language picture in our hub on what a deserialization gadget chain is.
The pieces of a POP chain
Three parts make a chain work.
- An entry method. A method the runtime calls by itself during deserialization. These are the footholds, covered in magic methods in deserialization attacks.
- Link gadgets. Classes whose methods call a method on one of their fields. Because the attacker sets the field, they choose which object is called next.
- A sink. The final method that does something harmful, such as running a command, writing a file, or loading a class by name.
The attacker sets the entry object’s field to a link gadget, sets that gadget’s field to the next, and points the last field at the sink. Rebuilding the top object pulls the whole structure into existence and the methods fire in order.
A small invented chain, step by step
Picture three classes in a fictional app called Acme Notes. None of them was written to be dangerous.
class Cleaner:
# entry method, runs automatically on rebuild
def on_wake(self):
self.target.render() # calls render() on a field we control
class Label:
def render(self):
return self.formatter.format(self.text) # calls format() on a field
class Runner:
def format(self, value):
os.system(value) # the sink: runs a command string
Now the attacker builds the object graph, not by writing code but by setting fields:
c = Cleaner()
c.target = Label()
c.target.formatter = Runner()
c.target.text = "id > /tmp/proof"
When the serialized form of c is deserialized, the runtime runs on_wake. That calls render on the Label. render calls format on the Runner, passing the attacker’s text. format runs the command. Three ordinary methods, zero injected code, one command executed. That is a POP chain.
Notice what the attacker actually shipped: a description of objects and their properties. The language supplied the control flow for free. This is the same trick whether the classes come from your code or from a library like the ones behind the Commons Collections gadget chain in Java.
Why this is hard to catch by reading code
No single class here is a bug. Runner.format calling os.system might be completely reasonable in its intended context. The vulnerability only appears when an attacker can connect Cleaner to Label to Runner through deserialization. A reviewer staring at one file sees nothing wrong, and a text search for a dangerous function finds the sink but cannot tell whether any reachable entry method leads to it. The real question is whether untrusted bytes can build a graph that reaches the sink, which is a reachability problem across many files and libraries. See our injection and input category for more bugs that hide in how data flows rather than in one line.
How to shut POP chains down
- Stop untrusted input from being deserialized into arbitrary object types. An allowlist of expected classes breaks the entry step, because the attacker’s chosen gadget is refused.
- Prefer plain data formats with a fixed schema, so rebuilding input produces values, never live objects with automatic methods.
- Reduce the classes on the classpath. Every library you do not need is a set of link gadgets you are carrying for the attacker.
- Sign serialized data you control and verify it before rebuilding, so tampered graphs are rejected.
POP chains are a clear example of a bug that lives in how an app fits together, not in any one function. Finding one means tracing untrusted input through many objects to a dangerous method, which is exactly the kind of source to sink reasoning an autonomous researcher is built to do. You can read more on our about page.
Frequently asked questions
What is property oriented programming?
It is a technique where an attacker controls behavior by setting the properties of objects rather than by injecting code. When a serialized object is rebuilt, the runtime runs methods automatically, and carefully chosen properties make those methods call into other objects until a dangerous one runs.
What is a POP chain?
A POP chain is the concrete payload built with property oriented programming. It links an entry method that fires on deserialization, one or more gadget classes that pass control along their fields, and a sink method that does something harmful like running a command.
Where did property oriented programming originate?
The term was coined in the PHP world, where object injection made automatic cleanup and wakeup methods easy to chain. The same pattern later appeared in Java, .NET, Python, and Ruby, so the idea is worth learning once and applying everywhere.
How do you defend against POP chains?
Restrict deserialization of untrusted input to an allowlist of expected classes, prefer plain data formats with a fixed schema, trim unused libraries so fewer gadget classes exist, and sign and verify any serialized data you control before rebuilding it.
Put an autonomous researcher on your own systems
UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.
