Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [UNRELEASED]

### Fixed

- Fix missing access and validation checks on several report and admin actions

## [1.10.0] - 2026-06-10

### Fixed
Expand Down
4 changes: 4 additions & 0 deletions front/preference.form.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
$pref = new PluginMreportingPreference();

if (isset($_POST['update'])) {
if (!$pref->getFromDB($_POST['id']) || $pref->fields['users_id'] != Session::getLoginUserID()) {
Html::back();
}
$_POST['users_id'] = Session::getLoginUserID();
$pref->update($_POST);
Html::back();
} else {
Expand Down
2 changes: 1 addition & 1 deletion front/profile.form.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* -------------------------------------------------------------------------
*/

Session::checkRight('profile', READ);
Session::checkRight('profile', UPDATE);

/** @var DBmysql $DB */
global $DB;
Expand Down
4 changes: 2 additions & 2 deletions hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,8 @@ function plugin_mreporting_giveItem($type, $ID, $data, $num)
}
}
}
$out = "<a href='config.form.php?id=" . $data['id'] . "'>" .
$data['raw']["ITEM_$num"] . '</a> (' . $title_func . ')';
$out = "<a href='config.form.php?id=" . htmlspecialchars((string) $data['id'], ENT_QUOTES) . "'>" .
htmlspecialchars($data['raw']["ITEM_$num"], ENT_QUOTES) . '</a> (' . $title_func . ')';
}

return $out;
Expand Down
23 changes: 23 additions & 0 deletions inc/common.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,24 @@ public static function initGraphParams(array $params): array
return $crit;
}

/**
* Reject the request unless the report is active and the current profile
* has read access to it (mirrors the check used to build the reports menu,
* but enforced here on the actual data-returning endpoints).
*/
private static function checkReportAccess($f_name, $classname)
{
$config = new PluginMreportingConfig();
if (
!$config->getFromDBByFunctionAndClassname($f_name, $classname)
|| !$config->fields['is_active']
|| !isset($_SESSION['glpiactiveprofile']['id'])
|| !PluginMreportingProfile::canViewReports($_SESSION['glpiactiveprofile']['id'], $config->fields['id'])
) {
throw new NotFoundHttpException();
}
}

/**
* show Graph : Show graph
*
Expand Down Expand Up @@ -671,6 +689,7 @@ public function showGraph($opt, $export = false, $forceFormat = null)
if (!is_a($classname, PluginMreportingBaseclass::class, true)) {
return false;
}
self::checkReportAccess($opt['f_name'], $classname);
$obj = new $classname($config);

$datas = $obj->{$opt['f_name']}($config);
Expand Down Expand Up @@ -1115,6 +1134,9 @@ public function export($opt)
foreach ($report['functions'] as $func) {
foreach ($opt['check'] as $do => $to) {
if ($do == $func['function'] . $classname) {
if (!$func['is_active'] || !$func['right']) {
continue;
}
//dynamic instanciation of class passed by 'short_classname' GET parameter
$config = PluginMreportingConfig::initConfigParams($func['function'], $classname);
$class = 'PluginMreporting' . $func['short_classname'];
Expand Down Expand Up @@ -1206,6 +1228,7 @@ public function export($opt)
if (!is_a($classname, PluginMreportingBaseclass::class, true)) {
return false;
}
self::checkReportAccess($opt['f_name'], $classname);
$obj = new $classname($config);

//dynamic call of method passed by 'f_name' GET parameter with previously instancied class
Expand Down
3 changes: 3 additions & 0 deletions inc/dashboard.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,9 @@ public function getFormForColumn()
public static function removeReportFromDashboard($id)
{
$report = new PluginMreportingDashboard();
if (!$report->getFromDB($id) || $report->fields['users_id'] != Session::getLoginUserID()) {
return false;
}

return $report->delete(['id' => $id]);
}
Expand Down
21 changes: 16 additions & 5 deletions inc/graphcsv.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ public function initGraph($options)
}
}

/**
* Prefix a leading =, +, - or @ so spreadsheet software doesn't
* interpret the field as a formula.
*/
private static function escapeCsvField($value)
{
$value = (string) $value;

return preg_match('/^[=+\-@]/', $value) ? "'" . $value : $value;
}

public function showHbar($params, $dashboard = false, $width = false)
{
/** @var array $CFG_GLPI */
Expand Down Expand Up @@ -91,7 +102,7 @@ public function showHbar($params, $dashboard = false, $width = false)
//titles
$out = $title . ' - ' . $desc . "\r\n";
foreach ($labels as $label) {
$out .= $label . $CFG_GLPI['csv_delimiter'];
$out .= self::escapeCsvField($label) . $CFG_GLPI['csv_delimiter'];
}
$out = substr($out, 0, -1) . "\r\n";

Expand All @@ -101,7 +112,7 @@ public function showHbar($params, $dashboard = false, $width = false)
}
$out = substr($out, 0, -1) . "\r\n";

echo htmlspecialchars($out);
echo $out;
}

public function showPie($params, $dashboard = false, $width = false)
Expand Down Expand Up @@ -158,7 +169,7 @@ public function showHgbar($params, $dashboard = false, $width = false)

foreach ($datas as $label2 => $cols) {
//title
$out .= $label2 . "\r\n";
$out .= self::escapeCsvField($label2) . "\r\n";

//subtitle
$i = 0;
Expand All @@ -167,7 +178,7 @@ public function showHgbar($params, $dashboard = false, $width = false)
if (isset($labels2[$i])) {
$label = str_replace(',', '-', $labels2[$i]);
}
$out .= $label . $CFG_GLPI['csv_delimiter'];
$out .= self::escapeCsvField($label) . $CFG_GLPI['csv_delimiter'];
$i++;
}
$out = substr($out, 0, -1) . "\r\n";
Expand All @@ -180,7 +191,7 @@ public function showHgbar($params, $dashboard = false, $width = false)
}
$out = substr($out, 0, -1) . "\r\n";

echo htmlspecialchars($out);
echo $out;
}

public function showVstackbar($params, $dashboard = false, $width = false)
Expand Down
4 changes: 2 additions & 2 deletions inc/helpdesk.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,7 @@ public function reportHbarTicketNumberByLocation($config = [])
$delay = PluginMreportingCommon::getCriteriaDate('glpi_tickets.date', $config['delay'], $config['randname']);

$datas = [];
$limit = $_REQUEST['glpilist_limit'] ?? 20;
$limit = (int) ($_REQUEST['glpilist_limit'] ?? 20);

$query = [
"SELECT" => [
Expand Down Expand Up @@ -1034,7 +1034,7 @@ public function reportHbarTicketNumberByLocation($config = [])
],
'GROUPBY' => [Location::getTable() . '.name'],
'ORDER' => ['count DESC'],
'LIMIT' => '0, ' . $limit,
'LIMIT' => $limit,
];
$query['WHERE']['AND'] = $delay;

Expand Down
6 changes: 3 additions & 3 deletions inc/helpdeskplus.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ public function reportHbarTopcategory($config = [])

//Init delay value
$delay = PluginMreportingCommon::getCriteriaDate('glpi_tickets.date', $config['delay'], $config['randname']);
$limit = $_SESSION['mreporting_values']['glpilist_limit'] ?? 20;
$limit = (int) ($_SESSION['mreporting_values']['glpilist_limit'] ?? 20);

$query = [
"SELECT" => [
Expand Down Expand Up @@ -718,7 +718,7 @@ public function reportHbarTopapplicant($config = [])

//Init delay value
$delay = PluginMreportingCommon::getCriteriaDate('glpi_tickets.date', $config['delay'], $config['randname']);
$limit = $_SESSION['mreporting_values']['glpilist_limit'] ?? 20;
$limit = (int) ($_SESSION['mreporting_values']['glpilist_limit'] ?? 20);

$query = [
"SELECT" => [
Expand Down Expand Up @@ -1009,7 +1009,7 @@ public function reportHgbarRespectedSlasByTopCategory($config = [])

$category = isset($_POST['categories']) && $_POST['categories'] > 0 ? $_POST['categories'] : false;

$category_limit = $_POST['glpilist_limit'] ?? 10;
$category_limit = (int) ($_POST['glpilist_limit'] ?? 10);

$_SESSION['glpilist_limit'] = $category_limit;

Expand Down
3 changes: 3 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@
<TaintedTextWithQuotes>
<errorLevel type="suppress">
<file name="inc/graph.class.php" />
<!-- CSV response, not HTML: formula-injection is mitigated in escapeCsvField() instead -->
<file name="inc/graphcsv.class.php" />
</errorLevel>
</TaintedTextWithQuotes>
<TaintedHtml>
<errorLevel type="suppress">
<file name="inc/graph.class.php" />
<file name="inc/graphcsv.class.php" />
</errorLevel>
</TaintedHtml>
<TaintedCallable errorLevel="suppress" />
Expand Down
6 changes: 4 additions & 2 deletions setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ function plugin_init_mreporting()
if (Plugin::isPluginActive('mreporting')) {
// *Direct* access to rapport file (from e-mail) :
if (isset($_GET['redirect']) && str_contains($_GET['redirect'], 'plugin_mreporting')) {
$filename = str_replace('plugin_mreporting_', '', $_GET['redirect']);
Html::redirect($CFG_GLPI['root_doc'] . '/files/_plugins/mreporting/notifications/' . $filename);
$filename = basename(str_replace('plugin_mreporting_', '', $_GET['redirect']));
if (preg_match('/^[\w\-.]+$/', $filename)) {
Html::redirect($CFG_GLPI['root_doc'] . '/files/_plugins/mreporting/notifications/' . $filename);
}
}

//Load additionnal language files in needed
Expand Down