hardening: enforce POST+CSRF for purge syslog devices utility#318
hardening: enforce POST+CSRF for purge syslog devices utility#318somethingwithproof wants to merge 3 commits intoCacti:developfrom
Conversation
Refs Cacti#259 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
There was a problem hiding this comment.
Pull request overview
This PR hardens the Syslog plugin’s destructive purge_syslog_hosts utilities action to mitigate CSRF risk by requiring a CSRF-protected POST, and updates the UI flow to submit a POST (instead of a GET link). It also adds a regression script to guard against reintroducing the insecure behavior.
Changes:
- Enforce
POSTand validate CSRF viacsrf_check(false)before executing the purge deletes. - Replace the utilities GET link with a jQuery UI confirmation dialog that submits a POST including
__csrf_magic. - Add a regression script asserting the hardening behavior and related UI/encoding expectations.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| setup.php | Adds POST+CSRF enforcement for purge action; replaces purge link with a dialog-driven POST flow including CSRF token. |
| tests/regression/issue259_csrf_purge_test.php | Adds a regression script that scans setup.php for required hardening snippets and safe UI/JS encoding patterns. |
setup.php
Outdated
| 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); |
There was a problem hiding this comment.
The user-facing error message for invalid purge requests includes the phrase "CSRF protected POST". To avoid leaking security mechanism details (and to align with the regression test intent that end-user messages should be generic), consider using a generic message (e.g., "Invalid request. Please try again.") and logging the specific failure reason via cacti_log() instead.
| 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); | |
| cacti_log('WARNING: syslog purge blocked -- invalid request method', false, 'SYSLOG'); | |
| raise_message('syslog_error', __('Invalid request. Please try again.', 'syslog'), MESSAGE_LEVEL_ERROR); | |
| header('Location: utilities.php'); | |
| exit; | |
| } | |
| if (function_exists('csrf_check')) { | |
| if (!csrf_check(false)) { | |
| cacti_log('WARNING: syslog purge blocked -- CSRF validation failed', false, 'SYSLOG'); | |
| raise_message('syslog_error', __('Invalid request. Please try again.', 'syslog'), MESSAGE_LEVEL_ERROR); |
| 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; | ||
| } |
There was a problem hiding this comment.
PR description mentions an "__csrf_magic fallback if csrf helper is unavailable", but the current else-branch when csrf_check() is unavailable hard-blocks the purge action. Either implement the documented fallback behavior (so purge can still work on supported environments without csrf_check) or update the PR description/tests to reflect the intentional fail-closed behavior.
| } | ||
|
|
||
| // Verify user-facing message does not expose CSRF internals (log message may use "CSRF") | ||
| if (strpos($setup, "raise_message('syslog_error', __('CSRF") !== false) { |
There was a problem hiding this comment.
The regression assertion intended to ensure user-facing errors do not expose CSRF internals is too narrow: it only checks for the literal substring "raise_message('syslog_error', __('CSRF", so it will not fail if the message contains "CSRF" later in the string. Consider strengthening this check to detect any raise_message() call whose translated message contains "CSRF" (or similar security-internal terms).
| if (strpos($setup, "raise_message('syslog_error', __('CSRF") !== false) { | |
| if (preg_match('/raise_message\s*\(\s*[^,]+,\s*__\(\s*([\'"])[^\'"]*CSRF[^\'"]*\1/si', $setup)) { |
f595568 to
75e4ad6
Compare
|
@TheWitness could you take a look? This is the re-opened CSRF+POST hardening for the purge devices utility (original #265), now rebased onto develop. All merge conflicts resolved. |
…eader - Distinct raise_message IDs per failure mode (syslog_method_error, syslog_csrf_error, syslog_csrf_unavailable) so log triage can differentiate non-POST, invalid token, and missing-helper paths - Add cacti_log entry on the non-POST rejection path so the audit trail is symmetric with the other two fail-closed branches - Document csrf_check($fatal=false) arg semantics inline so future readers see the helper contract - Rename the regression test comment block to call out explicitly that it is a source-scan lint, not a behavioral test; flag follow-up for real behavioral coverage once a DB-backed test harness exists Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Summary
Re-opens work from #265 (self-closed, never merged into main). Enforces POST + CSRF token validation before the
purge_syslog_hostsutility action executes theDELETE FROM syslog_hostsquery, and replaces the GET anchor link with a jQuery UI dialog + POST flow.Changes
setup.php— action handler and UI:REQUEST_METHOD === 'POST'before executing the purge; GET/HEAD requests log a warning and redirect back toutilities.php.csrf_check()is available, callcsrf_check(false)(the$fatal=falsearg tells the helper to return false on failure rather than die/exit, so we can log and redirect with a user-visible message).syslog_method_error,syslog_csrf_error,syslog_csrf_unavailable) so log triage can differentiate non-POST, invalid token, and missing-helper paths.<a class='hyperLink' href='utilities.php?action=purge_syslog_hosts'>GET link with a jQuery UI dialog matching thedisplayDialog()pattern from Cacti core. The dialog posts viapostUrl()/loadPageUsingPost()with__csrf_magic: csrfMagicToken.json_encode(..., JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT)to prevent</script>breakout.tests/regression/issue259_csrf_purge_test.php— source-scan lint:csrf_check+ JSON hex flag strings exist in setup.php.Validation
php -l setup.phpphp tests/regression/issue259_csrf_purge_test.phpFixes #259