-
-
Notifications
You must be signed in to change notification settings - Fork 436
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Disable class unserialization where it is not needed. #1251
Conversation
looks good from the general approach. |
@colinmollenhour do you know of anywhere that we need classes to be unserialized? Shouldn't these use Then we can override the PhpSerialize adapter /**
* Unserialize
*
* @todo Allow integration with unserialize_callback_func
* @param string $serialized
* @param array $opts
* @return mixed
* @throws Zend_Serializer_Exception on unserialize error
*/
public function unserialize($serialized, array $opts = array())
{
// TODO: @see php.ini directive "unserialize_callback_func"
$ret = @unserialize($serialized);
if ($ret === false && $serialized !== self::$_serializedFalse) {
$lastErr = error_get_last();
#require_once 'Zend/Serializer/Exception.php';
throw new Zend_Serializer_Exception($lastErr['message']);
}
return $ret;
} So |
Not offhand, that's why this needs much more review.
I think we should get away from using these Zend classes unless there are really good reasons. I think it is likely entirely obsolete now that PHP 7 added 'allow_classes' functionality so if anything I'd actually go back and remove uses of both Zend_Serializer and also the Mage 'unserializeArray' helper in favor of using this second option and not using a wrapper.
Good suggestions! |
I tend to agree. In this case PHP doesn't throw a normal exception so we would need to catch Throwable to get rid of the Zend_Serializer class. |
What about replacing all the found usages of raw /**
* @param string $str
* @return mixed
* @throws Exception
*/
public function unserializeArray($str)
{
try {
$result = unserialize($str, ['allowed_classes' => false]);
if ($result === false && $str !== serialize(false)) {
throw new Exception('Error unserializing data.');
}
return $result;
} catch (Error $e) {
throw new Exception('Error unserializing data: '.$e->getMessage(), 0, $e);
}
} This way all parse errors are converted to Exception and it also correctly handles unserializing "false" without counting it as an error. |
Changing the Mage_Core_Helper_UnserializeArray class (core/unserializeArray) or adding that new method? I don't know what the purpose of that class is/was. So it doesn't unserialize objects? This should just replace that whole thing. The name bugs me. "unserializeArray" implies it's unserializing an array. It can be anything. I'd make another parameter Is there a better exception to use than \Exception? Mage_Core_Exception even? An UnserializeException would be best. That could extend \RuntimeException |
All good ideas, @joshua-bn, please let me know if you'd like to tackle the issue, I don't think I will be able to any time really soon.. |
@colinmollenhour not sure if/when I will be able to work on it. If I get time, I'll give it a shot. I don't have a copy of OpenMage running. I just copy the changes from here to my highly modified implementation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might not be a perfect implementation, but it does a lot for security now and that's better than perfection later.
I don't know of a single used instance where magento serialized a class and expected it to be unserialized. It was always arrays of data in my experience. Maybe @pocallaghan has thoughts? To me this seems a change that should have been made years ago. |
In my opinion this is ready to merge. I tested with a fresh installation by creating a product and category, adding to cart, completing checkout, creating an account, adding to wishlist, updating wishlist comment, etc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. It looks good.
Possible issues with unserialize (second parameter) in php 7.1, 7.2 & 7.3:
|
as of https://www.php.net/manual/en/function.unserialize.php the second parameter should be supported since 7.0 |
@@ -197,7 +197,7 @@ protected function _unserializeField(Varien_Object $object, $field, $defaultValu | |||
if (empty($value)) { | |||
$object->setData($field, $defaultValue); | |||
} elseif (!is_array($value) && !is_object($value)) { | |||
$object->setData($field, unserialize($value)); | |||
$object->setData($field, unserialize($value, ['allowed_classes' => ['Varien_Object']])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yesterday I merged openmage into our codebase and this change broke several legitimate objects saved in the database resulting in an error The script tried to execute a method or access a property of an incomplete object.
this needs some changes for backward compatibility instead of forcing it to ['allowed_classes' => ['Varien_Object']]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@arthurpro can you please create an issue about it? Please include details about your use cases, how this can be reproduced. Thanks
The
unserialize()
method by default will unserialize a payload with any serialized classes making it vulnerable to poisonous payloads. This PR makes use of the second parameter added in PHP 7.0 which allows you to specify specific classes that may be allowed or to disallow all classes (effectively only allowing scalar values and arrays). It may or may not be patching any specific known vulnerabilities but it is definitely trying to be proactive in preventing future vulnerabilities by going ahead and disallowing classes anywhere there is not a known use case for them.Please review thoroughly as I am not an expert on every area of Magento and it is possible that Magento serializes classes legitimately in cases I am not aware of, but I did a quick review of every line I changed and it seems Magento already did a good job of generally serializing only arrays (e.g. no usages of
__sleep
and__wakeup
in Mage namespace, mostly arrays passed toVarien_Object
constructors)Description (*)
Adds
['allowed_classes' => false]
as second parameter ofunserialize()
to prevent possible vulnerabilities exploits that rely on the default behavior of unserialize to instantiate any class.Manual testing scenarios (*)
All areas where code was modified should ideally be tested..
Questions or comments
This is completely untested so far...
It would be great if we could somehow enforce the use of this second parameter (prevent use of unserialize with only the first parameter) in the future.. I don't know a great way to do this, though.
Contribution checklist (*)