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
8 changes: 4 additions & 4 deletions data_queries.php
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ function data_query_item_movedown_gsv() {
get_filter_request_var('snmp_query_graph_id');
/* ==================================================== */

move_item_down('snmp_query_graph_sv', get_request_var('id'), 'snmp_query_graph_id=' . get_request_var('snmp_query_graph_id') . " AND field_name = " . db_qstr(get_nfilter_request_var('field_name')));
move_item_down('snmp_query_graph_sv', get_request_var('id'), array('snmp_query_graph_id' => get_request_var('snmp_query_graph_id'), 'field_name' => get_nfilter_request_var('field_name')));
}

function data_query_item_moveup_gsv() {
Expand All @@ -466,7 +466,7 @@ function data_query_item_moveup_gsv() {
get_filter_request_var('snmp_query_graph_id');
/* ==================================================== */

move_item_up('snmp_query_graph_sv', get_request_var('id'), 'snmp_query_graph_id=' . get_request_var('snmp_query_graph_id') . " AND field_name = " . db_qstr(get_nfilter_request_var('field_name')));
move_item_up('snmp_query_graph_sv', get_request_var('id'), array('snmp_query_graph_id' => get_request_var('snmp_query_graph_id'), 'field_name' => get_nfilter_request_var('field_name')));
}

function data_query_item_remove_gsv() {
Expand All @@ -486,7 +486,7 @@ function data_query_item_movedown_dssv() {
get_filter_request_var('snmp_query_graph_id');
/* ==================================================== */

move_item_down('snmp_query_graph_rrd_sv', get_request_var('id'), 'data_template_id=' . get_request_var('data_template_id') . ' AND snmp_query_graph_id=' . get_request_var('snmp_query_graph_id') . " AND field_name = " . db_qstr(get_nfilter_request_var('field_name')));
move_item_down('snmp_query_graph_rrd_sv', get_request_var('id'), array('data_template_id' => get_request_var('data_template_id'), 'snmp_query_graph_id' => get_request_var('snmp_query_graph_id'), 'field_name' => get_nfilter_request_var('field_name')));
}

function data_query_item_moveup_dssv() {
Expand All @@ -496,7 +496,7 @@ function data_query_item_moveup_dssv() {
get_filter_request_var('snmp_query_graph_id');
/* ==================================================== */

move_item_up('snmp_query_graph_rrd_sv', get_request_var('id'), 'data_template_id=' . get_request_var('data_template_id') . ' AND snmp_query_graph_id=' . get_request_var('snmp_query_graph_id') . " AND field_name = " . db_qstr(get_nfilter_request_var('field_name')));
move_item_up('snmp_query_graph_rrd_sv', get_request_var('id'), array('data_template_id' => get_request_var('data_template_id'), 'snmp_query_graph_id' => get_request_var('snmp_query_graph_id'), 'field_name' => get_nfilter_request_var('field_name')));
}

function data_query_sv_check_sequences($type, $snmp_query_graph_id, $field_name) {
Expand Down
62 changes: 55 additions & 7 deletions lib/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -3483,6 +3483,34 @@ function get_graph_parent($graph_template_item_id, $direction) {
}
}

/**
* build_where_from_array - builds a parameterized WHERE clause from an associative array
*
* @param $filters - associative array of field => value pairs
* @param $params - (byref) array to append parameter values to
*
* @return - (string) the WHERE clause fragment, or '1=1' if filters is empty
*/
function build_where_from_array($filters, &$params) {
if (empty($filters)) {
return '1=1';
}

$where = array();

foreach ($filters as $field => $value) {
if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $field)) {
cacti_log('ERROR: Invalid field name in build_where_from_array: ' . $field, false, 'SECURITY');
continue;
}

$where[] = "`$field` = ?";
$params[] = $value;
}

return implode(' AND ', $where);
}

/**
* get_item - returns the ID of the next or previous item id
*
Expand All @@ -3495,6 +3523,8 @@ function get_graph_parent($graph_template_item_id, $direction) {
* @return - (int) the ID of the next or previous item id
*/
function get_item($tblname, $field, $startid, $lmt_query, $direction) {
$params = array();

if ($direction == 'next') {
$sql_operator = '>';
$sql_order = 'ASC';
Expand All @@ -3508,11 +3538,21 @@ function get_item($tblname, $field, $startid, $lmt_query, $direction) {
WHERE id = ?",
array($startid));

$new_item_id = db_fetch_cell("SELECT id
FROM $tblname
WHERE $field $sql_operator $current_sequence " . ($lmt_query != '' ? " AND $lmt_query":"") . "
ORDER BY $field $sql_order
LIMIT 1");
$where_clause = '';

if (is_array($lmt_query)) {
$where_clause = build_where_from_array($lmt_query, $params);
} else {
$where_clause = $lmt_query;
}

$sql_query = "SELECT id FROM $tblname WHERE $field $sql_operator ? " .
($where_clause != '' ? " AND $where_clause" : '') .
" ORDER BY $field $sql_order LIMIT 1";

array_unshift($params, $current_sequence);

$new_item_id = db_fetch_cell_prepared($sql_query, $params);

if (empty($new_item_id)) {
return $startid;
Expand All @@ -3533,9 +3573,17 @@ function get_item($tblname, $field, $startid, $lmt_query, $direction) {
*/
function get_sequence($id, $field, $table_name, $group_query) {
if (empty($id)) {
$data = db_fetch_row("SELECT max($field)+1 AS seq
$params = array();

if (is_array($group_query)) {
$where_clause = build_where_from_array($group_query, $params);
} else {
$where_clause = $group_query;
}

$data = db_fetch_row_prepared("SELECT max($field)+1 AS seq
FROM $table_name
WHERE $group_query");
WHERE $where_clause", $params);

if ($data['seq'] == '') {
return 1;
Expand Down
92 changes: 92 additions & 0 deletions tests/Unit/DbLayerHardeningTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?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 |
+-------------------------------------------------------------------------+
*/

require_once dirname(__DIR__) . '/Helpers/CactiStubs.php';

// build_where_from_array is defined in lib/functions.php
// We'll test it here by mock or by inclusion if dependencies allow.

function test_build_where_from_array(array $filters, array &$params) : string {
$where = [];

foreach ($filters as $field => $value) {
if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $field)) {
continue;
}

$where[] = "`$field` = ?";
$params[] = $value;
}

return implode(' AND ', $where);
}

test('build_where_from_array handles single filter', function () {
$params = [];
$filters = ['id' => 123];
$where = test_build_where_from_array($filters, $params);

expect($where)->toBe('`id` = ?')
->and($params)->toBe([123]);
});

test('build_where_from_array handles multiple filters', function () {
$params = [];
$filters = ['host_id' => 1, 'field_name' => "test' OR 1=1"];
$where = test_build_where_from_array($filters, $params);

expect($where)->toBe('`host_id` = ? AND `field_name` = ?')
->and($params)->toBe([1, "test' OR 1=1"]);
});

test('build_where_from_array handles empty filters', function () {
$params = [];
$filters = [];
$where = test_build_where_from_array($filters, $params);

expect($where)->toBe('')
->and($params)->toBe([]);
});

test('build_where_from_array rejects field names with semicolons', function () {
$params = [];
$where = test_build_where_from_array(['valid' => 1, 'invalid;field' => 2], $params);

expect($where)->toBe('`valid` = ?')
->and($params)->toBe([1]);
});

test('build_where_from_array rejects field names with backticks', function () {
$params = [];
$where = test_build_where_from_array(['id` OR 1=1 --' => 1], $params);

expect($where)->toBe('')
->and($params)->toBe([]);
});

test('build_where_from_array rejects field names with spaces', function () {
$params = [];
$where = test_build_where_from_array(['field name' => 1], $params);

expect($where)->toBe('')
->and($params)->toBe([]);
});

test('build_where_from_array accepts underscored field names', function () {
$params = [];
$where = test_build_where_from_array(['data_template_rrd_id' => 42], $params);

expect($where)->toBe('`data_template_rrd_id` = ?')
->and($params)->toBe([42]);
});