mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-24 03:57:32 +09:00
* fix(csrf): require POST tokens for item actions * test(csrf): cover every guarded action path * test(csrf): keep legacy guard contract extensible * fix(csrf): hide POST-only targets from crawlers * test(csrf): reject every crawlable state-action link * fix(csrf): fail closed without logging crawler probes * fix(csrf): post data-query item actions safely Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): normalize stripped token separators Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): distinguish method and token failures Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): escape POST action URL attributes Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): delegate POST action events Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): post data template add action Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * style(html): align POST button class Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
73 lines
3.3 KiB
PHP
73 lines
3.3 KiB
PHP
<?php
|
|
/*
|
|
+-------------------------------------------------------------------------+
|
|
| Copyright (C) 2004-2026 The Cacti Group |
|
|
| |
|
|
| This program is free software; you can redistribute it and/or |
|
|
| modify it under the terms of the GNU General Public License |
|
|
| as published by the Free Software Foundation; either version 2 |
|
|
| of the License, or (at your option) any later version. |
|
|
+-------------------------------------------------------------------------+
|
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
/*
|
|
* Delete actions (tree delete_node, host gt_remove/query_remove) fired on GET
|
|
* with no CSRF token, so a cross-origin <img> or link deleted objects using the
|
|
* victim's session cookie. They must arrive by POST with a token now: the
|
|
* server rejects the GET, and the UI submits them through the token POST path.
|
|
*/
|
|
|
|
$root = dirname(__DIR__, 2);
|
|
|
|
test('the server rejects these delete actions on GET without a token', function () use ($root) {
|
|
$src = file_get_contents($root . '/include/global.php');
|
|
|
|
// The GET-method block covers the original delete actions even as the
|
|
// protected list grows to include other state-changing actions.
|
|
foreach (['save', 'update_data', 'changepassword', 'delete_node', 'gt_remove', 'query_remove', 'remove', 'change_leaf'] as $action) {
|
|
expect($src)->toContain("'$action'");
|
|
}
|
|
// the block still requires the csrf token on POST
|
|
expect($src)->toContain("!isset(\$_POST['__csrf_magic'])");
|
|
});
|
|
|
|
test('tree.php deletes a node via a token POST, not GET', function () use ($root) {
|
|
$src = file_get_contents($root . '/tree.php');
|
|
|
|
expect($src)->toContain("\$.post('?action=delete_node', { 'id' : data.node.id, 'tree_id' : \$('#id').val(), '__csrf_magic' : csrfMagicToken })");
|
|
expect($src)->not->toContain("\$.get('?action=delete_node'");
|
|
});
|
|
|
|
test('host.php removes graph templates and data queries via a token POST', function () use ($root) {
|
|
$src = file_get_contents($root . '/host.php');
|
|
|
|
// gt_remove and query_remove no longer build a GET url through loadUrl
|
|
expect($src)->not->toContain("'host.php?action=gt_remove&id='");
|
|
expect($src)->not->toContain("'host.php?action=query_remove&id='");
|
|
// they go through postUrl with the csrf token
|
|
expect($src)->toContain("url: 'host.php?action=gt_remove'");
|
|
expect($src)->toContain("url: 'host.php?action=query_remove'");
|
|
expect(substr_count($src, '__csrf_magic: csrfMagicToken'))->toBeGreaterThanOrEqual(4);
|
|
});
|
|
|
|
test('the automation actions guarded here are not linked by GET anywhere', function () use ($root) {
|
|
/* 'remove' and 'change_leaf' can be required to post because no page emits
|
|
either as a link. An action still reached by a GET link would break the
|
|
moment it joined that list, which is why item_remove is not in it. */
|
|
$linked = [];
|
|
|
|
foreach (glob($root . '/*.php') as $page) {
|
|
$src = file_get_contents($page);
|
|
|
|
foreach (["action=remove'", 'action=remove"', 'action=remove&', 'action=change_leaf'] as $needle) {
|
|
if (str_contains($src, $needle)) {
|
|
$linked[] = basename($page) . ' => ' . $needle;
|
|
}
|
|
}
|
|
}
|
|
|
|
expect($linked)->toBe([]);
|
|
});
|