From ba072e42dc15123bdd61185ccce85e384ca452b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Tue, 12 Jul 2016 15:04:45 +0200 Subject: [PATCH] Validate serialized data before unserializing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need only strings, integers or arrays, so there is no need to unserialize strings containing any complex types. Signed-off-by: Michal Čihař --- libraries/core.lib.php | 88 +++++++++++++++++++ libraries/tracking.lib.php | 5 +- .../core/PMA_safeUnserialize_test.php | 55 ++++++++++++ 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 test/libraries/core/PMA_safeUnserialize_test.php diff --git a/libraries/core.lib.php b/libraries/core.lib.php index aed72ffc76..7aa04f9ebb 100644 --- a/libraries/core.lib.php +++ b/libraries/core.lib.php @@ -1008,3 +1008,91 @@ function PMA_sanitizeMySQLHost($name) return $name; } + +/** + * Safe unserializer wrapper + * + * It does not unserialize data containing objects + * + * @param string $data Data to unserialize + * + * @return mixed + */ +function PMA_safeUnserialize($data) +{ + if (! is_string($data)) { + return null; + } + + /* validate serialized data */ + $length = strlen($data); + $depth = 0; + for ($i = 0; $i < $length; $i++) { + $value = $data[$i]; + + switch ($value) + { + case '}': + /* end of array */ + if ($depth <= 0) { + return null; + } + $depth--; + break; + case 's': + /* string */ + // parse sting length + $strlen = intval($data[$i + 2]); + // string start + $i = strpos($data, ':', $i + 2); + if ($i === false) { + return null; + } + // skip string, quotes and ; + $i += 2 + $strlen + 1; + if ($data[$i] != ';') { + return null; + } + break; + + case 'b': + case 'i': + case 'd': + /* bool, integer or double */ + // skip value to sepearator + $i = strpos($data, ';', $i); + if ($i === false) { + return null; + } + break; + case 'a': + /* array */ + // find array start + $i = strpos($data, '{', $i); + if ($i === false) { + return null; + } + // remember nesting + $depth++; + break; + case 'N': + /* null */ + // skip to end + $i = strpos($data, ';', $i); + if ($i === false) { + return null; + } + break; + default: + /* any other elements are not wanted */ + return null; + } + } + + // check unterminated arrays + if ($depth > 0) { + return null; + } + + return unserialize($data); +} diff --git a/libraries/tracking.lib.php b/libraries/tracking.lib.php index bc478e1c17..7687cc7365 100644 --- a/libraries/tracking.lib.php +++ b/libraries/tracking.lib.php @@ -860,7 +860,10 @@ function PMA_getHtmlForSchemaSnapshot($url_query) ); // Unserialize snapshot - $temp = unserialize($data['schema_snapshot']); + $temp = PMA_safeUnserialize($data['schema_snapshot']); + if ($temp === null) { + $temp = array('COLUMNS' => array(), 'INDEXES' => array()); + } $columns = $temp['COLUMNS']; $indexes = $temp['INDEXES']; $html .= PMA_getHtmlForColumns($columns); diff --git a/test/libraries/core/PMA_safeUnserialize_test.php b/test/libraries/core/PMA_safeUnserialize_test.php new file mode 100644 index 0000000000..f2710c0792 --- /dev/null +++ b/test/libraries/core/PMA_safeUnserialize_test.php @@ -0,0 +1,55 @@ +assertEquals( + $expected, + PMA_safeUnserialize($data) + ); + } + + /** + * Test data provider + * + * @return array + */ + function provideMySQLHosts() + { + return array( + array('s:6:"foobar";', 'foobar'), + array('foobar', null), + array('b:0;', false), + array('O:1:"a":1:{s:5:"value";s:3:"100";}', null), + array('O:8:"stdClass":1:{s:5:"field";O:8:"stdClass":0:{}}', null), + array(serialize(array(1, 2, 3)), array(1, 2, 3)), + array(serialize('string""'), 'string""'), + array(serialize(array('foo' => 'bar')), array('foo' => 'bar')), + array(serialize(array('1', new stdClass(), '2')), null), + ); + } + +} +