AlexandrX
AlexandrX

Reputation: 838

How can I unserialize basic types and arrays secure?

I have to use unserialize with user-provided data in my project. IFAIK, using unserialize with untrusted data is unsecure due to possibility of execute user code via implicit call of wakeup-function and destructor ( https://wiki.php.net/rfc/secure_unserialize ).

OK, I've tried smth like this

$a = serialize(array('a'=>'b', 'c'=>20));
unserialize($a, array('allowed_classes'=>false));

but unserialize ignores ALL data this way...

So, the question is: how can I unserialize string contains the array(s) and basic datatypes (like ints, strings, bools and so on), but protect myself from unserialize the objects of undesired classes (and execute undesired code)?

Thanks in advance.

Upvotes: 1

Views: 2919

Answers (1)

MoeBrowne
MoeBrowne

Reputation: 161

You didn't mention which version of PHP you are using but the second argument of unserialize is only available in PHP 7.

From php.net:

7.0.0 The options parameter has been added.

PHP 5.5.9-1ubuntu4.20 (cli):

php > $a = serialize(array('a'=>'b', 'c'=>20));
php > var_dump(unserialize($a, array('allowed_classes'=>false)));
PHP Warning:  unserialize() expects exactly 1 parameter, 2 given in php shell code on line 1
PHP Stack trace:
PHP   1. {main}() php shell code:0
PHP   2. unserialize() php shell code:1
bool(false)

PHP 7.0.12 (cli):

php > $a = serialize(array('a'=>'b', 'c'=>20));
php > var_dump(unserialize($a, array('allowed_classes'=>false)));
array(2) {
  ["a"]=>
  string(1) "b"
  ["c"]=>
  int(20)
}

PHP 7.0.12 (cli) with a serialized object:

php > $a = serialize(array('a'=>'b', 'c'=>20, 'd'=> new Exception));
php > var_dump(unserialize($a, array('allowed_classes'=>false)));
array(3) {
  ["a"]=>
  string(1) "b"
  ["c"]=>
  int(20)
  ["d"]=>
  object(__PHP_Incomplete_Class)#1 (8) {
    ["__PHP_Incomplete_Class_Name"]=>
    string(9) "Exception"
    ["message":protected]=>
    string(0) ""
    ["string":"Exception":private]=>
    string(0) ""
    ["code":protected]=>
    int(0)
    ["file":protected]=>
    string(14) "php shell code"
    ["line":protected]=>
    int(1)
    ["trace":"Exception":private]=>
    array(0) {
    }
    ["previous":"Exception":private]=>
    NULL
  }
}

I can't speak for it's security or completeness but comment 119851 in the unserialize php docs gives this implmentation of unserialize with the options argument for > PHP 5.3:

function php7_unserialize($str, $options = array())
{
  if(version_compare(PHP_VERSION, '7.0.0', '>='))
  { return unserialize($str, $options); }

  $allowed_classes = isset($options['allowed_classes']) ?
    $options['allowed_classes'] : true;
  if(is_array($allowed_classes) || !$allowed_classes)
  {
    $str = preg_replace_callback(
      '/(?=^|:)(O|C):\d+:"([^"]*)":(\d+):{/',
      function($matches) use ($allowed_classes)
      {
        if(is_array($allowed_classes) &&
          in_array($matches[2], $allowed_classes))
        { return $matches[0]; }
        else
        {
          return $matches[1].':22:"__PHP_Incomplete_Class":'.
            ($matches[3] + 1).
            ':{s:27:"__PHP_Incomplete_Class_Name";'.
            serialize($matches[2]);
        }
      },
      $str
    );
  }
  unset($allowed_classes);
  return unserialize($str);
}

Upvotes: 2

Related Questions