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
23 changes: 12 additions & 11 deletions .github/workflows/plugin-ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
uses: actions/checkout@v4
with:
repository: Cacti/cacti
ref: release/1.2.31
path: cacti

- name: Checkout Syslog Plugin
Expand All @@ -57,7 +58,7 @@ jobs:
- name: Check PHP Syntax (Lint)
run: |
cd cacti/plugins/syslog
if find . -name '*.php' -not -path './vendor/*' -exec php -l {} 2>&1 \; | grep -iv 'no syntax errors detected'; then
if find . -name '*.php' -not -path './vendor/*' -exec php -l {} \; 2>&1 | grep -iv 'no syntax errors detected'; then
echo "Syntax errors found!"
exit 1
fi
Expand Down Expand Up @@ -122,6 +123,7 @@ jobs:
uses: actions/checkout@v4
with:
repository: Cacti/cacti
ref: release/1.2.31
path: cacti

- name: Checkout Syslog Plugin
Expand All @@ -143,7 +145,7 @@ jobs:
run: sudo apt-get update

- name: Install System Dependencies
run: sudo apt-get install -y apache2 snmp snmpd rrdtool fping libapache2-mod-php${{ matrix.php }}
run: sudo apt-get install -y apache2 snmp snmpd rrdtool fping

- name: Start SNMPD Agent and Test
run: |
Expand All @@ -163,16 +165,15 @@ jobs:
echo -e "[client]\nuser = root\npassword = cactiroot\nhost = 127.0.0.1\n" > ~/.my.cnf

- name: Initialize Cacti Database
env:
MYSQL_AUTH_USR: '--defaults-file=~/.my.cnf'
run: |
mysql $MYSQL_AUTH_USR -e 'CREATE DATABASE IF NOT EXISTS cacti;'
mysql $MYSQL_AUTH_USR -e "CREATE USER IF NOT EXISTS 'cactiuser'@'localhost' IDENTIFIED BY 'cactiuser';"
mysql $MYSQL_AUTH_USR -e "GRANT ALL PRIVILEGES ON cacti.* TO 'cactiuser'@'localhost';"
mysql $MYSQL_AUTH_USR -e "GRANT SELECT ON mysql.time_zone_name TO 'cactiuser'@'localhost';"
mysql $MYSQL_AUTH_USR -e "FLUSH PRIVILEGES;"
mysql $MYSQL_AUTH_USR cacti < ${{ github.workspace }}/cacti/cacti.sql
mysql $MYSQL_AUTH_USR -e "INSERT INTO settings (name, value) VALUES ('path_php_binary', '/usr/bin/php')" cacti
MYSQL_AUTH_USR="--defaults-file=$HOME/.my.cnf"
mysql "$MYSQL_AUTH_USR" -e 'CREATE DATABASE IF NOT EXISTS cacti;'
mysql "$MYSQL_AUTH_USR" -e "CREATE USER IF NOT EXISTS 'cactiuser'@'localhost' IDENTIFIED BY 'cactiuser';"
mysql "$MYSQL_AUTH_USR" -e "GRANT ALL PRIVILEGES ON cacti.* TO 'cactiuser'@'localhost';"
mysql "$MYSQL_AUTH_USR" -e "GRANT SELECT ON mysql.time_zone_name TO 'cactiuser'@'localhost';"
mysql "$MYSQL_AUTH_USR" -e "FLUSH PRIVILEGES;"
mysql "$MYSQL_AUTH_USR" cacti < ${{ github.workspace }}/cacti/cacti.sql
mysql "$MYSQL_AUTH_USR" -e "INSERT INTO settings (name, value) VALUES ('path_php_binary', '/usr/bin/php')" cacti

- name: Validate composer files
run: |
Expand Down
55 changes: 43 additions & 12 deletions functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,25 @@ function syslog_sendemail($to, $from, $subject, $message, $smsmessage = '') {
}
}

const SYSLOG_IMPORT_MAX_BYTES = 5 * 1024 * 1024;

function syslog_get_import_xml_payload($redirect_url) {
if (trim(get_nfilter_request_var('import_text')) != '') {
$import_text = (string) get_nfilter_request_var('import_text');

if (trim($import_text) !== '') {
// textbox input
return get_nfilter_request_var('import_text');
if (strlen($import_text) > SYSLOG_IMPORT_MAX_BYTES) {
cacti_log('SYSLOG ERROR: Text import payload exceeds the maximum size', false, 'SYSTEM');
header('Location: ' . $redirect_url);
exit;
}

return $import_text;
}

if (isset($_FILES['import_file']['tmp_name']) &&
$_FILES['import_file']['tmp_name'] != 'none' &&
$_FILES['import_file']['tmp_name'] != '') {
$_FILES['import_file']['tmp_name'] !== 'none' &&
$_FILES['import_file']['tmp_name'] !== '') {
// file upload
$tmp_name = $_FILES['import_file']['tmp_name'];

Expand All @@ -166,6 +176,14 @@ function syslog_get_import_xml_payload($redirect_url) {
exit;
}

$size = (int) ($_FILES['import_file']['size'] ?? filesize($tmp_name));

if ($size <= 0 || $size > SYSLOG_IMPORT_MAX_BYTES) {
cacti_log('SYSLOG ERROR: Uploaded import file has an invalid size', false, 'SYSTEM');
header('Location: ' . $redirect_url);
exit;
}

$fp = fopen($tmp_name, 'rb');

if ($fp === false) {
Expand All @@ -174,7 +192,7 @@ function syslog_get_import_xml_payload($redirect_url) {
exit;
}

$xml_data = fread($fp, filesize($tmp_name));
$xml_data = fread($fp, $size);
fclose($fp);

if ($xml_data === false) {
Expand All @@ -190,6 +208,22 @@ function syslog_get_import_xml_payload($redirect_url) {
exit;
}

function syslog_csv_cell(mixed $value): string {
$value = (string) $value;

if ($value === '' || str_starts_with($value, "'")) {
return $value;
}

$trimmed = ltrim($value, ' ');

if ($trimmed !== '' && in_array($trimmed[0], ['=', '+', '-', '@', "\t", "\r"], true)) {
return "'" . $value;
}

return $value;
}

function syslog_is_partitioned() {
global $syslogdb_default;

Expand Down Expand Up @@ -894,19 +928,16 @@ function syslog_export($tab) {
$severity = 'Unknown';
}

$host = trim($message['host'], ' =+-@');
$logmsg = trim($message['logmsg'], ' =+-@');

$line = [
$line = array_map('syslog_csv_cell', [
$message['name'],
$severity,
$message['logtime'],
$logmsg,
$host,
$message['logmsg'],
$message['host'],
ucfirst($message['facility']),
ucfirst($message['priority']),
$message['count']
];
]);

fputcsv($fp, $line);
}
Expand Down
2 changes: 1 addition & 1 deletion syslog.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ function syslog_view_alarm() {
WHERE seq = ?",
[get_request_var('id')]);

print trim($html, "' ");
print nl2br(html_escape(trim($html, "' ")));

print '</td></tr></table>';

Expand Down
5 changes: 3 additions & 2 deletions tests/regression/issue277_import_payload_loader_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@
exit(1);
}

if (strpos($functions, 'function syslog_get_import_xml_payload(') === false) {
if (!str_contains($functions, 'function syslog_get_import_xml_payload(')) {
fwrite(STDERR, "Shared import payload loader helper is missing.\n");
exit(1);
}

if (strpos($functions, "trim(get_nfilter_request_var('import_text')) != ''") === false) {
if (!str_contains($functions, '$import_text = (string) get_nfilter_request_var(\'import_text\')') ||
!str_contains($functions, 'trim($import_text) !== \'\'')) {
fwrite(STDERR, "Shared import payload loader is missing trimmed text handling.\n");
exit(1);
}
Expand Down
51 changes: 51 additions & 0 deletions tests/regression/issue318_output_import_hardening_test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

$functions = file_get_contents(__DIR__ . '/../../functions.php');
$syslog = file_get_contents(__DIR__ . '/../../syslog.php');

if ($functions === false || $syslog === false) {
fwrite(STDERR, "Unable to read Syslog sources\n");
exit(1);
}

foreach ([
'SYSLOG_IMPORT_MAX_BYTES',
'$import_text = (string) get_nfilter_request_var(\'import_text\')',
'strlen($import_text) > SYSLOG_IMPORT_MAX_BYTES',
'$size <= 0 || $size > SYSLOG_IMPORT_MAX_BYTES',
'function syslog_csv_cell(mixed $value): string',
"array_map('syslog_csv_cell'",
] as $needle) {
if (!str_contains($functions, $needle)) {
fwrite(STDERR, "Missing import/export hardening: $needle\n");
exit(1);
}
}

if (!preg_match('/function\s+syslog_csv_cell\s*\([^)]*\)\s*:\s*string\s*\{.*?\n\}/s', $functions, $match)) {
fwrite(STDERR, "Unable to extract syslog_csv_cell()\n");
exit(1);
}

eval(str_replace('function syslog_csv_cell', 'function issue318_csv_cell', $match[0]));

foreach ([
['=SUM(A1)', "'=SUM(A1)"],
["\tevil", "'\tevil"],
["\revil", "'\revil"],
[' =SUM(A1)', "' =SUM(A1)"],
["'=SUM(A1)", "'=SUM(A1)"],
['router-01', 'router-01'],
] as [$input, $expected]) {
if (issue318_csv_cell($input) !== $expected) {
fwrite(STDERR, 'CSV formula hardening failed for ' . var_export($input, true) . "\n");
exit(1);
}
}

if (!str_contains($syslog, 'nl2br(html_escape(trim($html, "\' ")))')) {
fwrite(STDERR, "Alert viewer must escape stored HTML\n");
exit(1);
}

print "issue318_output_import_hardening_test passed\n";
Loading