CVE-2024-23645: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: GLPI is a Free Asset and IT Management Software package. A malicious URL can be used to execute XSS on reports pages.  Upgrade to 10.0.12.

CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2024-02-01T18:15Z
Repository: https://github.com/glpi-project/glpi
Language: PHP
Files changed: front/stat.global.php, front/stat.graph.php, front/stat.location.php, front/stat.tracking.php, src/Stat.php
Commit date: 2024-02-01 09:15:01 +0100
Commit message: Sanitize parameters and encode special chars

Diff: diff --git a/front/stat.global.php b/front/stat.global.php
index 98e892dd835f..b3eb849b9665 100644
--- a/front/stat.global.php
+++ b/front/stat.global.php
@@ -42,10 +42,16 @@ include('../inc/includes.php');
 
 Html::header(__('Statistics'), $_SERVER['PHP_SELF'], "helpdesk", "stat");
 
 Session::checkRight("statistic", READ);
 
+//sanitize dates
+foreach (['date1', 'date2'] as $key) {
+    if (array_key_exists($key, $_GET) && preg_match('/\d{4}-\d{2}-\d{2}/', (string)$_GET[$key]) !== 1) {
+        unset($_GET[$key]);
+    }
+}
 if (empty($_GET["date1"]) && empty($_GET["date2"])) {
     $year          = date("Y") - 1;
     $_GET["date1"] = date("Y-m-d", mktime(1, 0, 0, (int)date("m"), (int)date("d"), $year));
     $_GET["date2"] = date("Y-m-d");
 }
diff --git a/front/stat.graph.php b/front/stat.graph.php
index 1aab80733438..b54411587978 100644
--- a/front/stat.graph.php
+++ b/front/stat.graph.php
@@ -52,10 +52,19 @@ Session::checkRight("statistic", READ);
 
 if (!$item = getItemForItemtype($_GET['itemtype'])) {
     exit;
 }
 
+//sanitize dates
+foreach (['date1', 'date2'] as $key) {
+    if (array_key_exists($key, $_GET) && preg_match('/\d{4}-\d{2}-\d{2}/', (string)$_GET[$key]) !== 1) {
+        unset($_GET[$key]);
+    }
+    if (array_key_exists($key, $_POST) && preg_match('/\d{4}-\d{2}-\d{2}/', (string)$_POST[$key]) !== 1) {
+        unset($_POST[$key]);
+    }
+}
 if (empty($_POST["date1"]) && empty($_POST["date2"])) {
     if (isset($_GET["date1"])) {
         $_POST["date1"] = $_GET["date1"];
     }
     if (isset($_GET["date2"])) {
@@ -382,11 +391,11 @@ $target = preg_replace("/&/", "&amp;", $_SERVER["REQUEST_URI"]);
 echo "<form method='post' name='form' action='$target'><div class='center'>";
 echo "<table class='tab_cadre'>";
 echo "<tr class='tab_bg_2'><td class='right'>" . __('Start date') . "</td><td>";
 Html::showDateField("date1", ['value' => $_POST["date1"]]);
 echo "</td><td rowspan='2' class='center'>";
-echo "<input type='hidden' name='itemtype' value=\"" . $_GET['itemtype'] . "\">";
+echo "<input type='hidden' name='itemtype' value=\"" . htmlspecialchars($_GET['itemtype']) . "\">";
 echo "<input type='submit' class='btn btn-primary' value=\"" . __s('Display report') . "\"></td></tr>";
 
 echo "<tr class='tab_bg_2'><td class='right'>" . __('End date') . "</td><td>";
 Html::showDateField("date2", ['value' => $_POST["date2"]]);
 echo "</td></tr>";
diff --git a/front/stat.location.php b/front/stat.location.php
index 9baedc5691fa..d3e9eab1edd2 100644
--- a/front/stat.location.php
+++ b/front/stat.location.php
@@ -49,12 +49,20 @@ Html::header(__('Statistics'), '', "helpdesk", "stat");
 Session::checkRight("statistic", READ);
 
 
 if (empty($_GET["showgraph"])) {
     $_GET["showgraph"] = 0;
+} else {
+    $_GET["showgraph"] = (int)$_GET["showgraph"];
 }
 
+//sanitize dates
+foreach (['date1', 'date2'] as $key) {
+    if (array_key_exists($key, $_GET) && preg_match('/\d{4}-\d{2}-\d{2}/', (string)$_GET[$key]) !== 1) {
+        unset($_GET[$key]);
+    }
+}
 if (empty($_GET["date1"]) && empty($_GET["date2"])) {
     $year          = date("Y") - 1;
     $_GET["date1"] = date("Y-m-d", mktime(1, 0, 0, date("m"), date("d"), $year));
     $_GET["date2"] = date("Y-m-d");
 }
@@ -69,30 +77,32 @@ if (
     $_GET["date2"] = $tmp;
 }
 
 if (!isset($_GET["start"])) {
     $_GET["start"] = 0;
-}
-// Why this test ?? For me it's doing nothing
-if (isset($_GET["dropdown"])) {
-    $_GET["dropdown"] = $_GET["dropdown"];
+} else {
+    $_GET["start"] = (int)$_GET["start"];
 }
 
 if (empty($_GET["dropdown"])) {
     $_GET["dropdown"] = "ComputerType";
+} else {
+    $_GET["dropdown"] = (string)$_GET["dropdown"];
 }
 
 if (!isset($_GET['itemtype'])) {
     $_GET['itemtype'] = 'Ticket';
+} else {
+    $_GET['itemtype'] = (string)$_GET["itemtype"];
 }
 
 $stat = new Stat();
 Stat::title();
 
 echo "<form method='get' name='form' action='stat.location.php'>";
 // keep it first param
-echo "<input type='hidden' name='itemtype' value='" . $_GET['itemtype'] . "'>";
+echo "<input type='hidden' name='itemtype' value=\"" . htmlspecialchars($_GET['itemtype']) . "\">";
 
 echo "<table class='tab_cadre_fixe' ><tr class='tab_bg_2'><td rowspan='2' width='30%'>";
 $values = [_n('Dropdown', 'Dropdowns', Session::getPluralNumber()) => ['ComputerType'    => _n('Type', 'Types', 1),
     'ComputerModel'   => _n('Model', 'Models', 1),
     'OperatingSystem' => OperatingSystem::getTypeName(1),
diff --git a/front/stat.tracking.php b/front/stat.tracking.php
index bff215c1c899..e8adbccc1738 100644
--- a/front/stat.tracking.php
+++ b/front/stat.tracking.php
@@ -52,22 +52,32 @@ if (!$item = getItemForItemtype($_GET['itemtype'])) {
     exit;
 }
 
 if (empty($_GET["type"])) {
     $_GET["type"] = "user";
+} else {
+    $_GET["type"] = (string)$_GET["type"];
 }
 
 if (empty($_GET["showgraph"])) {
     $_GET["showgraph"] = 0;
+} else {
+    $_GET["showgraph"] = (int)$_GET["showgraph"];
 }
 
 if (empty($_GET["value2"])) {
     $_GET["value2"] = 0;
 }
 
+//sanitize dates
+foreach (['date1', 'date2'] as $key) {
+    if (array_key_exists($key, $_GET) && preg_match('/\d{4}-\d{2}-\d{2}/', (string)$_GET[$key]) !== 1) {
+        unset($_GET[$key]);
+    }
+}
 if (empty($_GET["date1"]) && empty($_GET["date2"])) {
-    $year              = date("Y") - 1;
+    $year          = date("Y") - 1;
     $_GET["date1"] = date("Y-m-d", mktime(1, 0, 0, date("m"), date("d"), $year));
     $_GET["date2"] = date("Y-m-d");
 }
 
 if (
@@ -80,10 +90,12 @@ if (
     $_GET["date2"] = $tmp;
 }
 
 if (!isset($_GET["start"])) {
     $_GET["start"] = 0;
+} else {
+    $_GET["start"] = (int)$_GET["start"];
 }
 
 $stat = new Stat();
 Stat::title();
 
@@ -133,11 +145,11 @@ foreach ($items as $label => $tab) {
     }
 }
 
 echo "<div class='center'><form method='get' name='form' action='stat.tracking.php'>";
 // Keep it first param
-echo "<input type='hidden' name='itemtype' value=\"" . $_GET["itemtype"] . "\">";
+echo "<input type='hidden' name='itemtype' value=\"" . htmlspecialchars($_GET["itemtype"]) . "\">";
 
 echo "<table class='tab_cadre_fixe'>";
 echo "<tr class='tab_bg_2'><td rowspan='2' class='center' width='30%'>";
 Dropdown::showFromArray('type', $values, ['value' => $_GET['type']]);
 echo "</td>";
diff --git a/src/Stat.php b/src/Stat.php
index edb2446dface..ad9e7649ed81 100644
--- a/src/Stat.php
+++ b/src/Stat.php
@@ -2152,11 +2152,11 @@ class Stat extends CommonGLPI
      */
     public function displaySearchForm($itemtype, $date1, $date2, $display = true)
     {
         $out = "<form method='get' name='form' action='stat.global.php'><div class='center'>";
        // Keep it at first parameter
-        $out .= "<input type='hidden' name='itemtype' value='$itemtype'>";
+        $out .= "<input type='hidden' name='itemtype' value=\"" . htmlspecialchars($itemtype) . "\">";
 
         $out .= "<table class='tab_cadre'>";
         $out .= "<tr class='tab_bg_2'><td class='right'>" . __('Start date') . "</td><td>";
         $out .= Html::showDateField(
             'date1',

Vulnerable code: echo "<input type='hidden' name='itemtype' value=\"" . $_GET['itemtype'] . "\">";
}
// Why this test ?? For me it's doing nothing
if (isset($_GET["dropdown"])) {
    $_GET["dropdown"] = $_GET["dropdown"];
echo "<input type='hidden' name='itemtype' value='" . $_GET['itemtype'] . "'>";
    $year              = date("Y") - 1;
echo "<input type='hidden' name='itemtype' value=\"" . $_GET["itemtype"] . "\">";
        $out .= "<input type='hidden' name='itemtype' value='$itemtype'>";
Fixed code: //sanitize dates
foreach (['date1', 'date2'] as $key) {
    if (array_key_exists($key, $_GET) && preg_match('/\d{4}-\d{2}-\d{2}/', (string)$_GET[$key]) !== 1) {
        unset($_GET[$key]);
    }
}
//sanitize dates
foreach (['date1', 'date2'] as $key) {
    if (array_key_exists($key, $_GET) && preg_match('/\d{4}-\d{2}-\d{2}/', (string)$_GET[$key]) !== 1) {
        unset($_GET[$key]);
    }
    if (array_key_exists($key, $_POST) && preg_match('/\d{4}-\d{2}-\d{2}/', (string)$_POST[$key]) !== 1) {
        unset($_POST[$key]);
    }
}
echo "<input type='hidden' name='itemtype' value=\"" . htmlspecialchars($_GET['itemtype']) . "\">";
} else {
    $_GET["showgraph"] = (int)$_GET["showgraph"];
//sanitize dates
foreach (['date1', 'date2'] as $key) {
    if (array_key_exists($key, $_GET) && preg_match('/\d{4}-\d{2}-\d{2}/', (string)$_GET[$key]) !== 1) {
        unset($_GET[$key]);
    }
}
} else {
    $_GET["start"] = (int)$_GET["start"];
} else {
    $_GET["dropdown"] = (string)$_GET["dropdown"];
} else {
    $_GET['itemtype'] = (string)$_GET["itemtype"];
echo "<input type='hidden' name='itemtype' value=\"" . htmlspecialchars($_GET['itemtype']) . "\">";
} else {
    $_GET["type"] = (string)$_GET["type"];
} else {
    $_GET["showgraph"] = (int)$_GET["showgraph"];
//sanitize dates
foreach (['date1', 'date2'] as $key) {
    if (array_key_exists($key, $_GET) && preg_match('/\d{4}-\d{2}-\d{2}/', (string)$_GET[$key]) !== 1) {
        unset($_GET[$key]);
    }
}
    $year          = date("Y") - 1;
} else {
    $_GET["start"] = (int)$_GET["start"];
echo "<input type='hidden' name='itemtype' value=\"" . htmlspecialchars($_GET["itemtype"]) . "\">";
        $out .= "<input type='hidden' name='itemtype' value=\"" . htmlspecialchars($itemtype) . "\">";