CVE-2024-27937 (published 2024-03-18T16: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 obtain the email address of all GLPI users. This issue has been patched in version 10.0.13.\n


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: Cédric Anne
author_date: 2024-03-13 08:16:53+01:00
msg: Merge pull request from GHSA-98qw-hpg3-2hpj

* Filter entity restriction in dropdowns to match active entities

* Handle string values in entities filtering
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: dropdownAllItems.php
change_type: ModificationType.MODIFY
diff: @@ -43,6 +43,10 @@ Html::header_nocache();
 
 Session::checkCentralAccess();
 
+if (isset($_POST['entity_restrict'])) {
+    $_POST['entity_restrict'] = Session::getMatchingActiveEntities($_POST['entity_restrict']);
+}
+
 // Make a select box
 if ($_POST["idtable"] && class_exists($_POST["idtable"])) {
    // Link to user for search only > normal users

programming_language: PHP
filename: dropdownConnect.php
change_type: ModificationType.MODIFY
diff: @@ -52,7 +52,7 @@ Computer_Item::dropdownConnect(
     $_POST["itemtype"],
     $_POST['fromtype'],
     $_POST['myname'],
-    $_POST["entity_restrict"],
+    Session::getMatchingActiveEntities($_POST['entity_restrict']),
     $_POST["onlyglobal"],
     $used
 );

programming_language: PHP
filename: dropdownItilActors.php
change_type: ModificationType.MODIFY
diff: @@ -65,7 +65,7 @@ if (
                 }
 
                 $options = ['name'        => '_itil_' . $_POST["actortype"] . '[users_id]',
-                    'entity'      => $_POST['entity_restrict'],
+                    'entity'      => Session::getMatchingActiveEntities($_POST['entity_restrict']),
                     'right'       => $right,
                     'rand'        => $rand,
                     'ldap_import' => true
@@ -147,7 +147,7 @@ if (
 
                 $param = [
                     'name'      => '_itil_' . $_POST["actortype"] . '[groups_id]',
-                    'entity'    => $_POST['entity_restrict'],
+                    'entity'    => Session::getMatchingActiveEntities($_POST['entity_restrict']),
                     'condition' => $cond,
                     'rand'      => $rand
                 ];
@@ -179,7 +179,7 @@ if (
 
             case "supplier":
                 $options = ['name'      => '_itil_' . $_POST["actortype"] . '[suppliers_id]',
-                    'entity'    => $_POST['entity_restrict'],
+                    'entity'    => Session::getMatchingActiveEntities($_POST['entity_restrict']),
                     'rand'      => $rand
                 ];
                 if ($CFG_GLPI["notifications_mailing"]) {

programming_language: PHP
filename: dropdownProjectTaskTicket.php
change_type: ModificationType.MODIFY
diff: @@ -51,7 +51,7 @@ if (isset($_POST["projects_id"])) {
     }
 
     $p = ['itemtype'     => ProjectTask::getType(),
-        'entity_restrict' => $_POST['entity_restrict'],
+        'entity_restrict' => Session::getMatchingActiveEntities($_POST['entity_restrict']),
         'myname'          => $_POST["myname"],
         'condition'       => $condition,
         'rand'            => $_POST["rand"]

programming_language: PHP
filename: dropdownSoftwareLicense.php
change_type: ModificationType.MODIFY
diff: @@ -56,7 +56,7 @@ if ($_POST['softwares_id'] > 0) {
         'FROM'      => 'glpi_softwarelicenses',
         'WHERE'     => [
             'glpi_softwarelicenses.softwares_id'   => (int)$_POST['softwares_id']
-        ] + getEntitiesRestrictCriteria('glpi_softwarelicenses', 'entities_id', $_POST['entity_restrict'], true),
+        ] + getEntitiesRestrictCriteria('glpi_softwarelicenses', 'entities_id', Session::getMatchingActiveEntities($_POST['entity_restrict']), true),
         'ORDERBY'   => 'name'
     ]);
     $number = count($iterator);

programming_language: PHP
filename: dropdownTicketCategories.php
change_type: ModificationType.MODIFY
diff: @@ -41,7 +41,7 @@ if (strpos($_SERVER['PHP_SELF'], "dropdownTicketCategories.php")) {
     die("Sorry. You can't access this file directly");
 }
 
-$opt = ['entity' => $_POST["entity_restrict"]];
+$opt = ['entity' => Session::getMatchingActiveEntities($_POST['entity_restrict'])];
 $condition  = [];
 
 if (Session::getCurrentInterface() == "helpdesk") {

programming_language: PHP
filename: dropdownTrackingDeviceType.php
change_type: ModificationType.MODIFY
diff: @@ -76,14 +76,14 @@ if ($isValidItemtype) {
     $field_id = Html::cleanId("dropdown_" . $_POST['myname'] . $rand);
     $p = [
         'itemtype'            => $itemtype,
-        'entity_restrict'     => $_POST['entity_restrict'],
+        'entity_restrict'     => Session::getMatchingActiveEntities($_POST['entity_restrict']),
         'table'               => $table,
         'multiple'            => (int) ($_POST["multiple"] ?? 0) !== 0,
         'myname'              => $_POST["myname"],
         'rand'                => $_POST["rand"],
         'width'               => 'calc(100% - 25px)',
         '_idor_token'         => Session::getNewIDORToken($itemtype, [
-            'entity_restrict' => $_POST['entity_restrict'],
+            'entity_restrict' => Session::getMatchingActiveEntities($_POST['entity_restrict']),
         ]),
     ];
 

programming_language: PHP
filename: updateTrackingDeviceType.php
change_type: ModificationType.MODIFY
diff: @@ -40,6 +40,6 @@ header("Content-Type: text/html; charset=UTF-8");
 Html::header_nocache();
 
 Session::checkRight("ticket", UPDATE);
-Item_Ticket::dropdownMyDevices($_POST["userID"], $_POST["entity_restrict"]);
+Item_Ticket::dropdownMyDevices($_POST["userID"], Session::getMatchingActiveEntities($_POST['entity_restrict']));
 
 Html::ajaxFooter();

programming_language: PHP
filename: CommonDBRelation.php
change_type: ModificationType.MODIFY
diff: @@ -1478,7 +1478,7 @@ abstract class CommonDBRelation extends CommonDBConnexity
                     } else {
                         $options['name'] = 'peer_' . $peers_id;
                         if (isset($_POST['entity_restrict'])) {
-                            $options['entity'] = $_POST['entity_restrict'];
+                            $options['entity'] = Session::getMatchingActiveEntities($_POST['entity_restrict']);
                         }
                         if ($normalized_action == 'remove') {
                             $options['nochecklimit'] = true;

programming_language: PHP
filename: CommonITILObject.php
change_type: ModificationType.MODIFY
diff: @@ -4948,7 +4948,7 @@ abstract class CommonITILObject extends CommonDBTM
             'right'  => $right,
             'rand'   => $rand,
             'width'  => "95%",
-            'entity' => $options['entities_id'] ?? $options['entity_restrict']
+            'entity' => Session::getMatchingActiveEntities($options['entities_id'] ?? $options['entity_restrict']),
         ];
 
        //only for active ldap and corresponding right

programming_language: PHP
filename: Consumable.php
change_type: ModificationType.MODIFY
diff: @@ -206,7 +206,7 @@ class Consumable extends CommonDBChild
                         'items_id_name'
                                                               => 'give_items_id',
                         'entity_restrict'
-                                                              => $input["entities_id"],
+                                                              => Session::getMatchingActiveEntities($input["entities_id"]),
                         'itemtypes'
                                                               => $CFG_GLPI["consumables_types"]
                     ]);

programming_language: PHP
filename: Dropdown.php
change_type: ModificationType.MODIFY
diff: @@ -202,6 +202,7 @@ class Dropdown
                 $params['entity'] = getSonsOf('glpi_entities', $params['entity']);
             }
         }
+        $params['entity'] = Session::getMatchingActiveEntities($params['entity']);
 
         $field_id = Html::cleanId("dropdown_" . $params['name'] . $params['rand']);
 
@@ -2625,7 +2626,9 @@ JAVASCRIPT;
             return;
         }
 
-        if (
+        if (isset($post['entity_restrict']) && 'default' === $post['entity_restrict']) {
+            $post['entity_restrict'] = $_SESSION['glpiactiveentities'];
+        } elseif (
             isset($post["entity_restrict"])
             && !is_array($post["entity_restrict"])
             && (substr($post["entity_restrict"], 0, 1) === '[')
@@ -2638,10 +2641,7 @@ JAVASCRIPT;
                     $entities[] = (int)$value;
                 }
             }
-            $post["entity_restrict"] = $entities;
-        }
-        if (isset($post['entity_restrict']) && 'default' === $post['entity_restrict']) {
-            $post['entity_restrict'] = $_SESSION['glpiactiveentities'];
+            $post["entity_restrict"] = Session::getMatchingActiveEntities($entities);
         }
 
        // Security
@@ -3515,6 +3515,10 @@ JAVASCRIPT;
             return;
         }
 
+        if (isset($post['entity_restrict'])) {
+            $post['entity_restrict'] = Session::getMatchingActiveEntities($post['entity_restrict']);
+        }
+
         $fromitem->checkGlobal(UPDATE);
         $used = [];
         if (isset($post["used"])) {
@@ -3785,7 +3789,7 @@ JAVASCRIPT;
 
         if ($item->isEntityAssign()) {
             if (isset($post["entity_restrict"]) && ($post["entity_restrict"] >= 0)) {
-                $entity = $post["entity_restrict"];
+                $entity = Session::getMatchingActiveEntities($post['entity_restrict']);
             } else {
                 $entity = '';
             }
@@ -4023,6 +4027,7 @@ JAVASCRIPT;
             $entity_restrict = -1;
             if (isset($post['entity_restrict'])) {
                 $entity_restrict = Toolbox::jsonDecode($post['entity_restrict']);
+                $entity_restrict = Session::getMatchingActiveEntities($entity_restrict);
             }
 
             $start  = intval(($post['page'] - 1) * $post['page_limit']);
@@ -4129,6 +4134,7 @@ JAVASCRIPT;
         $entity_restrict = -1;
         if (isset($post['entity_restrict'])) {
             $entity_restrict = Toolbox::jsonDecode($post['entity_restrict']);
+            $entity_restrict = Session::getMatchingActiveEntities($entity_restrict);
         }
 
        // prevent instanciation of bad classes

programming_language: PHP
filename: Item_Ticket.php
change_type: ModificationType.MODIFY
diff: @@ -604,6 +604,8 @@ class Item_Ticket extends CommonItilObject_Item
             $userID = Session::getLoginUserID();
         }
 
+        $entity_restrict = Session::getMatchingActiveEntities($entity_restrict);
+
         $rand        = $params['rand'];
         $already_add = $params['used'];
 

programming_language: PHP
filename: NetworkPortInstantiation.php
change_type: ModificationType.MODIFY
diff: @@ -1025,7 +1025,7 @@ class NetworkPortInstantiation extends CommonDBChild
         $rand = Dropdown::showItemTypes('NetworkPortConnect_itemtype', $CFG_GLPI["networkport_types"]);
 
         $params = ['itemtype'           => '__VALUE__',
-            'entity_restrict'    => $p['entity'],
+            'entity_restrict'    => Session::getMatchingActiveEntities($p['entity']),
             'networkports_id'    => $ID,
             'comments'           => $p['comments'],
             'myname'             => $p['name'],

programming_language: PHP
filename: Session.php
change_type: ModificationType.MODIFY
diff: @@ -1957,6 +1957,48 @@ class Session
         return $_SESSION['glpiactive_entity'] ?? 0;
     }
 
+    /**
+     * Filter given entities ID list to return only these tht are matching current active entities in session.
+     *
+     * @since 10.0.13
+     *
+     * @param int|int[] $entities_ids
+     *
+     * @return int|int[]
+     */
+    public static function getMatchingActiveEntities(/*int|array*/ $entities_ids)/*: int|array*/
+    {
+        if (
+            (int)$entities_ids === -1
+            || (is_array($entities_ids) && count($entities_ids) === 1 && (int)reset($entities_ids) === -1)
+        ) {
+            // Special value that is generally used to fallback to all active entities.
+            return $entities_ids;
+        }
+
+        if (!is_array($entities_ids) && !is_int($entities_ids) && !ctype_digit($entities_ids)) {
+            // Unexpected value type.
+            return [];
+        }
+
+        $active_entities_ids = $_SESSION['glpiactiveentities'] ?? [];
+
+        if (!is_array($entities_ids) && in_array((int)$entities_ids, $active_entities_ids, true)) {
+            return (int)$entities_ids;
+        }
+
+        $filtered = [];
+        foreach ((array)$entities_ids as $entity_id) {
+            if (
+                (is_int($entity_id) || ctype_digit($entity_id))
+                && in_array((int)$entity_id, $active_entities_ids, true)
+            ) {
+                $filtered[] = (int)$entity_id;
+            }
+        }
+        return $filtered;
+    }
+
     /**
      * Get recursive state of active entity selection.
      *

programming_language: PHP
filename: User.php
change_type: ModificationType.MODIFY
diff: @@ -4617,6 +4617,7 @@ HTML;
         }
 
         $output = '';
+
         if (!($p['entity'] < 0) && $p['entity_sons']) {
             if (is_array($p['entity'])) {
                 $output .= "entity_sons options is not available with array of entity";
@@ -4624,6 +4625,7 @@ HTML;
                 $p['entity'] = getSonsOf('glpi_entities', $p['entity']);
             }
         }
+        $p['entity'] = Session::getMatchingActiveEntities($p['entity']);
 
         // Make a select box with all glpi users
         $view_users = self::canView();

programming_language: PHP
filename: Session.php
change_type: ModificationType.MODIFY
diff: @@ -630,4 +630,95 @@ class Session extends \DbTestCase
         \Session::loadLanguage('fr_FR');
         $this->string(\Session::getRightNameForError($module, $right))->isEqualTo($expected);
     }
+
+    protected function entitiesRestricProvider(): iterable
+    {
+        // Special case for -1
+        foreach ([-1, "-1", [-1], ["-1"]] as $value) {
+            yield [
+                'entity_restrict' => $value,
+                'active_entities' => [0, 1, 2, 3],
+                'result'          => is_array($value) ? [-1] : -1,
+            ];
+        }
+
+        // Integer input, matching
+        yield [
+            'entity_restrict' => 2,
+            'active_entities' => [0, 1, 2, 3],
+            'result'          => 2,
+        ];
+
+        // String input, matching
+        yield [
+            'entity_restrict' => '2',
+            'active_entities' => [0, 1, 2, 3],
+            'result'          => 2,
+        ];
+
+        // Integer input, NOT matching
+        yield [
+            'entity_restrict' => 7,
+            'active_entities' => [0, 1, 2, 3],
+            'result'          => [],
+        ];
+
+        // String input, matching
+        yield [
+            'entity_restrict' => '7',
+            'active_entities' => [0, 1, 2, 3],
+            'result'          => [],
+        ];
+
+        // Array input, ALL matching
+        yield [
+            'entity_restrict' => [0, '2', 3],
+            'active_entities' => [0, 1, 2, 3],
+            'result'          => [0, 2, 3],
+        ];
+
+        // Array input, PARTIAL matching
+        yield [
+            'entity_restrict' => [0, '2', 3, 12, 54, 96],
+            'active_entities' => [0, 1, 2, 3],
+            'result'          => [0, '2', 3],
+        ];
+
+        // Array input, NONE matching
+        yield [
+            'entity_restrict' => [0, '2', 3, 12, 54, 96],
+            'active_entities' => [1, 4],
+            'result'          => [],
+        ];
+
+        // Empty active entities
+        yield [
+            'entity_restrict' => [0, '2', 3, 12, 54, 96],
+            'active_entities' => null,
+            'result'          => [],
+        ];
+
+        // Unexpected unique value
+        yield [
+            'entity_restrict' => 'not a valid value',
+            'active_entities' => [0, 1, 2, 3],
+            'result'          => [],
+        ];
+
+        // Unexpected value in an array
+        yield [
+            'entity_restrict' => [0, 'not a valid value', 3],
+            'active_entities' => [0, 1, 2, 3],
+            'result'          => [0, 3],
+        ];
+    }
+
+    /**
+     * @dataProvider entitiesRestricProvider
+     */
+    public function testGetMatchingActiveEntities(/*int|array*/ $entity_restrict, ?array $active_entities, /*int|array*/ $result): void
+    {
+        $_SESSION['glpiactiveentities'] = $active_entities;
+        $this->variable(\Session::getMatchingActiveEntities($entity_restrict))->isEqualTo($result);
+    }
 }

programming_language: PHP