Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
aa00095
hardening: enforce POST+CSRF for purge syslog devices utility
somethingwithproof Mar 10, 2026
9bc39fc
fix(csrf): distinct error codes, audit log on non-POST, honest lint h…
somethingwithproof Apr 11, 2026
0ba5b32
fix(security): harden syslog bulk form and nav encoding
somethingwithproof Apr 11, 2026
5a120cd
Update syslog_alerts.php
TheWitness Jun 4, 2026
f22cfc4
Refactor navigation bar URL in syslog_removal.php
TheWitness Jun 4, 2026
93fca4c
Fix navigation bar URL in syslog_reports.php
TheWitness Jun 4, 2026
330497b
Address Syslog purge security review
somethingwithproof Jul 14, 2026
4a391aa
Preserve Syslog filter navigation safely
somethingwithproof Jul 14, 2026
3837c56
Strengthen Syslog CSRF regression coverage
somethingwithproof Jul 14, 2026
ba77288
Use runner PHP without versioned Apache package
somethingwithproof Jul 14, 2026
fb74143
refactor: reuse syslog JSON script encoder
somethingwithproof Jul 30, 2026
62b7576
Modernize CSRF hardening for PHP 8.0
somethingwithproof Aug 17, 2026
075e077
ci: repair the integration workflow
somethingwithproof Aug 17, 2026
3c6ccc5
refactor: rename the JSON script encoder to syslog_json_safe
somethingwithproof Sep 3, 2026
fb4b20a
fix: keep the purge post headerless
somethingwithproof Sep 4, 2026
3ae858d
refactor: drop the nav bar filter encoding from this branch
somethingwithproof Sep 4, 2026
863c3fe
test: drive the purge guard instead of grepping for it
somethingwithproof Sep 4, 2026
5b44732
docs: record issue#259 in the changelog
somethingwithproof Sep 4, 2026
ccf0c99
style: space the closing php tags like the rest of the repo
somethingwithproof Sep 4, 2026
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* issue#252: hardening: escape device hostname output in syslog view; parameterize alert API functions
* issue#256: hardening: prevent CSV formula injection and malformed CSV output in exports
* issue#258: Execute CREATE TABLE SQL correctly during replication sync
* issue#259: hardening: require POST and a CSRF token for the purge syslog devices utility
* issue#260: hardening: replace eval-based callback execution in syslog autocomplete JS
* 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)
Expand Down
12 changes: 12 additions & 0 deletions functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ function syslog_include_js() {
<?php
}

/**
* __esc() is not enough inside a <script> block, because the browser does
* not HTML-decode there. The value has to arrive as a JSON literal.
*
* @param mixed $value
*
* @return string
*/
function syslog_json_safe($value) {
return json_encode($value, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_THROW_ON_ERROR);
}

function syslog_allow_edits() {
global $config;

Expand Down
75 changes: 72 additions & 3 deletions setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -1607,7 +1607,31 @@ function syslog_utilities_action($action) {
return;
}

if ($action == 'purge_syslog_hosts') {
if ($action === 'purge_syslog_hosts') {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
cacti_log('WARNING: syslog purge blocked -- non-POST request', false, 'SYSLOG');
raise_message('syslog_method_error', __('Invalid request. Please try again.', 'syslog'), MESSAGE_LEVEL_ERROR);
header('Location: utilities.php?header=false');
exit;
}

// csrf_check($fatal) returns bool; $fatal=false tells the helper not to
// die/exit on failure so we can log and redirect with a user-visible
// message ourselves.
if (!function_exists('csrf_check')) {
cacti_log('WARNING: syslog purge blocked -- CSRF validation unavailable', false, 'SYSLOG');
raise_message('syslog_csrf_unavailable', __('Invalid request. Please try again.', 'syslog'), MESSAGE_LEVEL_ERROR);
header('Location: utilities.php?header=false');
exit;
}

if (!csrf_check(false)) {
cacti_log('WARNING: syslog purge blocked -- CSRF token validation failed', false, 'SYSLOG');
raise_message('syslog_csrf_error', __('Invalid request. Please try again.', 'syslog'), MESSAGE_LEVEL_ERROR);
header('Location: utilities.php?header=false');
exit;
}

$records = 0;

syslog_db_execute('DELETE FROM syslog_hosts
Expand Down Expand Up @@ -1642,7 +1666,7 @@ function syslog_utilities_action($action) {

raise_message('syslog_info', __('There were %s Device records removed from the Syslog database', $records, 'syslog'), MESSAGE_LEVEL_INFO);

header('Location: utilities.php');
header('Location: utilities.php?header=false');
exit;
}

Expand All @@ -1660,7 +1684,52 @@ function syslog_utilities_list() {

<tr class='even'>
<td>
<a class='hyperLink' href='utilities.php?action=purge_syslog_hosts'><?php print __('Purge Syslog Devices', 'syslog'); ?></a>
<input id='syslog_purge_hosts' type='button' value='<?php print __esc('Purge Syslog Devices', 'syslog'); ?>'>
<div id='syslog_purge_dialog' style='display:none;'>
<p><?php print __esc('Are you sure you want to purge stale Syslog devices?', 'syslog'); ?></p>
</div>
<script type='text/javascript'>
$(function() {
$('#syslog_purge_hosts').on('click', function() {
$('#syslog_purge_dialog').dialog({
title: <?php print syslog_json_safe(__('Confirm Purge', 'syslog')); ?>,
minHeight: 80,
minWidth: 400,
resizable: false,
draggable: true,
buttons: {
'Cancel': {
text: <?php print syslog_json_safe(__('Cancel', 'syslog')); ?>,
id: 'btnPurgeCancel',
click: function() {
$(this).dialog('close');
}
},
'Continue': {
text: <?php print syslog_json_safe(__('Continue', 'syslog')); ?>,
id: 'btnPurgeContinue',
click: function() {
$(this).dialog('close');

/* set the URL */
var strURL = 'utilities.php?header=false';

/* ensure that the csrf magic is appended */
var json = {action: 'purge_syslog_hosts'};
json.__csrf_magic = csrfMagicToken;

if (typeof postUrl == 'function') {
postUrl({url: strURL}, json);
} else {
loadPageUsingPost(strURL, json);
}
}
}
}
});
});
});
</script>
</td>
<td>
<?php print __('This menu pick provides a means to remove Devices that are no longer reporting into Cacti\'s syslog server.', 'syslog'); ?>
Expand Down
10 changes: 5 additions & 5 deletions syslog.php
Original file line number Diff line number Diff line change
Expand Up @@ -1184,11 +1184,11 @@ function syslog_filter($sql_where, $tab) {
?>
<script type='text/javascript'>
initSyslogMain({
pageTab: '<?php print get_request_var('tab'); ?>',
placeHolder: '<?php print __esc('Enter a search term', 'syslog'); ?>',
noneSelectedText: '<?php print __esc('Select Device(s)', 'syslog'); ?>',
devicesSelectedText: '<?php print __esc('Devices Selected', 'syslog'); ?>',
allDevicesText: '<?php print __esc('All Devices Selected', 'syslog'); ?>'
pageTab: <?php print syslog_json_safe(get_request_var('tab')); ?>,
placeHolder: <?php print syslog_json_safe(__('Enter a search term', 'syslog')); ?>,
noneSelectedText: <?php print syslog_json_safe(__('Select Device(s)', 'syslog')); ?>,
devicesSelectedText: <?php print syslog_json_safe(__('Devices Selected', 'syslog')); ?>,
allDevicesText: <?php print syslog_json_safe(__('All Devices Selected', 'syslog')); ?>
});
</script>
<?php
Expand Down
Loading
Loading