The vulnerability can be exploited when the function getItemForItemtype()
is called with a user controllable parameter (authenticated or not). For example, in ajax/common.tabs.php
line 63:
if ($item = getItemForItemtype($_POST['itemtype'])) { if ($item instanceof CommonDBTM && $item->isNewItem() && (!isset($_POST["id"]) || !$item->can($_POST["id"],'r'))) { exit(); } }
The function getItemForItemtype()
tries to instantiate an object with the name given in parameter. It can be found in inc/db.functions.php
line 200.
/** * Get new item objet for an itemtype * * @since version 0.83 * * @param $itemtype string itemtype * * @return itemtype object or false if class does not exists **/ function getItemForItemtype($itemtype) { if (class_exists($itemtype)) { return new $itemtype(); } return false; }
The fact is that the function class_exists
checks if the class exists by calling registered autoload functions. Let's see one of those function: SimplePie_Autoloader::autoload
(which is in inc/autoload.function.php
).
// SimplePie autoloader spl_autoload_register(array(new SimplePie_Autoloader(), 'autoload')); /** * Autoloader class * * @since version 0.84 * * @package SimplePie **/ class SimplePie_Autoloader { /** * Constructor **/ public function __construct() { $this->path = GLPI_SIMPLEPIE_PATH; } /** * Autoloader * * @param string $class The name of the class to attempt to load. **/ public function autoload($class) { // Only load the class if it starts with "SimplePie" if (strpos($class, 'SimplePie') !== 0) { return; } $filename = $this->path . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; require_once($filename); } }
If the class name begins with SimplePie
then all _
occurrences are remplaced with /
and the function require_once
is called. Thus, it is possible to include a local file by calling the page with, for example, itemtype=SimplePie_.._.._.._.._.._.._.._.._.._tmp_ponce
.
However, there are some problems to exploit correctly this vulnerability:
.
..php
and control its content.The first patch adds a check in the autoload function of SimplePie: https://forge.indepnet.net/projects/glpi/repository/revisions/23191/diff/branches/0.84-bugfixes/inc/autoload.function.php
Index: autoload.function.php =================================================================== --- autoload.function.php (révision 23190) +++ autoload.function.php (révision 23191) @@ -352,6 +352,11 @@ **/ public function autoload($class) { + // empty classname or non concerted plugin or classname containing dot (leaving GLPI main treee) + if (empty($class) || is_numeric($class) || (strpos($class, '.') !== false)) { + return false; + } + // Only load the class if it starts with "SimplePie" if (strpos($class, 'SimplePie') !== 0) { return;
We cannot use this autoload function anymore. But it is still possible to use another, such as Zend Framework. It is in lib/Zend/Loader/StandardAutoloader.php
. It begins by checking if the class name begins with Zend\
and construct the filename. So itemtype=Zend\_.._.._.._.._.._.._tmp_ponce
still works !
Complete patch is available here: https://forge.indepnet.net/projects/glpi/repository/revisions/23200/diff/trunk/inc/autoload.function.php
Index: autoload.function.php =================================================================== --- autoload.function.php (révision 23199) +++ autoload.function.php (révision 23200) @@ -258,9 +258,11 @@ // empty classname or non concerted plugin or classname containing dot (leaving GLPI main treee) if (empty($classname) || is_numeric($classname) || (strpos($classname, '.') !== false)) { - return false; + die("Security die. trying to load an forbidden class name"); } + + $dir = GLPI_ROOT . "/inc/"; if ($plug = isPluginItemType($classname)) { $plugname = strtolower($plug['plugin']); @@ -294,6 +296,14 @@ if (preg_match('/^CAS_.*/', $classname)) { return false; } + // Do not try to load Zend using GLPI autoload + if (preg_match('/^Zend.*/', $classname)) { + return false; + } + // Do not try to load Simplepie using GLPI autoload + if (preg_match('/^SimplePie.*/', $classname)) { + return false; + } $item = strtolower($classname); } @@ -312,6 +322,9 @@ } } +// Use spl autoload to allow stackable autoload. +spl_autoload_register('glpi_autoload'); + require_once (GLPI_ZEND_PATH . '/Loader/StandardAutoloader.php'); $option = array(Zend\Loader\StandardAutoloader::LOAD_NS => array('Zend' => GLPI_ZEND_PATH)); $loader = new Zend\Loader\StandardAutoloader($option); @@ -320,12 +333,9 @@ // SimplePie autoloader spl_autoload_register(array(new SimplePie_Autoloader(), 'autoload')); -// Use spl autoload to allow stackable autoload. -spl_autoload_register('glpi_autoload'); - /** * Autoloader class *
Two changes:
glpi_autoload
, if a .
is found into the parameter, then die
is called
Thus it is not possible anymore to provide a name with a .
without stopping the script.