MDL-62947 core_form: fix remote code execution exploit in QuickForms
authorJake Dallimore <jake@moodle.com>
Mon, 30 Jul 2018 06:14:54 +0000 (14:14 +0800)
committerJun Pataleta <jun@moodle.com>
Wed, 5 Sep 2018 04:12:19 +0000 (12:12 +0800)
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
lib/pear/HTML/QuickForm/element.php
lib/pear/HTML/QuickForm/hierselect.php
lib/pear/HTML/QuickForm/utils.php [new file with mode: 0644]

index d74dd55..0207066 100644 (file)
 
 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])) {
index 89597aa..18332ea 100644 (file)
 // $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);
             }
         }
     }
index 3762229..1bce43b 100644 (file)
 
 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 (file)
index 0000000..00c6ba8
--- /dev/null
@@ -0,0 +1,159 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * utility functions
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category    HTML
+ * @package     HTML_QuickForm
+ * @author      Chuck Burgess <ashnazg@php.net>
+ * @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 <ashnazg@php.net>
+ * @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;
+    }
+}
+?>