max_depth Option for unserialize()

The unserialize() function takes a string as its first argument that contains a single serialized variable and converts it back into a PHP value. This string is usually generated using the serialize() function. It is, of course, a bad idea, especially from a security point of view, to pass a value comes from the outside, from $_GET, for instance, to unserialize().

In the “Secure unserialize()” section we already discussed the allowed_classes option that limits the creation of objects through unserialize() to a list of classes. But automatic code execution through, for instance, the constructor of a class through an invocation of unserialize() with an unsafe argument is only one possible attack vector.

An attacker could prepare a string that leads to a stack overflow during the unserialization of deeply nested structures. To deal with this scenario, the max_depth option was introduced in PHP 7.4.

var_dump(
    unserialize(
        'a:1:{i:0;a:1:{i:0;a:1:{i:0;a:0:{}}}}'
    )
);

Executing the code shown above will print the output shown below:

array(1) {
  [0]=>
  array(1) {
    [0]=>
    array(1) {
      [0]=>
      array(0) {
      }
    }
  }
}

The array that is encoded in the string that is passed to unserialize() has a depth of 3. Here is an example of how to limit the depth of structures to be unserialized:

var_dump(
    unserialize(
        'a:1:{i:0;a:1:{i:0;a:1:{i:0;a:0:{}}}}',
        [
            'max_depth' => 2
        ]
    )
);

Executing the code shown above will print the output shown below:

Warning: unserialize(): Maximum depth of 2 exceeded. The depth limit can be
changed using the max_depth unserialize() option or the
unserialize_max_depth ini setting in ...
bool(false)

As you can see, PHP emits a warning that unserialize() was not successful due to the maximum depth that is allowed for nested structures. And instead of an array the function returned false.

The unserialize_max_depth configuration option can be set in your php.ini configuration file, for instance, to configure a maximum depth for unserializing nested structures for all unserialize() calls.

It should go without saying that you should not pass unsafe values that come from the outside to a function such as unserialize() to begin with.