CVE-2023-46727: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: GLPI is a free asset and IT management software package. Starting in version 10.0.0 and prior to version 10.0.11, GLPI inventory endpoint can be used to drive a SQL injection attack. Version 10.0.11 contains a patch for the issue. As a workaround, disable native inventory.
CWE description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
CVSS v3 base score: 9.8
Published: 2023-12-13T19:15Z
Repository: https://github.com/glpi-project/glpi
Language: PHP
Files changed: src/RuleImportAsset.php, tests/functional/Glpi/Inventory/Assets/Bios.php, tests/functional/Glpi/Inventory/Inventory.php
Commit date: 2023-12-13 08:18:40 +0100
Commit message: Merge pull request from GHSA-v799-2mp3-wgfr

Add test cases
Diff: diff --git a/src/RuleImportAsset.php b/src/RuleImportAsset.php
index e2a946043a09..4857cc11631d 100644
--- a/src/RuleImportAsset.php
+++ b/src/RuleImportAsset.php
@@ -32,10 +32,12 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  *
  * ---------------------------------------------------------------------
  */
 
+use Glpi\Toolbox\Sanitizer;
+
 class RuleImportAsset extends Rule
 {
     const RULE_ACTION_LINK_OR_IMPORT    = 0;
     const RULE_ACTION_LINK_OR_NO_IMPORT = 1;
     const RULE_ACTION_DENIED            = 2;
@@ -516,11 +518,11 @@ class RuleImportAsset extends Rule
             } else {
                // 1 join per criterion
                 $this->handleOneJoinPerCriteria($item, $it_criteria);
             }
 
-            $this->handleFieldsCriteria($item, $it_criteria, $input);
+            $this->handleFieldsCriteria($item, $it_criteria, Sanitizer::sanitize($input));
 
             if (isset($PLUGIN_HOOKS['use_rules'])) {
                 foreach ($PLUGIN_HOOKS['use_rules'] as $plugin => $val) {
                     if (!Plugin::isPluginActive($plugin)) {
                         continue;
diff --git a/tests/functional/Glpi/Inventory/Assets/Bios.php b/tests/functional/Glpi/Inventory/Assets/Bios.php
index 0d29445b8d02..5e4f2739ebc9 100644
--- a/tests/functional/Glpi/Inventory/Assets/Bios.php
+++ b/tests/functional/Glpi/Inventory/Assets/Bios.php
@@ -142,11 +142,11 @@ class Bios extends AbstractInventoryAsset
   <CONTENT>
     <HARDWARE>
       <NAME>pc002</NAME>
     </HARDWARE>
     <BIOS>
-      <SSN>ggheb7ne7</SSN>
+      <SSN>ggheb7'ne7</SSN>
       <BMANUFACTURER>Dell Inc.</BMANUFACTURER>
       <BVERSION>1.4.3</BVERSION>
       <MMANUFACTURER>Dell Inc.</MMANUFACTURER>
       <MMODEL>07TYC2</MMODEL>
       <MSN>/640HP72/CN129636460078/</MSN>
@@ -173,11 +173,11 @@ class Bios extends AbstractInventoryAsset
         $this->integer($manufacturers_id)->isGreaterThan(0);
 
         //create manually a computer, with a bios
         $computers_id = $computer->add([
             'name'   => 'pc002',
-            'serial' => 'ggheb7ne7',
+            'serial' => addslashes("ggheb7'ne7"),
             'entities_id' => 0
         ]);
         $this->integer($computers_id)->isGreaterThan(0);
 
         $bios_id = $device_bios->add([
@@ -217,11 +217,11 @@ class Bios extends AbstractInventoryAsset
   <CONTENT>
     <HARDWARE>
       <NAME>pc002</NAME>
     </HARDWARE>
     <BIOS>
-      <SSN>ggheb7ne7</SSN>
+      <SSN>ggheb7'ne7</SSN>
       <BMANUFACTURER>Dell Inc.</BMANUFACTURER>
       <BVERSION>1.4.4</BVERSION>
       <MMANUFACTURER>Dell Inc.</MMANUFACTURER>
       <MMODEL>07TYC2</MMODEL>
       <MSN>/640HP72/CN129636460078/</MSN>
diff --git a/tests/functional/Glpi/Inventory/Inventory.php b/tests/functional/Glpi/Inventory/Inventory.php
index 51220e608b2d..2b96a6283ad4 100644
--- a/tests/functional/Glpi/Inventory/Inventory.php
+++ b/tests/functional/Glpi/Inventory/Inventory.php
@@ -4899,10 +4899,49 @@ Compiled Tue 28-Sep-10 13:44 by prod_rel_team",
         //ensure database version has been updated
         $database = new \DatabaseInstance();
         $this->boolean($database->getFromDBByCrit(['name' => 'MariaDB']))->isTrue();
         $this->string($database->fields['version'])->isIdenticalTo('Ver 15.1 Distrib 10.5.10-MariaDB-modified');
 
+        //- ensure existing instances has been updated
+        $databases = $database->getDatabases();
+        $this->array($databases)->hasSize(2);
+        $this->array(array_pop($databases))
+            ->string['name']->isIdenticalTo('new_database')
+            ->integer['size']->isIdenticalTo(2048);
+        $this->array(array_pop($databases))
+            ->string['name']->isIdenticalTo('glpi')
+            ->integer['size']->isIdenticalTo(55000);
+
+        //test sql syntax error
+        $json = json_decode(file_get_contents(self::INV_FIXTURES . 'computer_2_partial_dbs.json'));
+        $mysql = $json->content->databases_services[0];
+        //update version
+        $mysql->name = "Maria'DB";
+        $dbs = $mysql->databases;
+
+        $db_glpi = &$dbs[0];
+        $db_glpi->size = 55000;
+        $db_glpi->last_backup_date = '2021-06-25 08:52:44';
+
+        $db_new = &$dbs[1];
+        $db_new->name = 'new_database';
+        $db_new->size = 2048;
+
+        $services = [$mysql];
+        $json->content->databases_services = $services;
+
+        $this->doInventory($json);
+
+        //check created databases & instances
+        $this->integer(countElementsInTable(\DatabaseInstance::getTable(), ['is_deleted' => 0]))->isIdenticalTo(1);
+        $this->integer(countElementsInTable(\DatabaseInstance::getTable(), ['is_deleted' => 1]))->isIdenticalTo(2);
+
+        //ensure database version has been updated
+        $database = new \DatabaseInstance();
+        $this->boolean($database->getFromDBByCrit(['name' => 'MariaDB']))->isTrue();
+        $this->string($database->fields['version'])->isIdenticalTo('Ver 15.1 Distrib 10.5.10-MariaDB-modified');
+
         //- ensure existing instances has been updated
         $databases = $database->getDatabases();
         $this->array($databases)->hasSize(2);
         $this->array(array_pop($databases))
             ->string['name']->isIdenticalTo('new_database')

Vulnerable code:             $this->handleFieldsCriteria($item, $it_criteria, $input);
      <SSN>ggheb7ne7</SSN>
            'serial' => 'ggheb7ne7',
      <SSN>ggheb7ne7</SSN>
Fixed code: use Glpi\Toolbox\Sanitizer;

            $this->handleFieldsCriteria($item, $it_criteria, Sanitizer::sanitize($input));
      <SSN>ggheb7'ne7</SSN>
            'serial' => addslashes("ggheb7'ne7"),
      <SSN>ggheb7'ne7</SSN>
        //- ensure existing instances has been updated
        $databases = $database->getDatabases();
        $this->array($databases)->hasSize(2);
        $this->array(array_pop($databases))
            ->string['name']->isIdenticalTo('new_database')
            ->integer['size']->isIdenticalTo(2048);
        $this->array(array_pop($databases))
            ->string['name']->isIdenticalTo('glpi')
            ->integer['size']->isIdenticalTo(55000);

        //test sql syntax error
        $json = json_decode(file_get_contents(self::INV_FIXTURES . 'computer_2_partial_dbs.json'));
        $mysql = $json->content->databases_services[0];
        //update version
        $mysql->name = "Maria'DB";
        $dbs = $mysql->databases;

        $db_glpi = &$dbs[0];
        $db_glpi->size = 55000;
        $db_glpi->last_backup_date = '2021-06-25 08:52:44';

        $db_new = &$dbs[1];
        $db_new->name = 'new_database';
        $db_new->size = 2048;

        $services = [$mysql];
        $json->content->databases_services = $services;

        $this->doInventory($json);

        //check created databases & instances
        $this->integer(countElementsInTable(\DatabaseInstance::getTable(), ['is_deleted' => 0]))->isIdenticalTo(1);
        $this->integer(countElementsInTable(\DatabaseInstance::getTable(), ['is_deleted' => 1]))->isIdenticalTo(2);

        //ensure database version has been updated
        $database = new \DatabaseInstance();
        $this->boolean($database->getFromDBByCrit(['name' => 'MariaDB']))->isTrue();
        $this->string($database->fields['version'])->isIdenticalTo('Ver 15.1 Distrib 10.5.10-MariaDB-modified');