|
| 1 | +<?php |
| 2 | + |
| 3 | +$setup = file_get_contents(dirname(__DIR__, 2) . '/setup.php'); |
| 4 | + |
| 5 | +if ($setup === false) { |
| 6 | + fwrite(STDERR, "Failed to read setup.php\n"); |
| 7 | + exit(1); |
| 8 | +} |
| 9 | + |
| 10 | +$required = array( |
| 11 | + "if (\$_SERVER['REQUEST_METHOD'] !== 'POST')", |
| 12 | + "if (function_exists('csrf_check'))", |
| 13 | + "if (!csrf_check(false))", |
| 14 | + "__csrf_magic: csrfMagicToken" |
| 15 | +); |
| 16 | + |
| 17 | +foreach ($required as $snippet) { |
| 18 | + if (strpos($setup, $snippet) === false) { |
| 19 | + fwrite(STDERR, "Missing expected CSRF hardening snippet: $snippet\n"); |
| 20 | + exit(1); |
| 21 | + } |
| 22 | +} |
| 23 | + |
| 24 | +if (strpos($setup, "href='utilities.php?action=purge_syslog_hosts'") !== false) { |
| 25 | + fwrite(STDERR, "Legacy GET purge link still present.\n"); |
| 26 | + exit(1); |
| 27 | +} |
| 28 | + |
| 29 | +// Verify fail-closed: the else branch (csrf_check unavailable) must reject, not fall through. |
| 30 | +// Assert globally safe properties rather than parsing the else block via brittle regex. |
| 31 | + |
| 32 | +// The fallback path must not attempt manual token checking |
| 33 | +if (strpos($setup, "\$_POST['__csrf_magic']") !== false) { |
| 34 | + fwrite(STDERR, "Fallback CSRF branch must not check token presence; must fail closed.\n"); |
| 35 | + exit(1); |
| 36 | +} |
| 37 | + |
| 38 | +// The fallback path must log the blocked attempt |
| 39 | +if (strpos($setup, "cacti_log('WARNING: syslog purge blocked") === false) { |
| 40 | + fwrite(STDERR, "Fail-closed branch must call cacti_log() to audit blocked purge attempts.\n"); |
| 41 | + exit(1); |
| 42 | +} |
| 43 | + |
| 44 | +// Log message must name the specific failure reason for incident response |
| 45 | +if (strpos($setup, 'CSRF validation unavailable') === false) { |
| 46 | + fwrite(STDERR, "Log message must specify 'CSRF validation unavailable' for operational clarity.\n"); |
| 47 | + exit(1); |
| 48 | +} |
| 49 | + |
| 50 | +// Verify JS confirm() uses json_encode, not __esc() inside JS string |
| 51 | +if (preg_match("/confirm\(\s*'/", $setup)) { |
| 52 | + fwrite(STDERR, "JS confirm() must use json_encode() for safe encoding, not __esc() in a quoted string.\n"); |
| 53 | + exit(1); |
| 54 | +} |
| 55 | + |
| 56 | +if (strpos($setup, 'json_encode(__(') === false) { |
| 57 | + fwrite(STDERR, "Expected json_encode(__(...)) for JS-safe encoding of confirm message.\n"); |
| 58 | + exit(1); |
| 59 | +} |
| 60 | + |
| 61 | +// Verify json_encode uses JSON_HEX_TAG to prevent </script> breakout in HTML script context |
| 62 | +if (strpos($setup, 'JSON_HEX_TAG') === false) { |
| 63 | + fwrite(STDERR, "json_encode() must use JSON_HEX_TAG to prevent script-context breakout.\n"); |
| 64 | + exit(1); |
| 65 | +} |
| 66 | + |
| 67 | +if (strpos($setup, 'JSON_HEX_AMP') === false) { |
| 68 | + fwrite(STDERR, "json_encode() must use JSON_HEX_AMP to escape ampersands in script context.\n"); |
| 69 | + exit(1); |
| 70 | +} |
| 71 | + |
| 72 | +if (strpos($setup, 'JSON_HEX_APOS') === false) { |
| 73 | + fwrite(STDERR, "json_encode() must use JSON_HEX_APOS.\n"); |
| 74 | + exit(1); |
| 75 | +} |
| 76 | + |
| 77 | +if (strpos($setup, 'JSON_HEX_QUOT') === false) { |
| 78 | + fwrite(STDERR, "json_encode() must use JSON_HEX_QUOT.\n"); |
| 79 | + exit(1); |
| 80 | +} |
| 81 | + |
| 82 | +// Verify user-facing message does not expose CSRF internals (log message may use "CSRF") |
| 83 | +if (strpos($setup, "raise_message('syslog_error', __('CSRF") !== false) { |
| 84 | + fwrite(STDERR, "User-facing raise_message must not expose CSRF internals to end users.\n"); |
| 85 | + exit(1); |
| 86 | +} |
| 87 | + |
| 88 | +// Verify generic user-facing message is present |
| 89 | +if (strpos($setup, "Invalid request. Please try again.") === false) { |
| 90 | + fwrite(STDERR, "Fail-closed branch must use generic 'Invalid request. Please try again.' message.\n"); |
| 91 | + exit(1); |
| 92 | +} |
| 93 | + |
| 94 | +// Verify fail-closed raise_message uses MESSAGE_LEVEL_ERROR severity |
| 95 | +if (strpos($setup, "raise_message('syslog_error', __('Invalid request. Please try again.', 'syslog'), MESSAGE_LEVEL_ERROR)") === false) { |
| 96 | + fwrite(STDERR, "Fail-closed branch raise_message must use MESSAGE_LEVEL_ERROR severity.\n"); |
| 97 | + exit(1); |
| 98 | +} |
| 99 | + |
| 100 | +// Verify log message does not expose internal function name |
| 101 | +if (strpos($setup, 'csrf_check() unavailable') !== false) { |
| 102 | + fwrite(STDERR, "Log message must not name internal validation function.\n"); |
| 103 | + exit(1); |
| 104 | +} |
| 105 | + |
| 106 | +echo "issue259_csrf_purge_test passed\n"; |
0 commit comments