如何安全地反序列化基本类型和数组?

时间:2016-11-03 22:02:40

标签: php security serialization

我必须在项目中使用用户提供的数据进行反序列化。 IFAIK使用unserialize与不受信任的数据是不安全的,因为可能通过隐式调用wakeup-function和析构函数(https://wiki.php.net/rfc/secure_unserialize)来执行用户代码。

好的,我试过这样的事情

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

但是反序列化会以这种方式忽略所有数据......

所以,问题是: 我如何反序列化字符串包含数组和基本数据类型(如整数,字符串,bool等),但保护自己不会反序列化不需要的类的对象(并执行不需要的代码)?

提前致谢。

1 个答案:

答案 0 :(得分:2)

您没有提到您使用的是哪个版本的PHP,但unserialize的第二个参数仅在PHP 7中可用。

来自php.net

  

7.0.0已添加选项参数。

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):

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
  }
}

我无法说明它的安全性或完整性,但unserialize php文档中的comment 119851使用>的options参数为unserialize赋予了这个实现。 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);
}