From 427f6bb58ee9aa23cfa65adcd01e79913cc12eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=BChne?= Date: Tue, 20 Mar 2018 07:54:46 +0100 Subject: [PATCH 1/4] PhpSerialize: Add option to whitelist classes for unserialize() --- src/Adapter/PhpSerialize.php | 46 ++++++++++++++++++++++++- src/Adapter/PhpSerializeOptions.php | 52 +++++++++++++++++++++++++++++ test/Adapter/PhpSerializeTest.php | 33 ++++++++++++++++++ 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/Adapter/PhpSerializeOptions.php diff --git a/src/Adapter/PhpSerialize.php b/src/Adapter/PhpSerialize.php index 14ecf04..8d2ee0b 100644 --- a/src/Adapter/PhpSerialize.php +++ b/src/Adapter/PhpSerialize.php @@ -21,8 +21,15 @@ class PhpSerialize extends AbstractAdapter */ private static $serializedFalse = null; + /** + * @var PhpSerializeOptions + */ + protected $options; + /** * Constructor + * + * @param array|\Traversable|PhpSerializeOptions|null $options */ public function __construct($options = null) { @@ -35,6 +42,36 @@ public function __construct($options = null) parent::__construct($options); } + /** + * Set options + * + * @param array|\Traversable|PhpSerializeOptions $options + * @return PhpSerialize + */ + public function setOptions($options) + { + if (! $options instanceof PhpSerializeOptions) { + $options = new PhpSerializeOptions($options); + } + + $this->options = $options; + return $this; + } + + /** + * Get options + * + * @return PhpSerializeOptions + */ + public function getOptions() + { + if ($this->options === null) { + $this->options = new PhpSerializeOptions(); + } + + return $this->options; + } + /** * Serialize using serialize() * @@ -85,7 +122,14 @@ public function unserialize($serialized) } ErrorHandler::start(E_NOTICE); - $ret = unserialize($serialized); + + if (PHP_MAJOR_VERSION >= 7) { + // the second parameter is only available on PHP 7.0 or higher + $ret = unserialize($serialized, ['allowed_classes' => $this->getOptions()->getUnserializationWhitelist()]); + } else { + $ret = unserialize($serialized); + } + $err = ErrorHandler::stop(); if ($ret === false) { throw new Exception\RuntimeException('Unserialization failed', 0, $err); diff --git a/src/Adapter/PhpSerializeOptions.php b/src/Adapter/PhpSerializeOptions.php new file mode 100644 index 0000000..fb9d15b --- /dev/null +++ b/src/Adapter/PhpSerializeOptions.php @@ -0,0 +1,52 @@ +unserializationWhitelist = $unserializationWhitelist; + return $this; + } + + /** + * @return array|bool + */ + public function getUnserializationWhitelist() + { + return $this->unserializationWhitelist; + } +} diff --git a/test/Adapter/PhpSerializeTest.php b/test/Adapter/PhpSerializeTest.php index e992a6e..f3ddbbb 100644 --- a/test/Adapter/PhpSerializeTest.php +++ b/test/Adapter/PhpSerializeTest.php @@ -165,4 +165,37 @@ public function testUnserializingInvalidStringRaisesException($string, $expected $this->expectExceptionMessage($expected); $this->adapter->unserialize($string); } + + public function testUnserializeNoWhitelistedClasses() + { + $value = 'O:8:"stdClass":0:{}'; + + $this->adapter->getOptions()->setUnserializationWhitelist(false); + + $data = $this->adapter->unserialize($value); + $this->assertNotInstanceOf(\stdClass::class, $data); + $this->assertInstanceOf('__PHP_Incomplete_Class', $data); + } + + public function testUnserializeClassNotAllowed() + { + $value = 'O:8:"stdClass":0:{}'; + + $this->adapter->getOptions()->setUnserializationWhitelist([\My\Dummy::class]); + + $data = $this->adapter->unserialize($value); + $this->assertNotInstanceOf(\stdClass::class, $data); + $this->assertInstanceOf('__PHP_Incomplete_Class', $data); + } + + public function testUnserializeClassAllowed() + { + $value = 'O:8:"stdClass":0:{}'; + + $this->adapter->getOptions()->setUnserializationWhitelist([\stdClass::class]); + + $data = $this->adapter->unserialize($value); + $this->assertInstanceOf(\stdClass::class, $data); + $this->assertNotInstanceOf('__PHP_Incomplete_Class', $data); + } } From 0dc335e77c8d2475346af0e05139d8e84f237643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=BChne?= Date: Tue, 20 Mar 2018 07:57:20 +0100 Subject: [PATCH 2/4] Add fallbacks for tests on PHP Version <7 --- test/Adapter/PhpSerializeTest.php | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/test/Adapter/PhpSerializeTest.php b/test/Adapter/PhpSerializeTest.php index f3ddbbb..e030365 100644 --- a/test/Adapter/PhpSerializeTest.php +++ b/test/Adapter/PhpSerializeTest.php @@ -173,8 +173,14 @@ public function testUnserializeNoWhitelistedClasses() $this->adapter->getOptions()->setUnserializationWhitelist(false); $data = $this->adapter->unserialize($value); - $this->assertNotInstanceOf(\stdClass::class, $data); - $this->assertInstanceOf('__PHP_Incomplete_Class', $data); + + if (PHP_MAJOR_VERSION >= 7) { + $this->assertNotInstanceOf(\stdClass::class, $data); + $this->assertInstanceOf('__PHP_Incomplete_Class', $data); + } else { + // Pre PHP 7.0 the whitelist should have no effect + $this->assertInstanceOf(\stdClass::class, $data); + } } public function testUnserializeClassNotAllowed() @@ -184,8 +190,14 @@ public function testUnserializeClassNotAllowed() $this->adapter->getOptions()->setUnserializationWhitelist([\My\Dummy::class]); $data = $this->adapter->unserialize($value); - $this->assertNotInstanceOf(\stdClass::class, $data); - $this->assertInstanceOf('__PHP_Incomplete_Class', $data); + + if (PHP_MAJOR_VERSION >= 7) { + $this->assertNotInstanceOf(\stdClass::class, $data); + $this->assertInstanceOf('__PHP_Incomplete_Class', $data); + } else { + // Pre PHP 7.0 the whitelist should have no effect + $this->assertInstanceOf(\stdClass::class, $data); + } } public function testUnserializeClassAllowed() From b32b4ee3691a5164eddda1ed56a149378641f5f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=BChne?= Date: Tue, 20 Mar 2018 08:07:05 +0100 Subject: [PATCH 3/4] Add docs --- docs/book/adapter.md | 7 ++++++- src/Adapter/PhpSerialize.php | 2 +- src/Adapter/PhpSerializeOptions.php | 14 +++++++------- test/Adapter/PhpSerializeTest.php | 6 +++--- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/docs/book/adapter.md b/docs/book/adapter.md index 82e7146..dbc5c47 100644 --- a/docs/book/adapter.md +++ b/docs/book/adapter.md @@ -16,7 +16,12 @@ The `Zend\Serializer\Adapter\PhpSerialize` adapter uses the built-in [serialize()](http://php.net/serialize)/[unserialize()](http://php.net/unserialize) functions, and is a good default adapter choice. -There are no configurable options for this adapter. +Available options include: + +Option | Data Type | Default Value | Description +--------------------------- | ----------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- +unserialize_class_whitelist | `array` or `bool` | `true` | The allowed classes for unserialize(), see [unserialize()](http://php.net/unserialize) for more information. Only available on PHP 7.0 or higher. + ## The IgBinary Adapter diff --git a/src/Adapter/PhpSerialize.php b/src/Adapter/PhpSerialize.php index 8d2ee0b..39a1764 100644 --- a/src/Adapter/PhpSerialize.php +++ b/src/Adapter/PhpSerialize.php @@ -125,7 +125,7 @@ public function unserialize($serialized) if (PHP_MAJOR_VERSION >= 7) { // the second parameter is only available on PHP 7.0 or higher - $ret = unserialize($serialized, ['allowed_classes' => $this->getOptions()->getUnserializationWhitelist()]); + $ret = unserialize($serialized, ['allowed_classes' => $this->getOptions()->getUnserializeClassWhitelist()]); } else { $ret = unserialize($serialized); } diff --git a/src/Adapter/PhpSerializeOptions.php b/src/Adapter/PhpSerializeOptions.php index fb9d15b..411b9e4 100644 --- a/src/Adapter/PhpSerializeOptions.php +++ b/src/Adapter/PhpSerializeOptions.php @@ -23,30 +23,30 @@ class PhpSerializeOptions extends AdapterOptions * * @var array|bool */ - protected $unserializationWhitelist = true; + protected $unserializeClassWhitelist = true; /** - * @param array|bool $unserializationWhitelist + * @param array|bool $unserializeClassWhitelist * * @return PhpSerializeOptions */ - public function setUnserializationWhitelist($unserializationWhitelist) + public function setUnserializeClassWhitelist($unserializeClassWhitelist) { - if (($unserializationWhitelist !== true) && (PHP_MAJOR_VERSION < 7)) { + if (($unserializeClassWhitelist !== true) && (PHP_MAJOR_VERSION < 7)) { throw new Exception\InvalidArgumentException( 'Class whitelist for unserialize() is only available on PHP 7.0 or higher.' ); } - $this->unserializationWhitelist = $unserializationWhitelist; + $this->unserializeClassWhitelist = $unserializeClassWhitelist; return $this; } /** * @return array|bool */ - public function getUnserializationWhitelist() + public function getUnserializeClassWhitelist() { - return $this->unserializationWhitelist; + return $this->unserializeClassWhitelist; } } diff --git a/test/Adapter/PhpSerializeTest.php b/test/Adapter/PhpSerializeTest.php index e030365..e2d56c0 100644 --- a/test/Adapter/PhpSerializeTest.php +++ b/test/Adapter/PhpSerializeTest.php @@ -170,7 +170,7 @@ public function testUnserializeNoWhitelistedClasses() { $value = 'O:8:"stdClass":0:{}'; - $this->adapter->getOptions()->setUnserializationWhitelist(false); + $this->adapter->getOptions()->setUnserializeClassWhitelist(false); $data = $this->adapter->unserialize($value); @@ -187,7 +187,7 @@ public function testUnserializeClassNotAllowed() { $value = 'O:8:"stdClass":0:{}'; - $this->adapter->getOptions()->setUnserializationWhitelist([\My\Dummy::class]); + $this->adapter->getOptions()->setUnserializeClassWhitelist([\My\Dummy::class]); $data = $this->adapter->unserialize($value); @@ -204,7 +204,7 @@ public function testUnserializeClassAllowed() { $value = 'O:8:"stdClass":0:{}'; - $this->adapter->getOptions()->setUnserializationWhitelist([\stdClass::class]); + $this->adapter->getOptions()->setUnserializeClassWhitelist([\stdClass::class]); $data = $this->adapter->unserialize($value); $this->assertInstanceOf(\stdClass::class, $data); From 4423d9ccd0708c35b7191c0994d0d4def4405f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=BChne?= Date: Tue, 20 Mar 2018 11:03:06 +0100 Subject: [PATCH 4/4] Fix Unit-Tests in PHP <7, fix class docs --- src/Adapter/PhpSerialize.php | 7 ++-- src/Adapter/PhpSerializeOptions.php | 8 ++--- test/Adapter/PhpSerializeTest.php | 50 ++++++++++++++++++++--------- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/Adapter/PhpSerialize.php b/src/Adapter/PhpSerialize.php index 39a1764..581e21e 100644 --- a/src/Adapter/PhpSerialize.php +++ b/src/Adapter/PhpSerialize.php @@ -3,12 +3,13 @@ * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository - * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2018 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ namespace Zend\Serializer\Adapter; +use Traversable; use Zend\Serializer\Exception; use Zend\Stdlib\ErrorHandler; @@ -29,7 +30,7 @@ class PhpSerialize extends AbstractAdapter /** * Constructor * - * @param array|\Traversable|PhpSerializeOptions|null $options + * @param array|Traversable|PhpSerializeOptions|null $options */ public function __construct($options = null) { @@ -45,7 +46,7 @@ public function __construct($options = null) /** * Set options * - * @param array|\Traversable|PhpSerializeOptions $options + * @param array|Traversable|PhpSerializeOptions $options * @return PhpSerialize */ public function setOptions($options) diff --git a/src/Adapter/PhpSerializeOptions.php b/src/Adapter/PhpSerializeOptions.php index 411b9e4..1d0cce8 100644 --- a/src/Adapter/PhpSerializeOptions.php +++ b/src/Adapter/PhpSerializeOptions.php @@ -3,7 +3,7 @@ * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository - * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2018 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -21,12 +21,12 @@ class PhpSerializeOptions extends AdapterOptions * or true if all classes should be allowed (behavior of pre PHP 7.0) * or false if no classes should be allowed * - * @var array|bool + * @var string[]|bool */ protected $unserializeClassWhitelist = true; /** - * @param array|bool $unserializeClassWhitelist + * @param string[]|bool $unserializeClassWhitelist * * @return PhpSerializeOptions */ @@ -43,7 +43,7 @@ public function setUnserializeClassWhitelist($unserializeClassWhitelist) } /** - * @return array|bool + * @return string[]|bool */ public function getUnserializeClassWhitelist() { diff --git a/test/Adapter/PhpSerializeTest.php b/test/Adapter/PhpSerializeTest.php index e2d56c0..d9ac4d0 100644 --- a/test/Adapter/PhpSerializeTest.php +++ b/test/Adapter/PhpSerializeTest.php @@ -11,10 +11,11 @@ use PHPUnit\Framework\TestCase; use Zend\Serializer; +use Zend\Serializer\Exception\InvalidArgumentException; /** - * @group Zend_Serializer - * @covers Zend\Serializer\Adapter\PhpSerialize + * @group Zend_Serializer + * @covers \Zend\Serializer\Adapter\PhpSerialize */ class PhpSerializeTest extends TestCase { @@ -170,16 +171,20 @@ public function testUnserializeNoWhitelistedClasses() { $value = 'O:8:"stdClass":0:{}'; - $this->adapter->getOptions()->setUnserializeClassWhitelist(false); + if (PHP_MAJOR_VERSION >= 7) { + $this->adapter->getOptions()->setUnserializeClassWhitelist(false); - $data = $this->adapter->unserialize($value); + $data = $this->adapter->unserialize($value); - if (PHP_MAJOR_VERSION >= 7) { $this->assertNotInstanceOf(\stdClass::class, $data); $this->assertInstanceOf('__PHP_Incomplete_Class', $data); } else { - // Pre PHP 7.0 the whitelist should have no effect - $this->assertInstanceOf(\stdClass::class, $data); + // In PHP < 7.0 the options-class will throw an exception + + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('Class whitelist for unserialize() is only available on PHP 7.0 or higher.'); + + $this->adapter->getOptions()->setUnserializeClassWhitelist(false); } } @@ -187,16 +192,20 @@ public function testUnserializeClassNotAllowed() { $value = 'O:8:"stdClass":0:{}'; - $this->adapter->getOptions()->setUnserializeClassWhitelist([\My\Dummy::class]); + if (PHP_MAJOR_VERSION >= 7) { + $this->adapter->getOptions()->setUnserializeClassWhitelist([\My\Dummy::class]); - $data = $this->adapter->unserialize($value); + $data = $this->adapter->unserialize($value); - if (PHP_MAJOR_VERSION >= 7) { $this->assertNotInstanceOf(\stdClass::class, $data); $this->assertInstanceOf('__PHP_Incomplete_Class', $data); } else { - // Pre PHP 7.0 the whitelist should have no effect - $this->assertInstanceOf(\stdClass::class, $data); + // In PHP < 7.0 the options-class will throw an exception + + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('Class whitelist for unserialize() is only available on PHP 7.0 or higher.'); + + $this->adapter->getOptions()->setUnserializeClassWhitelist(false); } } @@ -204,10 +213,19 @@ public function testUnserializeClassAllowed() { $value = 'O:8:"stdClass":0:{}'; - $this->adapter->getOptions()->setUnserializeClassWhitelist([\stdClass::class]); + if (PHP_MAJOR_VERSION >= 7) { + $this->adapter->getOptions()->setUnserializeClassWhitelist([\stdClass::class]); - $data = $this->adapter->unserialize($value); - $this->assertInstanceOf(\stdClass::class, $data); - $this->assertNotInstanceOf('__PHP_Incomplete_Class', $data); + $data = $this->adapter->unserialize($value); + $this->assertInstanceOf(\stdClass::class, $data); + $this->assertNotInstanceOf('__PHP_Incomplete_Class', $data); + } else { + // In PHP < 7.0 the options-class will throw an exception + + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('Class whitelist for unserialize() is only available on PHP 7.0 or higher.'); + + $this->adapter->getOptions()->setUnserializeClassWhitelist(false); + } } }