KNOWLEDGE BASEWEB APPLICATIONS

PHP Deserialization and Object Injection

A practical guide to working out what attacker-controlled PHP deserialization actually gives you, which classes are reachable, and how that can grow into real impact.

unserialize() is interesting, but it is not automatically RCE

When I see attacker-controlled data reaching unserialize(), that immediately gets my attention.

The second reaction should be: what can I actually do with it?

I break the problem into a few steps:

serialized data I can control

      PHP deserialization

 classes and magic methods I can reach

 useful side effect

       real security impact

The interesting part is not the word unserialize() itself. It is the code that becomes reachable after PHP creates the object.

First find the trust boundary

I want to know exactly where the serialized data comes from.

Common places are:

  • backup or restore archives;
  • session or cache data;
  • database records;
  • HTTP parameters or cookies;
  • import files;
  • framework state objects.

Then I check whether the application really lets me control that data. Encryption, signatures, integrity checks or access-control checks can completely change the story.

Look at what is loaded

PHP object injection becomes much more interesting when the application has a large class space.

Composer packages, framework classes and vendor libraries may already contain everything needed for a useful gadget chain.

My usual checklist is:

  • application and framework version;
  • Composer dependencies and versions;
  • autoload behavior;
  • __wakeup(), __destruct(), __toString(), __invoke() and friends;
  • classes that write files, delete files, make network requests or execute processes.

A class does not need to look security-sensitive by itself. I care about what happens when attacker-controlled properties reach its methods.

Follow the side effects

I search for useful behavior such as:

file write
file delete
path manipulation
process execution
network request
dynamic callback
template evaluation
expression evaluation

Then I work backwards and ask whether object properties give me enough control to reach that behavior.

That is basically what a gadget chain is: a route from the object I can create to the side effect I actually want.

Preconditions can make or break the exploit

A clean-looking chain on paper can still fail because the runtime is missing one small ingredient.

Things I check include:

  • optional PHP extensions;
  • writable directories;
  • dependency versions;
  • whether the class is autoloaded;
  • whether authentication is required;
  • whether a second request is needed;
  • whether a generated file must be triggered separately.

I write these down while testing. It saves a lot of confusion later when the exploit works on one target and not on another.

Keep the explanation simple

I prefer the vulnerability story to make sense before showing the final payload.

A format that works well is:

Root cause
  attacker-controlled data is deserialized

What that gives me
  object and property control

Useful class behavior
  attacker-controlled side effect

Impact
  file write / command execution / other compromise

That structure is much easier to reuse when the gadget chain changes between versions.

When I build the exploit

Once I have the chain working, I still test the boring bits:

  • Can I detect the vulnerable version?
  • Can I detect a missing dependency?
  • Can I clean up generated files?
  • Can I fail with a useful error instead of a dead session?
  • Can I make the chain work without hard-coded paths from my lab?

Those details are what turn a working proof of concept into something I trust enough to use again.