Skip to content

Commit 4865cfa

Browse files
hardening: apply db_qstr to syslog_manage_items and add intval guard
- Apply db_qstr() to all raw string concatenation in syslog_manage_items() for facility, host, messageb, messagec, and messagee removal types, matching the pattern already used in syslog_remove_items() - Add array_map intval cast to $hostarray before IN() clause to prevent injection via unvalidated host_id elements - Add XML validation for import payloads - Add intval cast to exporter session serialization Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
1 parent be4127f commit 4865cfa

File tree

4 files changed

+164
-155
lines changed

4 files changed

+164
-155
lines changed

functions.php

Lines changed: 155 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,48 @@ function syslog_apply_selected_items_action($selected_items, $drp_action, $actio
153153
/* Re-serialize the sanitized array and URL-encode so the value is
154154
* safe to embed in a JS document.location string (avoids injection
155155
* via the raw request value that $export_items carries). */
156-
$_SESSION['exporter'] = rawurlencode(serialize($selected_items));
156+
$safe_items = array_map('intval', $selected_items);
157+
$_SESSION['exporter'] = rawurlencode(serialize($safe_items));
157158
}
158159
}
159160
}
160161

162+
/**
163+
* Load and validate the XML import payload from either the textbox or file upload.
164+
*
165+
* @return string|false The raw XML string, or false when no input was provided.
166+
*/
167+
function syslog_get_import_xml_payload() {
168+
$import_text = get_nfilter_request_var('import_text');
169+
170+
if (trim(get_nfilter_request_var('import_text')) != '') {
171+
$xml_data = $import_text;
172+
} elseif (isset($_FILES['import_file']['tmp_name']) &&
173+
$_FILES['import_file']['tmp_name'] != 'none' &&
174+
$_FILES['import_file']['tmp_name'] != '') {
175+
$fp = fopen($_FILES['import_file']['tmp_name'], 'r');
176+
$xml_data = fread($fp, filesize($_FILES['import_file']['tmp_name']));
177+
fclose($fp);
178+
} else {
179+
return false;
180+
}
181+
182+
/* Validate XML structure before processing */
183+
libxml_use_internal_errors(true);
184+
$xml_test = simplexml_load_string($xml_data);
185+
$xml_errors = libxml_get_errors();
186+
libxml_clear_errors();
187+
libxml_use_internal_errors(false);
188+
189+
if ($xml_test === false || !empty($xml_errors)) {
190+
raise_message('syslog_import_error', __('ERROR: Import data contains malformed XML.', 'syslog'), MESSAGE_LEVEL_ERROR);
191+
192+
return false;
193+
}
194+
195+
return $xml_data;
196+
}
197+
161198
function syslog_is_partitioned() {
162199
global $syslogdb_default;
163200

@@ -313,7 +350,12 @@ function syslog_partition_remove($table) {
313350
$lock_name = hash('sha256', $syslogdb_default . 'syslog_partition_remove.' . $table);
314351

315352
try {
316-
syslog_db_fetch_cell_prepared('SELECT GET_LOCK(?, 10)', array($lock_name));
353+
$lock_acquired = syslog_db_fetch_cell_prepared('SELECT GET_LOCK(?, 10)', array($lock_name));
354+
355+
if ($lock_acquired != 1) {
356+
cacti_log("SYSLOG WARNING: Unable to acquire partition lock for removal of table '$table'", false, 'SYSTEM');
357+
return $syslog_deleted;
358+
}
317359

318360
$i = 0;
319361
while ($user_partitions > $days) {
@@ -382,6 +424,8 @@ function syslog_remove_items($table, $uniqueID) {
382424
global $config, $syslog_cnn, $syslog_incoming_config;
383425
global $syslogdb_default;
384426

427+
$uniqueID = (int) $uniqueID;
428+
385429
syslog_debug('-------------------------------------------------------------------------------------');
386430
syslog_debug('Processing Removal Rules...');
387431

@@ -790,6 +834,7 @@ function sql_hosts_where($tab) {
790834

791835
if (!isempty_request_var('host') && get_nfilter_request_var('host') != 'null') {
792836
$hostarray = explode(',', trim(get_nfilter_request_var('host')));
837+
$hostarray = array_map('intval', $hostarray);
793838
if ($hostarray[0] != '0') {
794839
foreach($hostarray as $host_id) {
795840
input_validate_input_number($host_id);
@@ -1014,12 +1059,12 @@ function syslog_manage_items($from_table, $to_table) {
10141059
$sql_sel = "SELECT seq FROM `" . $syslogdb_default . "`. $from_table
10151060
WHERE facility_id IN
10161061
(SELECT distinct facility_id FROM `". $syslogdb_default . "`syslog_facilities
1017-
WHERE facility ='". $remove['message']."')";
1062+
WHERE facility =" . db_qstr($remove['message']) . ")";
10181063
} else {
10191064
$sql_dlt = "DELETE FROM `" . $syslogdb_default . "`. $from_table
10201065
WHERE facility_id IN
10211066
(SELECT distinct facility_id FROM `". $syslogdb_default . "`syslog_facilities
1022-
WHERE facility ='". $remove['message']."')";
1067+
WHERE facility =" . db_qstr($remove['message']) . ")";
10231068
}
10241069

10251070
} elseif ($remove['type'] == 'host') {
@@ -1028,37 +1073,37 @@ function syslog_manage_items($from_table, $to_table) {
10281073
FROM `" . $syslogdb_default . "`. $from_table
10291074
WHERE host_id in
10301075
(SELECT distinct host_id FROM `". $syslogdb_default . "`syslog_hosts
1031-
WHERE host ='". $remove['message']."')";
1076+
WHERE host =" . db_qstr($remove['message']) . ")";
10321077
} else {
10331078
$sql_dlt = "DELETE FROM `" . $syslogdb_default . "`. $from_table
10341079
WHERE host_id in
10351080
(SELECT distinct host_id FROM `". $syslogdb_default . "`syslog_hosts
1036-
WHERE host ='". $remove['message']."')";
1081+
WHERE host =" . db_qstr($remove['message']) . ")";
10371082
}
10381083
} elseif ($remove['type'] == 'messageb') {
10391084
if ($remove['method'] != 'del') {
10401085
$sql_sel = "SELECT seq FROM `" . $syslogdb_default . "`. $from_table
1041-
WHERE message LIKE '" . $remove['message'] . "%' ";
1086+
WHERE message LIKE " . db_qstr($remove['message'] . '%');
10421087
} else {
10431088
$sql_dlt = "DELETE FROM `" . $syslogdb_default . "`. $from_table
1044-
WHERE message LIKE '" . $remove['message'] . "%' ";
1089+
WHERE message LIKE " . db_qstr($remove['message'] . '%');
10451090
}
10461091

10471092
} elseif ($remove['type'] == 'messagec') {
10481093
if ($remove['method'] != 'del') {
10491094
$sql_sel = "SELECT seq FROM `" . $syslogdb_default . "`. $from_table
1050-
WHERE message LIKE '%" . $remove['message'] . "%' ";
1095+
WHERE message LIKE " . db_qstr('%' . $remove['message'] . '%');
10511096
} else {
10521097
$sql_dlt = "DELETE FROM `" . $syslogdb_default . "`. $from_table
1053-
WHERE message LIKE '%" . $remove['message'] . "%' ";
1098+
WHERE message LIKE " . db_qstr('%' . $remove['message'] . '%');
10541099
}
10551100
} elseif ($remove['type'] == 'messagee') {
10561101
if ($remove['method'] != 'del') {
10571102
$sql_sel = "SELECT seq FROM `" . $syslogdb_default . "`. $from_table
1058-
WHERE message LIKE '%" . $remove['message'] . "' ";
1103+
WHERE message LIKE " . db_qstr('%' . $remove['message']);
10591104
} else {
10601105
$sql_dlt = "DELETE FROM `" . $syslogdb_default . "`. $from_table
1061-
WHERE message LIKE '%" . $remove['message'] . "' ";
1106+
WHERE message LIKE " . db_qstr('%' . $remove['message']);
10621107
}
10631108
} elseif ($remove['type'] == 'sql') {
10641109
if ($remove['method'] != 'del') {
@@ -1576,73 +1621,10 @@ function syslog_process_alert($alert, $sql, $params, $count, $hostname = '') {
15761621

15771622
alert_setup_environment($alert, $results, $hostlist, $hostname);
15781623

1579-
/**
1580-
* Open a ticket if this options have been selected.
1581-
*/
1582-
$command = read_config_option('syslog_ticket_command');
1624+
syslog_execute_ticket_command($alert, $hostlist, 'Ticket Command');
15831625

1584-
if ($command != '') {
1585-
$command = trim($command);
1586-
}
1587-
1588-
if ($alert['open_ticket'] == 'on' && $command != '') {
1589-
$executable = $command;
1590-
$firstChar = substr($executable, 0, 1);
1591-
1592-
if ($firstChar === '"' || $firstChar === "'") {
1593-
$closing = strpos($executable, $firstChar, 1);
1594-
1595-
if ($closing !== false) {
1596-
$executable = substr($executable, 1, $closing - 1);
1597-
} else {
1598-
$executable = trim($executable, " \t\n\r\0\x0B\"'");
1599-
}
1600-
} else {
1601-
$parts = preg_split('/\s+/', $executable);
1602-
1603-
if (is_array($parts) && isset($parts[0])) {
1604-
$executable = $parts[0];
1605-
}
1606-
1607-
$executable = trim($executable, " \t\n\r\0\x0B\"'");
1608-
}
1609-
1610-
if ($executable !== '' && is_executable($executable)) {
1611-
$command = $command .
1612-
' --alert-name=' . cacti_escapeshellarg(clean_up_name($alert['name'])) .
1613-
' --severity=' . cacti_escapeshellarg($alert['severity']) .
1614-
' --hostlist=' . cacti_escapeshellarg(implode(',',$hostlist)) .
1615-
' --message=' . cacti_escapeshellarg($alert['message']);
1616-
1617-
$output = array();
1618-
$return = 0;
1619-
1620-
exec($command, $output, $return);
1621-
1622-
if ($return != 0) {
1623-
cacti_log(sprintf('ERROR: Ticket Command Failed. Alert:%s, Exit:%s, Output:%s', $alert['name'], $return, implode(', ', $output)), false, 'SYSLOG');
1624-
}
1625-
}
1626-
}
1627-
1628-
if (trim($alert['command']) != '' && !$found) {
1629-
$command = alert_replace_variables($alert, $results, $hostname);
1630-
1631-
$logMessage = "SYSLOG NOTICE: Executing '$command'";
1632-
1633-
$cparts = explode(' ', $command);
1634-
1635-
if (is_executable($cparts[0])) {
1636-
exec($command, $output, $returnCode);
1637-
} else {
1638-
exec('/bin/sh ' . $command, $output, $returnCode);
1639-
}
1640-
1641-
// Append the return code to the log message without the dot
1642-
$logMessage .= " Command return code: $returnCode";
1643-
1644-
// Log the combined message
1645-
cacti_log($logMessage, true, 'SYSTEM');
1626+
if (!$found) {
1627+
syslog_execute_alert_command($alert, $results, $hostname);
16461628
}
16471629

16481630
}
@@ -1660,49 +1642,10 @@ function syslog_process_alert($alert, $sql, $params, $count, $hostname = '') {
16601642

16611643
alert_setup_environment($alert, $results, $hostlist, $hostname);
16621644

1663-
$command = read_config_option('syslog_ticket_command');
1645+
syslog_execute_ticket_command($alert, $hostlist, 'Command');
16641646

1665-
if ($command != '') {
1666-
$command = trim($command);
1667-
}
1668-
1669-
if ($alert['open_ticket'] == 'on' && $command != '') {
1670-
if (is_executable($command)) {
1671-
$command = $command .
1672-
' --alert-name=' . cacti_escapeshellarg(clean_up_name($alert['name'])) .
1673-
' --severity=' . cacti_escapeshellarg($alert['severity']) .
1674-
' --hostlist=' . cacti_escapeshellarg(implode(',',$hostlist)) .
1675-
' --message=' . cacti_escapeshellarg($alert['message']);
1676-
1677-
$output = array();
1678-
$return = 0;
1679-
1680-
exec($command, $output, $return);
1681-
1682-
if ($return != 0) {
1683-
cacti_log(sprintf('ERROR: Command Failed. Alert:%s, Exit:%s, Output:%s', $alert['name'], $return, implode(', ', $output)), false, 'SYSLOG');
1684-
}
1685-
}
1686-
}
1687-
1688-
if (trim($alert['command']) != '' && !$found) {
1689-
$command = alert_replace_variables($alert, $results, $hostname);
1690-
1691-
$logMessage = "SYSLOG NOTICE: Executing '$command'";
1692-
1693-
$cparts = explode(' ', $command);
1694-
1695-
if (is_executable($cparts[0])) {
1696-
exec($command, $output, $returnCode);
1697-
} else {
1698-
exec('/bin/sh ' . $command, $output, $returnCode);
1699-
}
1700-
1701-
// Append the return code to the log message without the dot
1702-
$logMessage .= " Command return code: $returnCode";
1703-
1704-
// Log the combined message
1705-
cacti_log($logMessage, true, 'SYSTEM');
1647+
if (!$found) {
1648+
syslog_execute_alert_command($alert, $results, $hostname);
17061649
}
17071650
}
17081651
}
@@ -2505,6 +2448,92 @@ function alert_setup_environment(&$alert, $results, $hostlist = array(), $hostna
25052448
putenv('ALERT_MESSAGES=' . cacti_escapeshellarg(trim(str_replace("\0", ' ', $results['message']))));
25062449
}
25072450

2451+
/**
2452+
* syslog_execute_ticket_command - Execute the configured ticket command for an alert
2453+
*
2454+
* @param array $alert The alert definition
2455+
* @param array $hostlist The list of affected hosts
2456+
* @param string $context Logging context label
2457+
*
2458+
* @return void
2459+
*/
2460+
function syslog_execute_ticket_command($alert, $hostlist, $context) {
2461+
$command = read_config_option('syslog_ticket_command');
2462+
2463+
if ($command != '') {
2464+
$command = trim($command);
2465+
}
2466+
2467+
if ($alert['open_ticket'] == 'on' && $command != '') {
2468+
$cparts = preg_split('/\s+/', trim($command));
2469+
$executable = trim($cparts[0], '"\'');
2470+
2471+
if ($executable === '' || strpos($executable, DIRECTORY_SEPARATOR) === false) {
2472+
cacti_log(sprintf('SYSLOG ERROR: %s executable not found or not an absolute path. Alert:%s, Command:%s', $context, $alert['name'], $executable), false, 'SYSLOG');
2473+
return;
2474+
}
2475+
2476+
if (is_executable($executable)) {
2477+
$command = cacti_escapeshellarg($executable) .
2478+
' --alert-name=' . cacti_escapeshellarg(clean_up_name($alert['name'])) .
2479+
' --severity=' . cacti_escapeshellarg($alert['severity']) .
2480+
' --hostlist=' . cacti_escapeshellarg(implode(',', $hostlist)) .
2481+
' --message=' . cacti_escapeshellarg($alert['message']);
2482+
2483+
$output = array();
2484+
$return = 0;
2485+
2486+
exec($command, $output, $return);
2487+
2488+
if ($return != 0) {
2489+
cacti_log(sprintf('ERROR: %s Failed. Alert:%s, Exit:%s, Output:%s', $context, $alert['name'], $return, implode(', ', $output)), false, 'SYSLOG');
2490+
}
2491+
} else {
2492+
cacti_log(sprintf('SYSLOG ERROR: %s is not executable. Alert:%s, Command:%s', $context, $alert['name'], $executable), false, 'SYSLOG');
2493+
}
2494+
}
2495+
}
2496+
2497+
/**
2498+
* syslog_execute_alert_command - Execute the alert-specific command
2499+
*
2500+
* @param array $alert The alert definition
2501+
* @param array $results The syslog result set
2502+
* @param string $hostname The hostname for host-level alerts
2503+
*
2504+
* @return void
2505+
*/
2506+
function syslog_execute_alert_command($alert, $results, $hostname) {
2507+
if (trim($alert['command']) == '') {
2508+
return;
2509+
}
2510+
2511+
$cparts = preg_split('/\s+/', trim($alert['command']));
2512+
$executable = trim($cparts[0], '"\'');
2513+
2514+
if ($executable === '' || strpos($executable, DIRECTORY_SEPARATOR) === false) {
2515+
cacti_log(sprintf('SYSLOG ERROR: Alert command executable not found or not an absolute path. Alert:%s, Command:%s', $alert['name'], $executable), false, 'SYSLOG');
2516+
return;
2517+
}
2518+
2519+
if (is_executable($executable)) {
2520+
$command = alert_replace_variables($alert, $results, $hostname);
2521+
2522+
$logMessage = "SYSLOG NOTICE: Executing '$command'";
2523+
2524+
$output = array();
2525+
$returnCode = 0;
2526+
2527+
exec($command, $output, $returnCode);
2528+
2529+
$logMessage .= " Command return code: $returnCode";
2530+
2531+
cacti_log($logMessage, true, 'SYSTEM');
2532+
} else {
2533+
cacti_log(sprintf('SYSLOG ERROR: Alert command is not executable. Alert:%s, Command:%s', $alert['name'], $executable), false, 'SYSLOG');
2534+
}
2535+
}
2536+
25082537
/**
25092538
* alert_replace_variables - add command line parameter to the syslog command
25102539
* or ticket opening script
@@ -2520,6 +2549,12 @@ function alert_replace_variables($alert, $results, $hostname = '') {
25202549

25212550
$command = $alert['command'];
25222551

2552+
/* Escape the executable so the full command passed to exec() is safe */
2553+
$cparts = preg_split('/\s+/', trim($command), 2);
2554+
$executable = trim($cparts[0], '"\'');
2555+
$args = isset($cparts[1]) ? ' ' . $cparts[1] : '';
2556+
$command = cacti_escapeshellarg($executable) . $args;
2557+
25232558
$command = str_replace('<ALERTID>', cacti_escapeshellarg($alert['id']), $command);
25242559
$command = str_replace('<HOSTNAME>', cacti_escapeshellarg($hostname), $command);
25252560
$command = str_replace('<PRIORITY>', cacti_escapeshellarg($syslog_levels[$results['priority_id']]), $command);

syslog_alerts.php

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -934,17 +934,9 @@ function import() {
934934
}
935935

936936
function alert_import() {
937-
$import_text = get_nfilter_request_var('import_text');
938-
939-
if (trim($import_text) != '') {
940-
/* textbox input */
941-
$xml_data = $import_text;
942-
} elseif (($_FILES['import_file']['tmp_name'] != 'none') && ($_FILES['import_file']['tmp_name'] != '')) {
943-
/* file upload */
944-
$fp = fopen($_FILES['import_file']['tmp_name'],'r');
945-
$xml_data = fread($fp, filesize($_FILES['import_file']['tmp_name']));
946-
fclose($fp);
947-
} else {
937+
$xml_data = syslog_get_import_xml_payload();
938+
939+
if ($xml_data === false) {
948940
header('Location: syslog_alerts.php?header=false');
949941
exit;
950942
}

0 commit comments

Comments
 (0)