From aed2c4dedc6e6f0dbe1e3f5f5e58b350d4557e47 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Tue, 10 Mar 2026 13:51:15 -0700 Subject: [PATCH] hardening: enforce POST+CSRF for purge syslog devices utility Refs #259 Signed-off-by: Thomas Vincent --- setup.php | 65 ++++++++++- tests/regression/issue259_csrf_purge_test.php | 106 ++++++++++++++++++ 2 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 tests/regression/issue259_csrf_purge_test.php diff --git a/setup.php b/setup.php index a90cb01..507c229 100644 --- a/setup.php +++ b/setup.php @@ -1566,6 +1566,25 @@ function syslog_utilities_action($action) { } if ($action == 'purge_syslog_hosts') { + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + raise_message('syslog_error', __('Invalid request. This action requires a CSRF protected POST.', 'syslog'), MESSAGE_LEVEL_ERROR); + header('Location: utilities.php'); + exit; + } + + if (function_exists('csrf_check')) { + if (!csrf_check(false)) { + raise_message('syslog_error', __('Invalid request. This action requires a CSRF protected POST.', 'syslog'), MESSAGE_LEVEL_ERROR); + header('Location: utilities.php'); + exit; + } + } else { + cacti_log('WARNING: syslog purge blocked -- CSRF validation unavailable', false, 'SYSLOG'); + raise_message('syslog_error', __('Invalid request. Please try again.', 'syslog'), MESSAGE_LEVEL_ERROR); + header('Location: utilities.php'); + exit; + } + $records = 0; syslog_db_execute('DELETE FROM syslog_hosts @@ -1618,7 +1637,50 @@ function syslog_utilities_list() { - + '> + + @@ -1626,4 +1688,3 @@ function syslog_utilities_list() { breakout in HTML script context +if (strpos($setup, 'JSON_HEX_TAG') === false) { + fwrite(STDERR, "json_encode() must use JSON_HEX_TAG to prevent script-context breakout.\n"); + exit(1); +} + +if (strpos($setup, 'JSON_HEX_AMP') === false) { + fwrite(STDERR, "json_encode() must use JSON_HEX_AMP to escape ampersands in script context.\n"); + exit(1); +} + +if (strpos($setup, 'JSON_HEX_APOS') === false) { + fwrite(STDERR, "json_encode() must use JSON_HEX_APOS.\n"); + exit(1); +} + +if (strpos($setup, 'JSON_HEX_QUOT') === false) { + fwrite(STDERR, "json_encode() must use JSON_HEX_QUOT.\n"); + exit(1); +} + +// Verify user-facing message does not expose CSRF internals (log message may use "CSRF") +if (strpos($setup, "raise_message('syslog_error', __('CSRF") !== false) { + fwrite(STDERR, "User-facing raise_message must not expose CSRF internals to end users.\n"); + exit(1); +} + +// Verify generic user-facing message is present +if (strpos($setup, "Invalid request. Please try again.") === false) { + fwrite(STDERR, "Fail-closed branch must use generic 'Invalid request. Please try again.' message.\n"); + exit(1); +} + +// Verify fail-closed raise_message uses MESSAGE_LEVEL_ERROR severity +if (strpos($setup, "raise_message('syslog_error', __('Invalid request. Please try again.', 'syslog'), MESSAGE_LEVEL_ERROR)") === false) { + fwrite(STDERR, "Fail-closed branch raise_message must use MESSAGE_LEVEL_ERROR severity.\n"); + exit(1); +} + +// Verify log message does not expose internal function name +if (strpos($setup, 'csrf_check() unavailable') !== false) { + fwrite(STDERR, "Log message must not name internal validation function.\n"); + exit(1); +} + +echo "issue259_csrf_purge_test passed\n";