How to Address Object Injection Vulnerabilities in PHP

I have been discussing the risks related to PHP Object Injection or insecure usage of unserialize() and how this insecure coding practice is unfortunately very prevalent in the WordPress plugin ecosystem. This post is for plugin (and really any PHP) developers for the purpose to share why you shouldn’t unseralize() data sent from untrusted sources, and how one easy code change can save you from writing vulnerable code.

Why not?

Many people have discussed the risks of PHP Object Injection (OWASP, FoxGlove), but for a TL;DR: Bad things happen when you unserialize() data received from a browser (e.g.. Cookies, POST/GET values, etc..)

What’s the fix?

The most common fix is to replace the usage of serialize and unserialize with json_encode and json_decode. This works perfectly for instances where an array or associative array was previously being used.

Example:

<?php
$array = ["one", "two", "three"];
# Our test array
print_r($array);
# Array
# (
# [0] => one
# [1] => two
# [2] => three
# )

Now, Let us create a serialize() copy of the same array and show how unserialize() handles the data:

<?php
$serialized_array = serialize($array);
# A serialized copy
echo $serialized_array;
# a:3:{i:0;s:3:"one";i:1;s:3:"two";i:2;s:5:"three";}
print_r(unserialize($serialized_array));
# print_r unserialize() of the variable gives the same output as above!
# Array
# (
# [0] => one
# [1] => two
# [2] => three
# )

serialize() works as expected above, but it is insecure. So, let us use json_encode() and json_decode() instead and verify they also give the same outputs.

<?php
$json_array = json_encode($array);
# A JSON copy
echo $json_array;
# ["one","two","three"]
print_r(json_decode($json_array));
# print_r json_decode() of the variable also gives the same output as unserialize!
# Array
# (
# [0] => one
# [1] => two
# [2] => three
# )

Does the same apply for tables or associative arrays? Yes!

<?php
$table = ["one" => "foo", "two" => "bar", "three" => "baz"];
# let's try an associative array
print_r($table);
# Array
# (
# [one] => foo
# [two] => bar
# [three] => baz
# )
$serialized_table = serialize($table);
# A serialized copy
echo $serialized_table;
# a:3:{s:3:"one";s:3:"foo";s:3:"two";s:3:"bar";s:5:"three";s:3:"baz";}
print_r(unserialize($serialized_table));
# print_r unserialize() of the variable gives the same output as above!
# Array
# (
# [one] => foo
# [two] => bar
# [three] => baz
# )
$json_table = json_encode($table);
# A JSON copy
echo $json_table;
# {"one":"foo","two":"bar","three":"baz"}
print_r(json_decode($json_table, TRUE));
# print_r json_decode() of the variable also gives unserialize!
# (note the TRUE argument to ensure the returned value is an Array)
# Array
# (
# [one] => foo
# [two] => bar
# [three] => baz
# )

I hope the above walk through helps clarify that just dropping in and replacing serialize/unserialize with json_encode/json_decode may be an extremely simply and effective fix.

If you have a custom object you’ve been storing in user controlled input, then this may require some extra work and possible refactoring but it’s not impossible, you have two options:

  1. Refactor your code so the data which represents that Object class can be exported to an associative array or table, which can safely be encoded and decoded over the wire using json to safely re-create the Object class later.
  2. Store your serialized objects in your database and pass a unique identifier to the browser. Later you can retrieve the serialized data from the database (using the unique ID provided by the browser) to re-create that object safely.

Not a direct fix as shown above, and implementation will differ for every situation but it will be worth it to prevent opening up your site to object injection vulnerabilities.

Note: PHP 7 and higher support an option for “allowed_classes” in unserialize(), this is a good way to prevent people from injecting unexpected objects, however may still leave the site vulnerable from attacks where attackers abuse access to the object’s data structure, as well as not prevent an attack which may target PHP itself and simply over-flow or cause memory corruption by injecting unexpected data into an Object structure.

I hope the above quick tutorial is helpful. We are still finding many PHP Object Injection vulnerabilities in WordPress plugins and have been working with the developers to address them. We hope that sooner than later the word gets out and more people take the time to clean up this insecure coding practice.

0 Comments