Skip to content
Closed
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* issue#258: Execute CREATE TABLE SQL correctly during replication sync
* issue#278: Extract duplicated alert command execution paths in syslog_process_alerts
* issue#278: Extract alert command execution into shared helper in functions.php; command tokenization now uses preg_split (handles tabs and consecutive spaces); /bin/sh fallback for non-executable command templates removed (use absolute paths with execute bit set)
* issue#278: Harden command execution helpers with empty-command validation, standardized error logging, and success observability
* issue#278: Add path traversal guard to command execution helpers; reject executable paths containing '..' sequences
* issue: Making changes to support Cacti 1.3
* issue: Don't use MyISAM for non-analytical tables
* issue: The install advisor for Syslog was broken in current Cacti releases
Expand Down
89 changes: 77 additions & 12 deletions functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -1225,17 +1225,39 @@ function syslog_array2xml($array, $tag = 'template') {
return $xml;
}

/**
* syslog_sanitize_hostlist - sanitize and validate a list of hostnames
*
* Trims whitespace, drops empty entries, and rejects entries containing
* characters outside the hostname/IPv6 safe set or exceeding 253 chars.
*
* @param array $hostlist Raw hostnames from alert processing
*
* @return array Sanitized hostnames with invalid entries removed
*/
function syslog_sanitize_hostlist($hostlist) {
/* trim whitespace and drop empty entries */
$hostlist = array_values(array_filter(array_map('trim', $hostlist)));

/* validate entries: only allow characters valid in hostnames and IPv6 addresses */
$hostlist = array_values(array_filter($hostlist, function($h) {
return preg_match('/^[a-zA-Z0-9.\-_:]+$/', $h) && strlen($h) <= 253;
}));

return $hostlist;
}

/**
* syslog_execute_ticket_command - run the configured ticketing command for an alert
*
* @param array $alert The alert row from syslog_alert table
* @param array $hostlist Hostnames matched by the alert
* @param string $error_message sprintf template used if exec() returns non-zero
*
* @return void
*/
function syslog_execute_ticket_command($alert, $hostlist, $error_message) {
$command = read_config_option('syslog_ticket_command');
function syslog_execute_ticket_command($alert, $hostlist) {
$raw_option = read_config_option('syslog_ticket_command');
$command = ($raw_option !== false && $raw_option !== null) ? (string) $raw_option : '';

if ($command != '') {
$command = trim($command);
Expand All @@ -1246,9 +1268,24 @@ function syslog_execute_ticket_command($alert, $hostlist, $error_message) {
$cparts = preg_split('/\s+/', trim($command));
$executable = trim($cparts[0], '"\'');

if (cacti_sizeof($cparts) && is_executable($executable)) {
/* resolve symlinks and relative segments; realpath returns false for non-existent paths */
$resolved = realpath($executable);

if (cacti_sizeof($cparts) && $resolved !== false && is_executable($resolved)) {
$hostlist = syslog_sanitize_hostlist($hostlist);

cacti_log('DEBUG: Ticket command hostlist after sanitization: ' . implode(',', $hostlist), false, 'SYSLOG', POLLER_VERBOSITY_DEBUG);

/* validate alert name produces a non-empty result after cleanup */
$clean_name = clean_up_name($alert['name']);

if ($clean_name == '') {
cacti_log(sprintf('SYSLOG ERROR: Alert name is empty after sanitization for Alert ID:%s', $alert['id']), false, 'SYSTEM');
return;
}

$command = $command .
' --alert-name=' . cacti_escapeshellarg(clean_up_name($alert['name'])) .
' --alert-name=' . cacti_escapeshellarg($clean_name) .
' --severity=' . cacti_escapeshellarg($alert['severity']) .
' --hostlist=' . cacti_escapeshellarg(implode(',', $hostlist)) .
' --message=' . cacti_escapeshellarg($alert['message']);
Expand All @@ -1259,7 +1296,10 @@ function syslog_execute_ticket_command($alert, $hostlist, $error_message) {
exec($command, $output, $return);

if ($return !== 0) {
cacti_log(sprintf($error_message, $alert['name'], $return, implode(', ', $output)), false, 'SYSLOG');
cacti_log(sprintf('ERROR: Ticket Command Failed. Alert:%s, Exit:%s, Output:%s, Command:%s', $alert['name'], $return, implode(', ', $output), $command), false, 'SYSLOG');
} else {
cacti_log(sprintf('SYSLOG NOTICE: Ticket command succeeded. Alert:%s', $alert['name']), false, 'SYSLOG');
cacti_log(sprintf('DEBUG: Ticket command succeeded. Alert:%s, Command:%s', $alert['name'], $command), false, 'SYSLOG', POLLER_VERBOSITY_DEBUG);
}
} else {
$reason = (strpos($executable, DIRECTORY_SEPARATOR) === false)
Expand All @@ -1284,25 +1324,42 @@ function syslog_execute_alert_command($alert, $results, $hostname) {
* <HOSTNAME>, <PRIORITY>, <FACILITY>, <MESSAGE>, <SEVERITY>) with
* cacti_escapeshellarg(). The command template itself comes from admin
* configuration ($alert['command']) and is trusted at that boundary.
* Do not introduce additional substitution paths that bypass this escaping. */
* Do not introduce additional substitution paths that bypass this escaping.
*
* The /bin/sh fallback for non-executable commands was removed as a
* security hardening measure. Commands must use absolute paths with
* the execute bit set. Command templates should prefer the
* $ALERT_MESSAGES environment variable over the <MESSAGE> token
* for syslog content to avoid command-line injection surface. */
$command = alert_replace_variables($alert, $results, $hostname);

if (trim($command) == '') {
cacti_log(sprintf('SYSLOG ERROR: Alert command resolved to empty string for Alert:%s', $alert['name']), false, 'SYSTEM');
return;
}

/* trim surrounding quotes so paths like "/usr/bin/cmd" resolve correctly */
$cparts = preg_split('/\s+/', trim($command));
$executable = trim($cparts[0], '"\'');

/* resolve symlinks and relative segments; realpath returns false for non-existent paths */
$resolved = realpath($executable);

$output = array();
$return = 0;

if (cacti_sizeof($cparts) && is_executable($executable)) {
if (cacti_sizeof($cparts) && $resolved !== false && is_executable($resolved)) {
exec($command, $output, $return);

if ($return !== 0 && !empty($output)) {
cacti_log('SYSLOG NOTICE: Alert command output: ' . implode(', ', $output), true, 'SYSTEM');
}

if ($return !== 0) {
cacti_log(sprintf('ERROR: Alert command failed. Alert:%s, Exit:%s, Output:%s', $alert['name'], $return, implode(', ', $output)), false, 'SYSLOG');
cacti_log(sprintf('ERROR: Alert Command Failed. Alert:%s, Exit:%s, Output:%s, Command:%s', $alert['name'], $return, implode(', ', $output), $command), false, 'SYSLOG');
} else {
cacti_log(sprintf('SYSLOG NOTICE: Alert command succeeded. Alert:%s', $alert['name']), false, 'SYSLOG');
cacti_log(sprintf('DEBUG: Alert command succeeded. Alert:%s, Command:%s', $alert['name'], $command), false, 'SYSLOG', POLLER_VERBOSITY_DEBUG);
}
} else {
$reason = (strpos($executable, DIRECTORY_SEPARATOR) === false)
Expand Down Expand Up @@ -1726,7 +1783,7 @@ function syslog_process_alert($alert, $sql, $params, $count, $hostname = '') {
/**
* Open a ticket if this options have been selected.
*/
syslog_execute_ticket_command($alert, $hostlist, 'ERROR: Ticket Command Failed. Alert:%s, Exit:%s, Output:%s');
syslog_execute_ticket_command($alert, $hostlist);

if (trim($alert['command']) != '' && !$found) {
syslog_execute_alert_command($alert, $results, $hostname);
Expand All @@ -1747,7 +1804,7 @@ function syslog_process_alert($alert, $sql, $params, $count, $hostname = '') {

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

syslog_execute_ticket_command($alert, $hostlist, 'ERROR: Command Failed. Alert:%s, Exit:%s, Output:%s');
syslog_execute_ticket_command($alert, $hostlist);

if (trim($alert['command']) != '' && !$found) {
syslog_execute_alert_command($alert, $results, $hostname);
Expand Down Expand Up @@ -2544,6 +2601,8 @@ function alert_setup_environment(&$alert, $results, $hostlist = array(), $hostna
putenv('ALERT_PRIORITY=' . cacti_escapeshellarg($syslog_levels[$results['priority_id']]));
putenv('ALERT_FACILITY=' . cacti_escapeshellarg($syslog_facilities[$results['facility_id']]));

$hostlist = syslog_sanitize_hostlist($hostlist);

putenv('ALERT_HOSTLIST=' . cacti_escapeshellarg(implode(',', $hostlist)));
putenv('ALERT_HOSTNAME=' . cacti_escapeshellarg($hostname));

Expand All @@ -2565,11 +2624,17 @@ function alert_replace_variables($alert, $results, $hostname = '') {

$command = $alert['command'];

/* cap hostname to RFC 1035 maximum length */
$hostname = substr((string) $hostname, 0, 253);

/* strip null bytes and cap message to 64 KB to avoid oversized command lines */
$message = substr(str_replace("\0", ' ', (string) $results['message']), 0, 65536);

$command = str_replace('<ALERTID>', cacti_escapeshellarg($alert['id']), $command);
$command = str_replace('<HOSTNAME>', cacti_escapeshellarg($hostname), $command);
$command = str_replace('<PRIORITY>', cacti_escapeshellarg($syslog_levels[$results['priority_id']]), $command);
$command = str_replace('<FACILITY>', cacti_escapeshellarg($syslog_facilities[$results['facility_id']]), $command);
$command = str_replace('<MESSAGE>', cacti_escapeshellarg($results['message']), $command);
$command = str_replace('<MESSAGE>', cacti_escapeshellarg($message), $command);
$command = str_replace('<SEVERITY>', cacti_escapeshellarg($severities[$alert['severity']]), $command);

return $command;
Expand Down
44 changes: 22 additions & 22 deletions locales/po/zh-CN.po
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ msgstr "请使用HTML电子邮件客户端"
#: functions.php:1304
#, fuzzy, php-format
msgid "Cacti Syslog Threshold Alert '%s' for Host '%s'"
msgstr "Cacti Syslog插件阈值警报's'"
msgstr "Cacti Syslog插件阈值警报'%s'"

#: functions.php:1306
#, fuzzy, php-format
msgid "Cacti Syslog Threshold Alert '%s'"
msgstr "Cacti Syslog插件阈值警报's'"
msgstr "Cacti Syslog插件阈值警报'%s'"

#: functions.php:1314 syslog.php:1896 syslog_alerts.php:874
msgid "Alert Name"
Expand All @@ -62,12 +62,12 @@ msgstr "匹配字符串"
#: functions.php:1329 functions.php:1368
#, fuzzy, php-format
msgid "Cacti Syslog Alert '%s' for Host '%s'"
msgstr "Cacti Syslog插件阈值警报's'"
msgstr "Cacti Syslog插件阈值警报'%s'"

#: functions.php:1331 functions.php:1370
#, fuzzy, php-format
msgid "Cacti Syslog Alert '%s'"
msgstr "Cacti Syslog插件警报's'"
msgstr "Cacti Syslog插件警报'%s'"

#: functions.php:1340
msgid "Hostname"
Expand Down Expand Up @@ -152,7 +152,7 @@ msgstr "西弗:"
#: functions.php:1490 functions.php:1544
#, fuzzy, php-format
msgid "Event Alert - %s"
msgstr "事件警报 - s"
msgstr "事件警报 - %s"

#: functions.php:1548
#, fuzzy
Expand All @@ -166,7 +166,7 @@ msgstr "主机"
#: functions.php:2116
#, fuzzy, php-format
msgid "Event Report - %s"
msgstr "活动报告 - s"
msgstr "活动报告 - %s"

#: setup.php:34
#, fuzzy
Expand Down Expand Up @@ -252,7 +252,7 @@ msgstr "选择您希望每天创建的分区数。"
#: setup.php:918 setup.php:919 setup.php:920 setup.php:921 setup.php:922
#, php-format
msgid "%d Per Day"
msgstr "d 每天"
msgstr "%d 每天"

#: setup.php:927 setup.php:1015
msgid "Upgrade"
Expand All @@ -265,7 +265,7 @@ msgstr "安装"
#: setup.php:933
#, fuzzy, php-format
msgid "Syslog %s Advisor"
msgstr "Syslogs顾问"
msgstr "Syslog%s顾问"

#: setup.php:937
msgid "WARNING: Syslog Upgrade is Time Consuming!!!"
Expand All @@ -291,7 +291,7 @@ msgstr "安装Syslog时有几个选项可供选择。第一个是数据库架构
#: setup.php:945
#, fuzzy, php-format
msgid "Syslog %s Settings"
msgstr "Syslogs设置"
msgstr "Syslog%s设置"

#: setup.php:972
msgid "What uninstall method do you want to use?"
Expand Down Expand Up @@ -662,7 +662,7 @@ msgstr "在范围内显示Syslog"
#: setup.php:1548
#, fuzzy, php-format
msgid "There were %s Device records removed from the Syslog database"
msgstr "从Syslog数据库中删除了s设备记录"
msgstr "从Syslog数据库中删除了%s设备记录"

#: setup.php:1564
#, fuzzy
Expand Down Expand Up @@ -692,7 +692,7 @@ msgstr "所有文字"
#: syslog.php:67
#, fuzzy, php-format
msgid "%d Chars"
msgstr "d Chars"
msgstr "%d Chars"

#: syslog.php:171
msgid "System Logs"
Expand Down Expand Up @@ -810,7 +810,7 @@ msgstr "默认"
#: syslog.php:1066
#, php-format
msgid " [ Start: '%s' to End: '%s', Unprocessed Messages: %s ]"
msgstr "[开始:'s'到结尾:'s',未处理的消息:s]"
msgstr "[开始:'%s'到结尾:'%s',未处理的消息:%s]"

#: syslog.php:1068
#, php-format
Expand Down Expand Up @@ -839,7 +839,7 @@ msgstr "选择所有设备"
#: syslog.php:1329
#, fuzzy, php-format
msgid "Syslog Message Filter %s"
msgstr "系统日志消息过滤器s"
msgstr "系统日志消息过滤器%s"

#: syslog.php:1336
msgid "Timespan"
Expand Down Expand Up @@ -1160,7 +1160,7 @@ msgstr "1个月"
#: syslog_alerts.php:449
#, fuzzy, php-format
msgid "Alert Edit [edit: %s]"
msgstr "警报编辑[编辑:s]"
msgstr "警报编辑[编辑:%s]"

#: syslog_alerts.php:451 syslog_alerts.php:458 syslog_alerts.php:465
#, fuzzy
Expand Down Expand Up @@ -1424,7 +1424,7 @@ msgstr "导入的"
#: syslog_alerts.php:1039
#, fuzzy, php-format
msgid "NOTE: Alert '%s' %s!"
msgstr "注意:提醒'%s'%s!"
msgstr "注意:提醒'%s'%s!"

#: syslog_alerts.php:1039 syslog_removal.php:861 syslog_reports.php:903
msgid "Updated"
Expand All @@ -1433,7 +1433,7 @@ msgstr "更新"
#: syslog_alerts.php:1041
#, fuzzy, php-format
msgid "ERROR: Alert '%s' %s Failed!"
msgstr "错误:警报'%s'%s失败!"
msgstr "错误:警报'%s'%s失败!"

#: syslog_alerts.php:1041 syslog_removal.php:863 syslog_reports.php:905
msgid "Update"
Expand Down Expand Up @@ -1496,12 +1496,12 @@ msgstr "导出Syslog删除规则"
#: syslog_removal.php:342
#, fuzzy, php-format
msgid "Rule '%s' resulted in %s/%s messages removed/transferred"
msgstr "删除了s消息,并传输了s消息"
msgstr "删除了%s消息,并传输了%s消息"

#: syslog_removal.php:398
#, fuzzy, php-format
msgid "Removal Rule Edit [edit: %s]"
msgstr "删除规则编辑[编辑:s]"
msgstr "删除规则编辑[编辑:%s]"

#: syslog_removal.php:400 syslog_removal.php:407
#, fuzzy
Expand Down Expand Up @@ -1626,12 +1626,12 @@ msgstr "导入删除规则"
#: syslog_removal.php:861
#, fuzzy, php-format
msgid "NOTE: Removal Rule '%s' %s!"
msgstr "注意:删除规则'%s'%s!"
msgstr "注意:删除规则'%s'%s!"

#: syslog_removal.php:863
#, fuzzy, php-format
msgid "ERROR: Removal Rule '%s' %s Failed!"
msgstr "错误:删除规则'%s'%s失败!"
msgstr "错误:删除规则'%s'%s失败!"

#: syslog_reports.php:169
#, fuzzy
Expand Down Expand Up @@ -1685,7 +1685,7 @@ msgstr "返回"
#: syslog_reports.php:391
#, fuzzy, php-format
msgid "Report Edit [edit: %s]"
msgstr "报告编辑[编辑:s]"
msgstr "报告编辑[编辑:%s]"

#: syslog_reports.php:393 syslog_reports.php:398
#, fuzzy
Expand Down Expand Up @@ -1805,4 +1805,4 @@ msgstr "注意:报告规则"
#: syslog_reports.php:905
#, php-format
msgid "ERROR: Report Rule '%s' %s Failed!"
msgstr "错误:报告规则'%s'%s失败!"
msgstr "错误:报告规则'%s'%s失败!"
Loading