CVE-2024-27098 (published 2024-03-18T17:15Z)

GLPI is a Free Asset and IT Management Software package, Data center management, ITIL Service Desk, licenses tracking and software auditing. An authenticated user can execute a SSRF based attack using Arbitrary Object Instantiation. This issue has been patched in version 10.0.13.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/glpi-project/glpi
commit_details: repo_url: https://github.com/glpi-project/glpi
author: Johan Cwiklinski
author_date: 2024-03-13 08:15:19+01:00
msg: Merge pull request from GHSA-92x4-q9w5-837w

* Check for valid itemtype before creating objects

* Restrict HTTP methods allowed by Guzzle client

---------

Co-authored-by: Cédric Anne <cedric.anne@gmail.com>
repository: repo_url: https://github.com/glpi-project/glpi
repo_name: glpi-project/glpi
description: GLPI is a Free Asset and IT Management Software package, Data center management, ITIL Service Desk, licenses tracking and software auditing.
repo_language: PHP
owner: glpi-project
file_changes: filename: comments.php
change_type: ModificationType.MODIFY
diff: @@ -120,7 +120,7 @@ if (
                 }
 
                 if (isset($_POST['with_dc_position'])) {
-                    $item = new $_POST['itemtype']();
+                    $item = getItemForItemtype($_POST['itemtype']);
                     echo "<script type='text/javascript' >\n";
 
                    //if item have a DC position (reload url to it's rack)

programming_language: PHP
filename: dropdownLocation.php
change_type: ModificationType.MODIFY
diff: @@ -45,7 +45,7 @@ if (
     throw new \RuntimeException('Required argument missing or incorrect!');
 }
 
-$item = new $_REQUEST['itemtype']();
+$item = getItemForItemtype($_REQUEST['itemtype']);
 $item->getFromDB((int) $_REQUEST['items_id']);
 
 $locations_id = $item->fields['locations_id'] ?? 0;

programming_language: PHP
filename: kanban.php
change_type: ModificationType.MODIFY
diff: @@ -170,7 +170,7 @@ if (($_POST['action'] ?? null) === 'update') {
 } else if (($_POST['action'] ?? null) === 'move_item') {
     $checkParams(['card', 'column', 'position', 'kanban']);
     /** @var Kanban|CommonDBTM $kanban */
-    $kanban = new $_POST['kanban']['itemtype']();
+    $kanban = getItemForItemtype($_POST['kanban']['itemtype']);
     $can_move = $kanban->canOrderKanbanCard($_POST['kanban']['items_id']);
     if ($can_move) {
         Item_Kanban::moveCard(

programming_language: PHP
filename: rule.php
change_type: ModificationType.MODIFY
diff: @@ -41,7 +41,7 @@ Session::checkLoginUser();
 switch ($_REQUEST['action']) {
     case "move_rule":
         if (is_subclass_of($_POST['collection_classname'], RuleCollection::getType())) {
-            $rule_collection = new $_POST['collection_classname']();
+            $rule_collection = getItemForItemtype($_POST['collection_classname']);
             $rule_collection->moveRule((int) $_POST['rule_id'], (int) $_POST['ref_id'], $_POST['sort_action']);
         }
         break;

programming_language: PHP
filename: updateTranslationFields.php
change_type: ModificationType.MODIFY
diff: @@ -44,7 +44,7 @@ Html::header_nocache();
 
 Session::checkRight("dropdown", UPDATE);
 if (isset($_POST['itemtype']) && isset($_POST['language'])) {
-    $item = new $_POST['itemtype']();
+    $item = getItemForItemtype($_POST['itemtype']);
     $item->getFromDB($_POST['items_id']);
     DropdownTranslation::dropdownFields($item, $_POST['language']);
 }

programming_language: PHP
filename: updateTranslationValue.php
change_type: ModificationType.MODIFY
diff: @@ -43,7 +43,7 @@ Session::checkRight("dropdown", UPDATE);
 $matching_field = null;
 
 if (isset($_POST['itemtype'], $_POST['field']) && is_a($_POST['itemtype'], CommonDropdown::class, true)) {
-    $itemtype = new $_POST['itemtype']();
+    $itemtype = getItemForItemtype($_POST['itemtype']);
     $matching_field = $itemtype->getAdditionalField($_POST['field']);
 }
 

programming_language: PHP
filename: composer.json
change_type: ModificationType.MODIFY
diff: @@ -145,7 +145,8 @@
             "patch -f -p1 -d vendor/laminas/laminas-mail/ < tools/patches/laminas-mail-invalid-header-ignore.patch || true",
             "patch -f -p1 -d vendor/laminas/laminas-mail/ < tools/patches/laminas-mail-address-no-length-check.patch || true",
             "patch -f -p1 -d vendor/atoum/atoum/ < tools/patches/atoum-4.1.0...4.2.0.patch || true",
-            "patch -f -p1 -d vendor/michelf/php-markdown/ < tools/patches/php-markdown-php8-compat.patch || true"
+            "patch -f -p1 -d vendor/michelf/php-markdown/ < tools/patches/php-markdown-php8-compat.patch || true",
+            "patch -f -p1 -d vendor/guzzlehttp/guzzle/ < tools/patches/guzzle-http-client-restrict-http-methods.patch || true"
         ],
         "post-update-cmd": [
             "@php -r \"file_put_contents('.composer.hash', sha1_file('composer.lock'));\"",
@@ -153,7 +154,8 @@
             "patch -f -p1 -d vendor/laminas/laminas-mail/ < tools/patches/laminas-mail-invalid-header-ignore.patch || true",
             "patch -f -p1 -d vendor/laminas/laminas-mail/ < tools/patches/laminas-mail-address-no-length-check.patch || true",
             "patch -f -p1 -d vendor/atoum/atoum/ < tools/patches/atoum-4.1.0...4.2.0.patch || true",
-            "patch -f -p1 -d vendor/michelf/php-markdown/ < tools/patches/php-markdown-php8-compat.patch || true"
+            "patch -f -p1 -d vendor/michelf/php-markdown/ < tools/patches/php-markdown-php8-compat.patch || true",
+            "patch -f -p1 -d vendor/guzzlehttp/guzzle/ < tools/patches/guzzle-http-client-restrict-http-methods.patch || true"
         ],
         "build": [
             "bin/console dependencies install && bin/console locales:compile"

programming_language: Shell
filename: device.php
change_type: ModificationType.MODIFY
diff: @@ -41,5 +41,5 @@ if (!isset($_GET['itemtype']) || !class_exists($_GET['itemtype'])) {
     );
 }
 
-$dropdown = new $_GET['itemtype']();
+$dropdown = getItemForItemtype($_GET['itemtype']);
 include(GLPI_ROOT . "/front/dropdown.common.php");

programming_language: PHP
filename: devicemodel.form.php
change_type: ModificationType.MODIFY
diff: @@ -41,5 +41,5 @@ if (!isset($_GET['itemtype']) || !class_exists($_GET['itemtype'])) {
     );
 }
 
-$dropdown = new $_GET['itemtype']();
+$dropdown = getItemForItemtype($_GET['itemtype']);
 include(GLPI_ROOT . "/front/dropdown.common.form.php");

programming_language: PHP
filename: devicemodel.php
change_type: ModificationType.MODIFY
diff: @@ -41,5 +41,5 @@ if (!isset($_GET['itemtype']) || !class_exists($_GET['itemtype'])) {
     );
 }
 
-$dropdown = new $_GET['itemtype']();
+$dropdown = getItemForItemtype($_GET['itemtype']);
 include(GLPI_ROOT . "/front/dropdown.common.php");

programming_language: PHP
filename: devicetype.form.php
change_type: ModificationType.MODIFY
diff: @@ -41,5 +41,5 @@ if (!isset($_GET['itemtype']) || !class_exists($_GET['itemtype'])) {
     );
 }
 
-$dropdown = new $_GET['itemtype']();
+$dropdown = getItemForItemtype($_GET['itemtype']);
 include(GLPI_ROOT . "/front/dropdown.common.form.php");

programming_language: PHP
filename: devicetype.php
change_type: ModificationType.MODIFY
diff: @@ -41,5 +41,5 @@ if (!isset($_GET['itemtype']) || !class_exists($_GET['itemtype'])) {
     );
 }
 
-$dropdown = new $_GET['itemtype']();
+$dropdown = getItemForItemtype($_GET['itemtype']);
 include(GLPI_ROOT . "/front/dropdown.common.php");

programming_language: PHP
filename: item_device.php
change_type: ModificationType.MODIFY
diff: @@ -44,7 +44,7 @@ if (!isset($_GET['itemtype']) || !class_exists($_GET['itemtype'])) {
     );
 }
 
-$itemDevice = new $_GET['itemtype']();
+$itemDevice = getItemForItemtype($_GET['itemtype']);
 if (!$itemDevice->canView()) {
     Session::redirectIfNotLoggedIn();
     Html::displayRightError();

programming_language: PHP
filename: itilfollowup.form.php
change_type: ModificationType.MODIFY
diff: @@ -50,7 +50,7 @@ $handled = false;
 if (!isset($_POST['itemtype']) || !class_exists($_POST['itemtype'])) {
     Html::displayErrorAndDie('Lost');
 }
-$track = new $_POST['itemtype']();
+$track = getItemForItemtype($_POST['itemtype']);
 
 
 if (isset($_POST["add"])) {

programming_language: PHP
filename: itilsolution.form.php
change_type: ModificationType.MODIFY
diff: @@ -43,7 +43,7 @@ include('../inc/includes.php');
 Session::checkLoginUser();
 
 $solution = new ITILSolution();
-$track = new $_POST['itemtype']();
+$track = getItemForItemtype($_POST['itemtype']);
 $track->getFromDB($_POST['items_id']);
 
 $redirect = null;

programming_language: PHP
filename: RuleAction.php
change_type: ModificationType.MODIFY
diff: @@ -51,7 +51,7 @@ class RuleAction extends CommonDBChild
         $forbidden[] = 'update';
 
         if (isset($_POST['rule_class_name']) && is_subclass_of(\Rule::class, $_POST['rule_class_name'])) {
-            $rule = new $_POST['rule_class_name']();
+            $rule = getItemForItemtype($_POST['rule_class_name']);
             if ($rule->maxActionsCount() == 1) {
                 $forbidden[] = 'clone';
             }

programming_language: PHP
filename: APIRest.php
change_type: ModificationType.MODIFY
diff: @@ -105,17 +105,11 @@ class APIRest extends APIBaseClass
            // Guzzle lib will automatically push the correct Content-type
             unset($params['headers']['Content-Type']);
         }
-        $verb = strtolower($verb);
-        if (in_array($verb, ['get', 'post', 'delete', 'put', 'options', 'patch'])) {
-            try {
-                return $this->http_client->{$verb}(
-                    $this->base_uri . $relative_uri,
-                    $params
-                );
-            } catch (\Throwable $e) {
-                throw $e;
-            }
-        }
+        return $this->http_client->request(
+            $verb,
+            $this->base_uri . $relative_uri,
+            $params
+        );
     }
 
     protected function query(

programming_language: PHP
filename: guzzle-http-client-restrict-http-methods.patch
change_type: ModificationType.ADD
diff: @@ -0,0 +1,33 @@
+diff --git a/src/Client.php b/src/Client.php
+index 58f1d891..7448bb49 100644
+--- a/src/Client.php
++++ b/src/Client.php
+@@ -70,28 +70,6 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
+         $this->configureDefaults($config);
+     }
+ 
+-    /**
+-     * @param string $method
+-     * @param array  $args
+-     *
+-     * @return PromiseInterface|ResponseInterface
+-     *
+-     * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0.
+-     */
+-    public function __call($method, $args)
+-    {
+-        if (\count($args) < 1) {
+-            throw new InvalidArgumentException('Magic request methods require a URI and optional options array');
+-        }
+-
+-        $uri = $args[0];
+-        $opts = $args[1] ?? [];
+-
+-        return \substr($method, -5) === 'Async'
+-            ? $this->requestAsync(\substr($method, 0, -5), $uri, $opts)
+-            : $this->request($method, $uri, $opts);
+-    }
+-
+     /**
+      * Asynchronously send an HTTP request.
+      *

programming_language: PHP