Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

--- develop ---

* security: Escape threshold web output and harden RLIKE filters
* issue#686: Applying a templated threshold to a graph via the wrench icon, creates a duplicate graph
* issue#707: Excessive timeout for row caching prevents data from being updated timely
* issue#710: Fixing Typo in thold_daemons.service File
Expand Down
45 changes: 45 additions & 0 deletions tests/Unit/TholdRlikeClauseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU General Public License |
| as published by the Free Software Foundation; either version 2 |
| of the License, or (at your option) any later version. |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
| http://www.cacti.net/ |
+-------------------------------------------------------------------------+
*/

final class TholdRlikeClauseTest extends TestCase {
/**
* @return void
*/
public static function setUpBeforeClass(): void {
self::loadPluginSource('thold_functions.php');
}

/**
* @return void
*/
public function testEmitsTheOperatorAndAQuotedOperand(): void {
$this->assertSame("RLIKE 'router'", thold_rlike_clause('router'));
}

/**
* @return void
*/
public function testQuotesInTheFilterAreEscaped(): void {
$this->assertSame("RLIKE ''' OR 1=1 -- '", thold_rlike_clause("' OR 1=1 -- "));
}

/**
* @return void
*/
public function testCoreHelperIsPreferredWhenAvailable(): void {
$this->assertSame(db_qstr_rlike('x'), thold_rlike_clause('x'));
}
}
16 changes: 8 additions & 8 deletions thold.php
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ function list_tholds() {
}

if (get_request_var('rfilter') != '') {
$sql_where .= ($sql_where == '' ? '(' : ' AND ') . " td.name_cache RLIKE '" . get_request_var('rfilter') . "'";
$sql_where .= ($sql_where == '' ? '(' : ' AND ') . ' td.name_cache ' . thold_rlike_clause(get_request_var('rfilter'));
}
Comment thread
somethingwithproof marked this conversation as resolved.

if ($statefilter != '') {
Expand Down Expand Up @@ -763,18 +763,18 @@ function list_tholds() {
</tr>
</table>
<input type='hidden' name='search' value='search'>
<input type='hidden' id='page' value='<?php print get_filter_request_var('page'); ?>'>
<input type='hidden' id='page' value='<?php print html_escape(get_filter_request_var('page')); ?>'>
</form>
<script type='text/javascript'>

function applyFilter() {
strURL = 'thold.php?header=false&host_id=' + $('#host_id').val();
strURL += '&state=' + $('#state').val();
strURL += '&thold_template_id=' + $('#thold_template_id').val();
strURL += '&data_template_id=' + $('#data_template_id').val();
strURL += '&site_id=' + $('#site_id').val();
strURL += '&rows=' + $('#rows').val();
strURL += '&rfilter=' + base64_encode($('#rfilter').val());
strURL += '&state=' + encodeURIComponent($('#state').val());
strURL += '&thold_template_id=' + encodeURIComponent($('#thold_template_id').val());
strURL += '&data_template_id=' + encodeURIComponent($('#data_template_id').val());
strURL += '&site_id=' + encodeURIComponent($('#site_id').val());
strURL += '&rows=' + encodeURIComponent($('#rows').val());
strURL += '&rfilter=' + encodeURIComponent(base64_encode($('#rfilter').val()));
loadPageNoHeader(strURL);
}

Expand Down
16 changes: 16 additions & 0 deletions thold_functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -8401,6 +8401,22 @@ function thold_get_cached_name(&$thold_data) {
return $thold_data['name_cache'];
}

/**
* Quote a value for an RLIKE comparison using the hardened core helper when
* available while retaining compatibility with older supported Cacti builds.
*
* @param string $value Raw filter value.
*
* @return string RLIKE operator and quoted operand.
*/
function thold_rlike_clause($value) {
if (function_exists('db_qstr_rlike')) {
return db_qstr_rlike($value);
}

return 'RLIKE ' . db_qstr($value);
}

/**
* Substitute one tag, rendering an absent value as an empty string.
*
Expand Down
66 changes: 33 additions & 33 deletions thold_graph.php
Original file line number Diff line number Diff line change
Expand Up @@ -251,20 +251,20 @@ function form_thold_filter() {
</td>
</tr>
</table>
<input type='hidden' id='page' value='<?php print get_request_var('page'); ?>'>
<input type='hidden' id='page' value='<?php print html_escape(get_request_var('page')); ?>'>
<input type='hidden' id='tab' value='thold'>
</form>
<script type='text/javascript'>

function applyFilter() {
strURL = 'thold_graph.php?header=false&action=thold';
strURL += '&state=' + $('#state').val();
strURL += '&thold_template_id=' + $('#thold_template_id').val();
strURL += '&data_template_id=' + $('#data_template_id').val();
strURL += '&host_id=' + $('#host_id').val();
strURL += '&site_id=' + $('#site_id').val();
strURL += '&rows=' + $('#rows').val();
strURL += '&rfilter=' + base64_encode($('#rfilter').val());
strURL += '&state=' + encodeURIComponent($('#state').val());
strURL += '&thold_template_id=' + encodeURIComponent($('#thold_template_id').val());
strURL += '&data_template_id=' + encodeURIComponent($('#data_template_id').val());
strURL += '&host_id=' + encodeURIComponent($('#host_id').val());
strURL += '&site_id=' + encodeURIComponent($('#site_id').val());
strURL += '&rows=' + encodeURIComponent($('#rows').val());
strURL += '&rfilter=' + encodeURIComponent(base64_encode($('#rfilter').val()));
loadPageNoHeader(strURL);
}

Expand Down Expand Up @@ -404,7 +404,7 @@ function tholds() {
$statefilter = thold_get_state_filter(get_request_var('state'));

if (get_request_var('rfilter') != '') {
$sql_where .= ($sql_where == '' ? '(' : ' AND ') . " td.name_cache RLIKE '" . get_request_var('rfilter') . "'";
$sql_where .= ($sql_where == '' ? '(' : ' AND ') . 'td.name_cache ' . thold_rlike_clause(get_request_var('rfilter'));
}
Comment thread
somethingwithproof marked this conversation as resolved.

if (get_request_var('data_template_id') != '-1') {
Expand Down Expand Up @@ -937,8 +937,8 @@ function hosts() {

if (get_request_var('rfilter') != '') {
$sql_where .= " (h.deleted = ''
AND (h.hostname RLIKE '" . get_request_var('rfilter') . "'
OR h.description RLIKE '" . get_request_var('rfilter') . "')";
AND (h.hostname " . thold_rlike_clause(get_request_var('rfilter')) . '
OR h.description ' . thold_rlike_clause(get_request_var('rfilter')) . ')';
}

if (get_request_var('host_status') == '-1') {
Expand Down Expand Up @@ -1261,18 +1261,18 @@ function form_host_filter() {
</td>
</tr>
</table>
<input type='hidden' name='page' value='<?php print get_request_var('page'); ?>'>
<input type='hidden' name='page' value='<?php print html_escape(get_request_var('page')); ?>'>
<input type='hidden' name='tab' value='hoststat'>
</form>
<script type='text/javascript'>

function applyFilter() {
strURL = 'thold_graph.php?header=false&action=hoststat';
strURL += '&host_status=' + $('#host_status').val();
strURL += '&host_template_id=' + $('#host_template_id').val();
strURL += '&site_id=' + $('#site_id').val();
strURL += '&rows=' + $('#rows').val();
strURL += '&rfilter=' + base64_encode($('#rfilter').val());
strURL += '&host_status=' + encodeURIComponent($('#host_status').val());
strURL += '&host_template_id=' + encodeURIComponent($('#host_template_id').val());
strURL += '&site_id=' + encodeURIComponent($('#site_id').val());
strURL += '&rows=' + encodeURIComponent($('#rows').val());
strURL += '&rfilter=' + encodeURIComponent(base64_encode($('#rfilter').val()));
loadPageNoHeader(strURL);
}

Expand Down Expand Up @@ -1395,7 +1395,7 @@ function thold_export_log() {
}

if (get_request_var('rfilter') != '') {
$sql_where .= ($sql_where == '' ? '' : ' AND') . " tl.description RLIKE '" . get_request_var('rfilter') . "'";
$sql_where .= ($sql_where == '' ? '' : ' AND') . ' tl.description ' . thold_rlike_clause(get_request_var('rfilter'));
}
Comment thread
somethingwithproof marked this conversation as resolved.

$sql_order = '';
Expand Down Expand Up @@ -1490,7 +1490,7 @@ function thold_show_log() {
}

if (get_request_var('rfilter') != '') {
$sql_where .= ($sql_where == '' ? '' : ' AND') . " tl.description RLIKE '" . get_request_var('rfilter') . "'";
$sql_where .= ($sql_where == '' ? '' : ' AND') . ' tl.description ' . thold_rlike_clause(get_request_var('rfilter'));
}

$sql_order = get_order_string();
Expand Down Expand Up @@ -1734,13 +1734,13 @@ function form_thold_log_filter() {

function applyFilter() {
strURL = 'thold_graph.php?header=false&action=log';
strURL += '&status=' + $('#status').val();
strURL += '&threshold_id=' + $('#threshold_id').val();
strURL += '&thold_template_id=' + $('#thold_template_id').val();
strURL += '&host_id=' + $('#host_id').val();
strURL += '&site_id=' + $('#site_id').val();
strURL += '&rows=' + $('#rows').val();
strURL += '&rfilter=' + base64_encode($('#rfilter').val());
strURL += '&status=' + encodeURIComponent($('#status').val());
strURL += '&threshold_id=' + encodeURIComponent($('#threshold_id').val());
strURL += '&thold_template_id=' + encodeURIComponent($('#thold_template_id').val());
strURL += '&host_id=' + encodeURIComponent($('#host_id').val());
strURL += '&site_id=' + encodeURIComponent($('#site_id').val());
strURL += '&rows=' + encodeURIComponent($('#rows').val());
strURL += '&rfilter=' + encodeURIComponent(base64_encode($('#rfilter').val()));
loadPageNoHeader(strURL);
}

Expand All @@ -1751,13 +1751,13 @@ function clearFilter() {

function exportLog() {
strURL = 'thold_graph.php?action=exportlog';
strURL += '&status=' + $('#status').val();
strURL += '&threshold_id=' + $('#threshold_id').val();
strURL += '&thold_template_id=' + $('#thold_template_id').val();
strURL += '&host_id=' + $('#host_id').val();
strURL += '&site_id=' + $('#site_id').val();
strURL += '&rows=' + $('#rows').val();
strURL += '&rfilter=' + base64_encode($('#rfilter').val());
strURL += '&status=' + encodeURIComponent($('#status').val());
strURL += '&threshold_id=' + encodeURIComponent($('#threshold_id').val());
strURL += '&thold_template_id=' + encodeURIComponent($('#thold_template_id').val());
strURL += '&host_id=' + encodeURIComponent($('#host_id').val());
strURL += '&site_id=' + encodeURIComponent($('#site_id').val());
strURL += '&rows=' + encodeURIComponent($('#rows').val());
strURL += '&rfilter=' + encodeURIComponent(base64_encode($('#rfilter').val()));
document.location = strURL;
Pace.stop();
}
Expand Down
2 changes: 1 addition & 1 deletion thold_templates.php
Original file line number Diff line number Diff line change
Expand Up @@ -2186,7 +2186,7 @@ function templates() {

function applyFilter() {
strURL = 'thold_templates.php?header=false&rows=' + $('#rows').val();
strURL += '&filter=' + $('#filter').val();
strURL += '&filter=' + encodeURIComponent($('#filter').val());
loadPageNoHeader(strURL);
}

Expand Down
50 changes: 40 additions & 10 deletions thold_webapi.php
Original file line number Diff line number Diff line change
Expand Up @@ -793,39 +793,39 @@ function thold_wizard() {

function applyTholdFilter() {
strURL = 'thold.php?action=add&header=false';
strURL += '&type_id=' + $('#type_id').val();
strURL += '&type_id=' + encodeURIComponent($('#type_id').val());

if ($('#type_id').val() == 'thold') {
if ($('#my_host_id').length && $('#my_host_id').val() > 0) {
strURL += '&my_host_id=' + $('#my_host_id').val();
strURL += '&my_host_id=' + encodeURIComponent($('#my_host_id').val());
}

if ($('#local_graph_id').length && $('#local_graph_id').val() > 0) {
strURL += '&local_graph_id=' + $('#local_graph_id').val();
strURL += '&local_graph_id=' + encodeURIComponent($('#local_graph_id').val());
}

if ($('#data_template_rrd_id').length && $('#data_template_rrd_id').val() > 0) {
strURL += '&data_template_rrd_id=' + $('#data_template_rrd_id').val();
strURL += '&data_template_rrd_id=' + encodeURIComponent($('#data_template_rrd_id').val());
}
} else {
if ($('#thold_template_id').length && $('#thold_template_id').val() > 0) {
strURL += '&thold_template_id=' + $('#thold_template_id').val();
strURL += '&thold_template_id=' + encodeURIComponent($('#thold_template_id').val());
}

if ($('#graph_template_id').length && $('#graph_template_id').val() > 0) {
strURL += '&graph_template_id=' + $('#graph_template_id').val();
strURL += '&graph_template_id=' + encodeURIComponent($('#graph_template_id').val());
}

if ($('#data_query_id').length && $('#data_query_id').val() > 0) {
strURL += '&data_query_id=' + $('#data_query_id').val();
strURL += '&data_query_id=' + encodeURIComponent($('#data_query_id').val());
}

if ($('#data_template_id').length && $('#data_template_id').val() > 0) {
strURL += '&data_template_id=' + $('#data_template_id').val();
strURL += '&data_template_id=' + encodeURIComponent($('#data_template_id').val());
}

if ($('#my_host_id').length && $('#my_host_id').val() != 0) {
strURL += '&my_host_id=' + $('#my_host_id').val();
strURL += '&my_host_id=' + encodeURIComponent($('#my_host_id').val());
}

if ($('#snmp_index').length && $('#snmp_index').val() != '') {
Expand Down Expand Up @@ -861,7 +861,37 @@ function applyTholdFilter() {
function thold_new_graphs_save($host_id) {
$return_array = false;

$selected_graphs_array = cacti_unserialize(stripslashes(get_nfilter_request_var('selected_graphs_array')));
// Nested wizard structure (cg/sg), not a flat ID list. Flat
// sanitize_unserialize_selected_items() rejects nested arrays and would
// break graph creation. Prefer the graphs-specific sanitizer when present
// (Cacti develop+); otherwise unserialize with allowed_classes => false.
$raw = get_nfilter_request_var('selected_graphs_array');

if (function_exists('sanitize_unserialize_selected_graphs')) {
$selected_graphs_array = sanitize_unserialize_selected_graphs($raw);
} elseif (function_exists('cacti_unserialize') && is_string($raw) && $raw !== '') {
$selected_graphs_array = cacti_unserialize(stripslashes($raw));
} elseif (is_string($raw) && $raw !== '') {
$unstripped = stripslashes($raw);

if (preg_match('/^a:[0-9]+:{/', $unstripped) && !preg_match('/(^|;|{|})O:\+?[0-9]+:"/', $unstripped)) {
$selected_graphs_array = unserialize($unstripped, ['allowed_classes' => false]);
} else {
$selected_graphs_array = false;
}
} else {
$selected_graphs_array = false;
}

if (!is_array($selected_graphs_array)) {
return false;
}

foreach ($selected_graphs_array as $form_type => $form_array) {
if (($form_type !== 'cg' && $form_type !== 'sg') || !is_array($form_array)) {
return false;
}
}

$values = [];

Expand Down
Loading