diff --git a/automation_devices.php b/automation_devices.php index ff704dd40..8b5560924 100644 --- a/automation_devices.php +++ b/automation_devices.php @@ -65,6 +65,8 @@ set_default_action(); switch(grv('action')) { case 'purge': + csrf_require_post(); + purge_discovery_results(); break; @@ -611,6 +613,7 @@ function create_automation_devices_filter() : array { 'display' => __('Purge'), 'action' => 'default', 'title' => __('Purge the Discovered Devices from the Database'), + 'callback' => 'postUrl({ url: \'automation_devices.php?action=purge\' }, { __csrf_magic: csrfMagicToken })' ] ], 'sort' => [ diff --git a/changelog.d/GHSA-x664-x4hf-p9xh.security b/changelog.d/GHSA-x664-x4hf-p9xh.security new file mode 100644 index 000000000..16ff72feb --- /dev/null +++ b/changelog.d/GHSA-x664-x4hf-p9xh.security @@ -0,0 +1 @@ +Require a POST request with a CSRF token for state-changing GET actions (User Log purge, Automation Devices purge, RRDCheck purge, and plugin disable) so they can no longer be triggered cross-site via a GET request diff --git a/include/csrf.php b/include/csrf.php index 957b79cfb..4fb02d860 100644 --- a/include/csrf.php +++ b/include/csrf.php @@ -414,3 +414,20 @@ function csrf_error_callback() : void { exit; } + +/** + * csrf_require_post - reject a non-POST request for a state-changing action. + * + * State-changing actions reached through a direct action= URL must arrive by + * POST so they cannot be triggered cross-site with a GET (for example an + * tag). csrf-magic validates the token on the resulting POST. + * + * @return void + */ +function csrf_require_post() : void { + if (!isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] !== 'POST') { + header('Allow: POST'); + http_response_code(405); + exit; + } +} diff --git a/plugins.php b/plugins.php index 4c5cb2500..8f4246d4f 100644 --- a/plugins.php +++ b/plugins.php @@ -307,6 +307,8 @@ switch($action) { break; case 'disable': + csrf_require_post(); + api_plugin_disable($plugin); header('Location: plugins.php'); @@ -945,6 +947,18 @@ function update_show_current() : void { displayDialog(url, dialogTitle, dialogMessage, dialogForm, buttonContinue, buttonCancel, 80, 400); }); + $('.pidisable').off('click').on('click', function(event) { + event.preventDefault(); + + var url = $(this).attr('href'); + + if (url == '' || url == '#') { + return; + } + + postUrl({ url: url }, { __csrf_magic: csrfMagicToken }); + }); + $('.piforceremove').off('click').on('click', function(event) { event.preventDefault(); diff --git a/rrdcheck.php b/rrdcheck.php index 471c8a990..8704eeab6 100644 --- a/rrdcheck.php +++ b/rrdcheck.php @@ -39,6 +39,8 @@ if (read_config_option('rrdcheck_enable') != 'on') { switch(grv('action')) { case 'purge': + csrf_require_post(); + rrdcheck_purge(); default: @@ -250,10 +252,11 @@ function create_rrdcheck_filter() : array { 'title' => __('Reset filter to default values'), ], 'purge' => [ - 'method' => 'button', - 'display' => __('Purge'), - 'action' => 'default', - 'title' => __('Purge Data Source Checks from the Database'), + 'method' => 'button', + 'display' => __('Purge'), + 'action' => 'default', + 'title' => __('Purge Data Source Checks from the Database'), + 'callback' => 'postUrl({ url: \'rrdcheck.php?action=purge\' }, { __csrf_magic: csrfMagicToken })' ] ], 'sort' => [ diff --git a/user_log.php b/user_log.php index be40ef7d3..1d13f4263 100644 --- a/user_log.php +++ b/user_log.php @@ -29,6 +29,8 @@ set_default_action(); switch (grv('action')) { case 'purge_execute': + csrf_require_post(); + clear_user_log(); raise_message('purge_user_log', __('User Log Purged.'), MESSAGE_LEVEL_INFO); header('location: user_log.php'); @@ -220,7 +222,7 @@ function purge_user_log() : void { $(function() { $('#pc').click(function() { strURL = location.pathname+'?action=purge_execute'; - loadUrl({url:strURL}) + postUrl({url: strURL}, {__csrf_magic: csrfMagicToken}); }); $('#cancel').click(function() {