Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--core/Common.php27
-rw-r--r--core/Concurrency/DistributedList.php3
-rw-r--r--core/CronArchive.php4
-rw-r--r--core/DataAccess/ArchiveSelector.php2
-rw-r--r--core/DataTable.php6
-rw-r--r--core/DataTable/Renderer/Php.php3
-rw-r--r--core/Scheduler/Timetable.php3
-rw-r--r--core/Tracker/Visit/ReferrerSpamFilter.php2
-rw-r--r--core/Updates/3.0.0-b1.php2
-rw-r--r--plugins/Annotations/AnnotationList.php3
-rw-r--r--plugins/Referrers/SearchEngine.php2
-rw-r--r--plugins/Referrers/Social.php3
-rw-r--r--plugins/UserCountryMap/Controller.php4
-rw-r--r--tests/PHPUnit/Unit/CommonTest.php39
15 files changed, 91 insertions, 16 deletions
diff --git a/.travis.yml b/.travis.yml
index a9618ccc3b..2dd4972567 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -63,10 +63,10 @@ matrix:
sudo: false
addons: false
# All tests after another
- - php: 5.6
+ - php: 7
env: TEST_SUITE=AllTests MYSQL_ADAPTER=MYSQLI ALLTEST_EXTRA_OPTIONS="--run-first-half-only"
sudo: required
- - php: 5.6
+ - php: 7
env: TEST_SUITE=AllTests MYSQL_ADAPTER=MYSQLI ALLTEST_EXTRA_OPTIONS="--run-second-half-only"
sudo: required
# UITests use a specific version because the default 5.5 (== 5.5.38) is missing FreeType support
diff --git a/core/Common.php b/core/Common.php
index c617f6ce00..4c20559e4a 100644
--- a/core/Common.php
+++ b/core/Common.php
@@ -256,6 +256,33 @@ class Common
return $string;
}
+ /**
+ * Secure wrapper for unserialize, which by default disallows unserializing classes
+ *
+ * @param string $string String to unserialize
+ * @param array $allowedClasses Class names that should be allowed to unserialize
+ *
+ * @return mixed
+ */
+ public static function safe_unserialize($string, $allowedClasses = [])
+ {
+ if (PHP_MAJOR_VERSION >= 7) {
+ try {
+ return unserialize($string, ['allowed_classes' => empty($allowedClasses) ? false : $allowedClasses]);
+ } catch (\Throwable $e) {
+ $logger = StaticContainer::get('Psr\Log\LoggerInterface');
+ $logger->debug('Unable to unserialize a string: {message} (string = {string})', [
+ 'message' => $e->getMessage(),
+ 'backtrace' => $e->getTraceAsString(),
+ 'string' => $string,
+ ]);
+ return false;
+ }
+ }
+
+ return @unserialize($string);
+ }
+
/*
* Escaping input
*/
diff --git a/core/Concurrency/DistributedList.php b/core/Concurrency/DistributedList.php
index 572b25d6ff..a01f27c1d8 100644
--- a/core/Concurrency/DistributedList.php
+++ b/core/Concurrency/DistributedList.php
@@ -7,6 +7,7 @@
*/
namespace Piwik\Concurrency;
+use Piwik\Common;
use Piwik\Container\StaticContainer;
use Piwik\Option;
use Psr\Log\LoggerInterface;
@@ -160,7 +161,7 @@ class DistributedList
$result = array();
if ($array
- && ($array = unserialize($array))
+ && ($array = Common::safe_unserialize($array))
&& count($array)
) {
$result = $array;
diff --git a/core/CronArchive.php b/core/CronArchive.php
index 195ce93a7e..49df8bab61 100644
--- a/core/CronArchive.php
+++ b/core/CronArchive.php
@@ -864,7 +864,7 @@ class CronArchive
}
$content = $this->request($url);
- $daysResponse = @unserialize($content);
+ $daysResponse = Common::safe_unserialize($content);
if (empty($content)
|| !is_array($daysResponse)
@@ -1015,7 +1015,7 @@ class CronArchive
$success = $success && $this->checkResponse($content, $url);
if ($noSegmentUrl == $url && $success) {
- $stats = @unserialize($content);
+ $stats = Common::safe_unserialize($content);
if (!is_array($stats)) {
$this->logError("Error unserializing the following response from $url: " . $content);
diff --git a/core/DataAccess/ArchiveSelector.php b/core/DataAccess/ArchiveSelector.php
index a944f23057..54024b88ff 100644
--- a/core/DataAccess/ArchiveSelector.php
+++ b/core/DataAccess/ArchiveSelector.php
@@ -316,7 +316,7 @@ class ArchiveSelector
private static function moveChunkRowToRows(&$rows, $row, Chunk $chunk, $loadAllSubtables, $idSubtable)
{
// $blobs = array([subtableID] = [blob of subtableId])
- $blobs = unserialize($row['value']);
+ $blobs = Common::safe_unserialize($row['value']);
if (!is_array($blobs)) {
return;
diff --git a/core/DataTable.php b/core/DataTable.php
index 22b09b97ee..b14281938c 100644
--- a/core/DataTable.php
+++ b/core/DataTable.php
@@ -1336,7 +1336,11 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess
private function unserializeRows($serialized)
{
$serialized = str_replace(self::$previousRowClasses, self::$rowClassToUseForUnserialize, $serialized);
- $rows = unserialize($serialized);
+ $rows = Common::safe_unserialize($serialized, [
+ Row::class,
+ DataTableSummaryRow::class,
+ \Piwik_DataTable_SerializedRow::class
+ ]);
if ($rows === false) {
throw new Exception("The unserialization has failed!");
diff --git a/core/DataTable/Renderer/Php.php b/core/DataTable/Renderer/Php.php
index 9a121bc628..540bfb9a02 100644
--- a/core/DataTable/Renderer/Php.php
+++ b/core/DataTable/Renderer/Php.php
@@ -9,6 +9,7 @@
namespace Piwik\DataTable\Renderer;
use Exception;
+use Piwik\Common;
use Piwik\DataTable\Renderer;
use Piwik\DataTable\Simple;
use Piwik\DataTable;
@@ -77,7 +78,7 @@ class Php extends Renderer
if ($this->prettyDisplay) {
if (!is_array($toReturn)) {
- $toReturn = unserialize($toReturn);
+ $toReturn = Common::safe_unserialize($toReturn);
}
$toReturn = "<pre>" . var_export($toReturn, true) . "</pre>";
}
diff --git a/core/Scheduler/Timetable.php b/core/Scheduler/Timetable.php
index 93980b7ac0..2970dc83ac 100644
--- a/core/Scheduler/Timetable.php
+++ b/core/Scheduler/Timetable.php
@@ -9,6 +9,7 @@
namespace Piwik\Scheduler;
+use Piwik\Common;
use Piwik\Option;
use Piwik\Date;
@@ -24,7 +25,7 @@ class Timetable
public function __construct()
{
$optionData = Option::get(self::TIMETABLE_OPTION_STRING);
- $unserializedTimetable = @unserialize($optionData);
+ $unserializedTimetable = Common::safe_unserialize($optionData);
$this->timetable = $unserializedTimetable === false ? array() : $unserializedTimetable;
}
diff --git a/core/Tracker/Visit/ReferrerSpamFilter.php b/core/Tracker/Visit/ReferrerSpamFilter.php
index 3bca5bb667..21d5216068 100644
--- a/core/Tracker/Visit/ReferrerSpamFilter.php
+++ b/core/Tracker/Visit/ReferrerSpamFilter.php
@@ -75,7 +75,7 @@ class ReferrerSpamFilter
$list = Option::get(self::OPTION_STORAGE_NAME);
if ($list) {
- $this->spammerList = unserialize($list);
+ $this->spammerList = Common::safe_unserialize($list);
} else {
// Fallback to reading the bundled list
$file = PIWIK_VENDOR_PATH . '/matomo/referrer-spam-blacklist/spammers.txt';
diff --git a/core/Updates/3.0.0-b1.php b/core/Updates/3.0.0-b1.php
index a1561dfc16..0e3b9f2ac1 100644
--- a/core/Updates/3.0.0-b1.php
+++ b/core/Updates/3.0.0-b1.php
@@ -134,7 +134,7 @@ class Updates_3_0_0_b1 extends Updates
foreach ($options as $option) {
$name = $option['option_name'];
$pluginName = str_replace(array('Plugin_', '_Settings'), '', $name);
- $values = @unserialize($option['option_value']);
+ $values = Common::safe_unserialize($option['option_value']);
if (empty($values)) {
continue;
diff --git a/plugins/Annotations/AnnotationList.php b/plugins/Annotations/AnnotationList.php
index 823c7bf125..845cbfe9ae 100644
--- a/plugins/Annotations/AnnotationList.php
+++ b/plugins/Annotations/AnnotationList.php
@@ -9,6 +9,7 @@
namespace Piwik\Plugins\Annotations;
use Exception;
+use Piwik\Common;
use Piwik\Date;
use Piwik\Option;
use Piwik\Piwik;
@@ -330,7 +331,7 @@ class AnnotationList
$serialized = Option::get($optionName);
if ($serialized !== false) {
- $result[$id] = @unserialize($serialized);
+ $result[$id] = Common::safe_unserialize($serialized);
if (empty($result[$id])) {
// in case unserialize failed
$result[$id] = array();
diff --git a/plugins/Referrers/SearchEngine.php b/plugins/Referrers/SearchEngine.php
index 8f7d174281..28498c73c6 100644
--- a/plugins/Referrers/SearchEngine.php
+++ b/plugins/Referrers/SearchEngine.php
@@ -54,7 +54,7 @@ class SearchEngine extends Singleton
$list = Option::get(self::OPTION_STORAGE_NAME);
if ($list) {
- $this->definitionList = unserialize(base64_decode($list));
+ $this->definitionList = Common::safe_unserialize(base64_decode($list));
} else {
// Fallback to reading the bundled list
$yml = file_get_contents(PIWIK_INCLUDE_PATH . self::DEFINITION_FILE);
diff --git a/plugins/Referrers/Social.php b/plugins/Referrers/Social.php
index b10ae61294..f7a0cca7db 100644
--- a/plugins/Referrers/Social.php
+++ b/plugins/Referrers/Social.php
@@ -8,6 +8,7 @@
*/
namespace Piwik\Plugins\Referrers;
use Piwik\Cache;
+use Piwik\Common;
use Piwik\Option;
use Piwik\Piwik;
use Piwik\Singleton;
@@ -51,7 +52,7 @@ class Social extends Singleton
$list = Option::get(self::OPTION_STORAGE_NAME);
if ($list) {
- $this->definitionList = unserialize(base64_decode($list));
+ $this->definitionList = Common::safe_unserialize(base64_decode($list));
} else {
// Fallback to reading the bundled list
$yml = file_get_contents(PIWIK_INCLUDE_PATH . self::DEFINITION_FILE);
diff --git a/plugins/UserCountryMap/Controller.php b/plugins/UserCountryMap/Controller.php
index c78aa3f971..c41ae83e66 100644
--- a/plugins/UserCountryMap/Controller.php
+++ b/plugins/UserCountryMap/Controller.php
@@ -75,7 +75,7 @@ class Controller extends \Piwik\Plugin\Controller
. '&filter_limit=-1'
);
$config = array();
- $config['visitsSummary'] = unserialize($request->process());
+ $config['visitsSummary'] = Common::safe_unserialize($request->process());
$config['countryDataUrl'] = $this->_report('UserCountry', 'getCountry',
$this->idSite, $period, $date, $token_auth, false, $segment);
$config['regionDataUrl'] = $this->_report('UserCountry', 'getRegion',
@@ -277,7 +277,7 @@ class Controller extends \Piwik\Plugin\Controller
. '&token_auth=' . $token_auth
. '&filter_limit=-1'
);
- $metaData = unserialize($request->process());
+ $metaData = Common::safe_unserialize($request->process());
$metrics = array();
if (!empty($metaData[0]['metrics']) && is_array($metaData[0]['metrics'])) {
diff --git a/tests/PHPUnit/Unit/CommonTest.php b/tests/PHPUnit/Unit/CommonTest.php
index 08931b6bfe..7466a742bc 100644
--- a/tests/PHPUnit/Unit/CommonTest.php
+++ b/tests/PHPUnit/Unit/CommonTest.php
@@ -10,10 +10,13 @@ namespace Piwik\Tests\Unit;
use Exception;
use PHPUnit_Framework_TestCase;
+use Piwik\Application\Environment;
use Piwik\Common;
use Piwik\Container\StaticContainer;
use Piwik\Filesystem;
use Piwik\Intl\Data\Provider\RegionDataProvider;
+use Piwik\Log;
+use Piwik\Tests\Framework\Mock\FakeLogger;
/**
* @backupGlobals enabled
@@ -279,6 +282,42 @@ class CommonTest extends PHPUnit_Framework_TestCase
}
}
+ public function testSafeUnserialize()
+ {
+ if (PHP_MAJOR_VERSION < 7) {
+ $this->markTestSkipped('secure unserialize tests are for PHP7 only');
+ return;
+ }
+
+ // should unserialize an allowed class
+ $this->assertTrue(Common::safe_unserialize('O:12:"Piwik\Common":0:{}', ['Piwik\Common']) instanceof Common);
+
+ // not allowed classed should result in an incomplete class
+ $this->assertTrue(Common::safe_unserialize('O:12:"Piwik\Common":0:{}') instanceof \__PHP_Incomplete_Class);
+
+ // strings not unserializable should return false and trigger a debug log
+ $logger = $this->createFakeLogger();
+ $this->assertFalse(Common::safe_unserialize('{1:somebroken}'));
+ $this->assertContains('Unable to unserialize a string: unserialize(): Error at offset 0 of 14 bytes', $logger->output);
+ }
+
+ private function createFakeLogger()
+ {
+ $logger = new FakeLogger();
+
+ $newEnv = new Environment('test', array(
+ 'Psr\Log\LoggerInterface' => $logger,
+ 'Tests.log.allowAllHandlers' => true,
+ ));
+ $newEnv->init();
+
+ $newMonologLogger = $newEnv->getContainer()->make('Psr\Log\LoggerInterface');
+ $oldLogger = new Log($newMonologLogger);
+ Log::setSingletonInstance($oldLogger);
+
+ return $logger;
+ }
+
/**
* Dataprovider for testGetBrowserLanguage
*/