From c87010baca251ae1ccb39c4afb4de6595ee0b560 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Mon, 30 Jul 2018 14:14:54 +0800 Subject: [PATCH] MDL-62947 core_form: fix remote code execution exploit in QuickForms Applies the patch found upstream: https://github.com/pear/HTML_QuickForm/commit/ d3a6d5c44dedf3c164c6c79198e4ef479bcedcd2 and make util methods static for php7 compatibility. --- lib/pear/HTML/QuickForm.php | 14 ++- lib/pear/HTML/QuickForm/element.php | 22 +++- lib/pear/HTML/QuickForm/hierselect.php | 27 +++-- lib/pear/HTML/QuickForm/utils.php | 159 +++++++++++++++++++++++++ 4 files changed, 204 insertions(+), 18 deletions(-) create mode 100644 lib/pear/HTML/QuickForm/utils.php diff --git a/lib/pear/HTML/QuickForm.php b/lib/pear/HTML/QuickForm.php index d74dd55ef43..0207066f889 100644 --- a/lib/pear/HTML/QuickForm.php +++ b/lib/pear/HTML/QuickForm.php @@ -21,6 +21,10 @@ require_once('PEAR.php'); require_once('HTML/Common.php'); +/** + * Static utility methods. + */ +require_once('HTML/QuickForm/utils.php'); $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = array( @@ -834,9 +838,15 @@ class HTML_QuickForm extends HTML_Common { } elseif (false !== ($pos = strpos($elementName, '['))) { $base = substr($elementName, 0, $pos); - $idx = "['" . str_replace(array(']', '['), array('', "']['"), substr($elementName, $pos + 1, -1)) . "']"; + $keys = str_replace( + array('\\', '\'', ']', '['), array('\\\\', '\\\'', '', "']['"), + substr($elementName, $pos + 1, -1) + ); + $idx = "['" . $keys . "']"; + $keyArray = explode("']['", $keys); + if (isset($this->_submitValues[$base])) { - $value = eval("return (isset(\$this->_submitValues['{$base}']{$idx})) ? \$this->_submitValues['{$base}']{$idx} : null;"); + $value = HTML_QuickForm_utils::recursiveValue($this->_submitValues[$base], $keyArray, NULL); } if ((is_array($value) || null === $value) && isset($this->_submitFiles[$base])) { diff --git a/lib/pear/HTML/QuickForm/element.php b/lib/pear/HTML/QuickForm/element.php index 89597aa8a06..18332ea9997 100644 --- a/lib/pear/HTML/QuickForm/element.php +++ b/lib/pear/HTML/QuickForm/element.php @@ -20,6 +20,10 @@ // $Id$ require_once('HTML/Common.php'); +/** + * Static utility methods. + */ +require_once('HTML/QuickForm/utils.php'); /** * Base class for form elements @@ -351,8 +355,12 @@ class HTML_QuickForm_element extends HTML_Common if (isset($values[$elementName])) { return $values[$elementName]; } elseif (strpos($elementName, '[')) { - $myVar = "['" . str_replace(array(']', '['), array('', "']['"), $elementName) . "']"; - return eval("return (isset(\$values$myVar)) ? \$values$myVar : null;"); + $keys = str_replace( + array('\\', '\'', ']', '['), array('\\\\', '\\\'', '', "']['"), + $elementName + ); + $arrayKeys = explode("']['", $keys); + return HTML_QuickForm_utils::recursiveValue($values, $arrayKeys); } else { return null; } @@ -483,10 +491,12 @@ class HTML_QuickForm_element extends HTML_Common if (!strpos($name, '[')) { return array($name => $value); } else { - $valueAry = array(); - $myIndex = "['" . str_replace(array(']', '['), array('', "']['"), $name) . "']"; - eval("\$valueAry$myIndex = \$value;"); - return $valueAry; + $keys = str_replace( + array('\\', '\'', ']', '['), array('\\\\', '\\\'', '', "']['"), + $name + ); + $keysArray = explode("']['", $keys); + return HTML_QuickForm_utils::recursiveBuild($keysArray, $value); } } } diff --git a/lib/pear/HTML/QuickForm/hierselect.php b/lib/pear/HTML/QuickForm/hierselect.php index 37622297d03..1bce43b6e7a 100644 --- a/lib/pear/HTML/QuickForm/hierselect.php +++ b/lib/pear/HTML/QuickForm/hierselect.php @@ -22,6 +22,10 @@ require_once('HTML/QuickForm/group.php'); require_once('HTML/QuickForm/select.php'); +/** + * Static utility methods. + */ +require_once 'HTML/QuickForm/utils.php'; /** * Class to dynamically create two or more HTML Select elements @@ -233,16 +237,19 @@ class HTML_QuickForm_hierselect extends HTML_QuickForm_group */ function _setOptions() { - $toLoad = ''; + $arrayKeys = []; foreach (array_keys($this->_elements) AS $key) { - $array = eval("return isset(\$this->_options[{$key}]{$toLoad})? \$this->_options[{$key}]{$toLoad}: null;"); - if (is_array($array)) { - $select =& $this->_elements[$key]; - $select->_options = array(); - $select->loadArray($array); - - $value = is_array($v = $select->getValue()) ? $v[0] : key($array); - $toLoad .= '[\'' . str_replace(array('\\', '\''), array('\\\\', '\\\''), $value) . '\']'; + if (isset($this->_options[$key])) { + if ((empty($arrayKeys)) || HTML_QuickForm_utils::recursiveIsset($this->_options[$key], $arrayKeys)) { + $array = empty($arrayKeys) ? $this->_options[$key] : HTML_QuickForm_utils::recursiveValue($this->_options[$key], $arrayKeys); + if (is_array($array)) { + $select =& $this->_elements[$key]; + $select->_options = array(); + $select->loadArray($array); + $value = is_array($v = $select->getValue()) ? $v[0] : key($array); + $arrayKeys[] = $value; + } + } } } } // end func _setOptions @@ -585,4 +592,4 @@ JAVASCRIPT; // }}} } // end class HTML_QuickForm_hierselect -?> \ No newline at end of file +?> diff --git a/lib/pear/HTML/QuickForm/utils.php b/lib/pear/HTML/QuickForm/utils.php new file mode 100644 index 00000000000..00c6ba8e90e --- /dev/null +++ b/lib/pear/HTML/QuickForm/utils.php @@ -0,0 +1,159 @@ + + * @copyright 2001-2018 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id$ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Provides a collection of static methods for array manipulation. + * + * (courtesy of CiviCRM project (https://civicrm.org/) + * + * @category HTML + * @package HTML_QuickForm + * @author Chuck Burgess + * @version Release: @package_version@ + * @since 3.2 + */ +class HTML_QuickForm_utils +{ + /** + * Get a single value from an array-tree. + * + * @param array $values Ex: ['foo' => ['bar' => 123]]. + * @param array $path Ex: ['foo', 'bar']. + * @param mixed $default + * @return mixed Ex 123. + * + * @access public + * @static + */ + static function pathGet($values, $path, $default = NULL) { + foreach ($path as $key) { + if (!is_array($values) || !isset($values[$key])) { + return $default; + } + $values = $values[$key]; + } + return $values; + } + + /** + * Check if a key isset which may be several layers deep. + * + * This is a helper for when the calling function does not know how many layers deep + * the path array is so cannot easily check. + * + * @param array $values + * @param array $path + * @return bool + * + * @access public + * @static + */ + static function pathIsset($values, $path) { + foreach ($path as $key) { + if (!is_array($values) || !isset($values[$key])) { + return FALSE; + } + $values = $values[$key]; + } + return TRUE; + } + + /** + * Set a single value in an array tree. + * + * @param array $values Ex: ['foo' => ['bar' => 123]]. + * @param array $pathParts Ex: ['foo', 'bar']. + * @param mixed $value Ex: 456. + * @return void + * + * @access public + * @static + */ + static function pathSet(&$values, $pathParts, $value) { + $r = &$values; + $last = array_pop($pathParts); + foreach ($pathParts as $part) { + if (!isset($r[$part])) { + $r[$part] = array(); + } + $r = &$r[$part]; + } + $r[$last] = $value; + } + + /** + * Check if a key isset which may be several layers deep. + * + * This is a helper for when the calling function does not know how many layers deep the + * path array is so cannot easily check. + * + * @param array $array + * @param array $path + * @return bool + * + * @access public + * @static + */ + static function recursiveIsset($array, $path) { + return self::pathIsset($array, $path); + } + + /** + * Check if a key isset which may be several layers deep. + * + * This is a helper for when the calling function does not know how many layers deep the + * path array is so cannot easily check. + * + * @param array $array + * @param array $path An array of keys, + * e.g [0, 'bob', 8] where we want to check if $array[0]['bob'][8] + * @param mixed $default Value to return if not found. + * @return bool + * + * @access public + * @static + */ + static function recursiveValue($array, $path, $default = NULL) { + return self::pathGet($array, $path, $default); + } + + /** + * Append the value to the array using the key provided. + * + * e.g if value is 'llama' & path is [0, 'email', 'location'] result will be + * [0 => ['email' => ['location' => 'llama']] + * + * @param $path + * @param $value + * @param array $source + * @return array + * + * @access public + * @static + */ + static function recursiveBuild($path, $value, $source = array()) { + self::pathSet($source, $path, $value); + return $source; + } +} +?> -- 2.43.0