Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions auth_changepassword.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@
}

if (isset($_SERVER['HTTP_REFERER'])) {
header('Location: ' . $_SERVER['HTTP_REFERER']);
$_ref = sanitize_uri($_SERVER['HTTP_REFERER']);
$_ref_host = parse_url($_ref, PHP_URL_HOST);
$_srv_host = preg_replace('/:\d+$/', '', $_SERVER['HTTP_HOST']);
header('Location: ' . (($_ref_host === null || $_ref_host === $_srv_host) ? $_ref : 'index.php'));
} else {
header('Location: index.php');
}
Expand All @@ -108,7 +111,10 @@
cacti_cookie_logout();

if (isset($_SERVER['HTTP_REFERER'])) {
header('Location: ' . $_SERVER['HTTP_REFERER']);
$_ref = sanitize_uri($_SERVER['HTTP_REFERER']);
$_ref_host = parse_url($_ref, PHP_URL_HOST);
$_srv_host = preg_replace('/:\d+$/', '', $_SERVER['HTTP_HOST']);
header('Location: ' . (($_ref_host === null || $_ref_host === $_srv_host) ? $_ref : 'index.php'));
} else {
header('Location: index.php');
}
Expand Down Expand Up @@ -387,7 +393,9 @@
</tr>
<tr>
<td class='nowrap' colspan='2'><input type='submit' class='ui-button ui-corner-all ui-widget' value='<?php print __esc('Save'); ?>'>
<?php print $user['must_change_password'] != 'on' ? "<input type='button' class='ui-button ui-corner-all ui-widget' onClick='document.location=\"$return\"' value='". __esc('Return') . "'>":"";?>
<?php if ($user['must_change_password'] != 'on') { ?>
<input type='button' class='ui-button ui-corner-all ui-widget' onClick='document.location=<?php print json_encode((string) $return); ?>' value='<?php print __esc('Return'); ?>'>
<?php } ?>
</td>
</tr>
</table>
Expand Down
2 changes: 1 addition & 1 deletion auth_profile.php
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ function settings_javascript() {
<script type='text/javascript'>

var themeFonts = <?php print read_config_option('font_method');?>;
var currentTab = '<?php print get_nfilter_request_var('tab');?>';
var currentTab = <?php print json_encode((string) get_nfilter_request_var('tab'));?>;
var currentTheme = '<?php print get_selected_theme();?>';
var currentLang = '<?php print read_config_option('user_language');?>';
var authMethod = '<?php print read_config_option('auth_method');?>';
Expand Down
4 changes: 2 additions & 2 deletions lib/html_graph.php
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,8 @@ function html_graph_preview_filter($page, $action, $devices_where = '', $templat
var graph_start = <?php print get_current_graph_start();?>;
var graph_end = <?php print get_current_graph_end();?>;
var timeOffset = <?php print date('Z');?>;
var pageAction = '<?php print $action;?>';
var graphPage = '<?php print $page;?>';
var pageAction = <?php print json_encode($action);?>;
var graphPage = <?php print json_encode($page);?>;
var date1Open = false;
var date2Open = false;

Expand Down
5 changes: 4 additions & 1 deletion link.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
// Prevent redirect loops
if (isset($_SERVER['HTTP_REFERER'])) {
if (strpos($_SERVER['HTTP_REFERER'], 'link.php') === false) {
$referer = $_SERVER['HTTP_REFERER'];
$raw = sanitize_uri($_SERVER['HTTP_REFERER']);
$ref_host = parse_url($raw, PHP_URL_HOST);
$srv_host = preg_replace('/:\d+$/', '', $_SERVER['HTTP_HOST']);
$referer = ($ref_host === null || $ref_host === $srv_host) ? $raw : 'index.php';
$_SESSION['link_referer'] = $referer;
} elseif (isset($_SESSION['link_referer'])) {
$referer = sanitize_uri($_SESSION['link_referer']);
Expand Down
81 changes: 81 additions & 0 deletions tests/Unit/JsContextHardeningTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?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 |
+-------------------------------------------------------------------------+
*/

/*
* Regression checks for JavaScript-context output hardening.
*
* These tests ensure request/session derived values are encoded or normalized
* before being embedded in JavaScript.
*/

$authProfilePath = __DIR__ . '/../../auth_profile.php';
$authResetpasswordPath = __DIR__ . '/../../auth_resetpassword.php';
$authChangepasswordPath = __DIR__ . '/../../auth_changepassword.php';
$pluginsPath = __DIR__ . '/../../plugins.php';
$htmlGraphPath = __DIR__ . '/../../lib/html_graph.php';
$dataDebugPath = __DIR__ . '/../../data_debug.php';

test('auth_profile encodes tab for JavaScript and redirect URL', function () use ($authProfilePath) {
$contents = file_get_contents($authProfilePath);

expect($contents)->toContain("json_encode((string) grv('tab'))");
expect($contents)->toContain("gfrv('tab', FILTER_VALIDATE_REGEXP");
expect($contents)->toContain('rawurlencode($currentTab)');
});

test('plugins page normalizes state and encodes sort column in JavaScript', function () use ($pluginsPath) {
$contents = file_get_contents($pluginsPath);

expect($contents)->toContain("json_encode((string) grv('sort_column'))");
expect($contents)->toContain("var tableState = <?php print (int) grv('state'); ?>;");
expect($contents)->not->toContain("var tableState = <?php print grv('state'); ?>");
});

test('graph list view uses JSON and sanitized CSV for graph list', function () use ($htmlGraphPath) {
$contents = file_get_contents($htmlGraphPath);

expect($contents)->toContain('$graph_list_js = []');
expect($contents)->toContain('ctype_digit($item)');
expect($contents)->toContain('json_encode($graph_list_js)');
expect($contents)->toContain("graph_list=<?php print \$graph_list_csv; ?>");
expect($contents)->not->toContain("new Array(<?php print grv('graph_list'); ?>)");
});

test('graph pages encode JS-bound PHP strings safely', function () use ($htmlGraphPath) {
$contents = file_get_contents($htmlGraphPath);

expect($contents)->toContain('var pageAction = <?php print json_encode($action); ?>');
expect($contents)->toContain('var graphPage = <?php print json_encode($page); ?>');
expect($contents)->toContain("json_encode((string) \$suffix)");
});

test('data debug escapes tooltip title values before rendering', function () use ($dataDebugPath) {
$contents = file_get_contents($dataDebugPath);

expect($contents)->toContain('$value_title = htmle((string) $value);');
});

test('auth reset password encodes return location in onclick handlers', function () use ($authResetpasswordPath) {
$contents = file_get_contents($authResetpasswordPath);

expect($contents)->toContain("document.location=<?php print json_encode((string) \$return); ?>");
expect($contents)->not->toContain("document.location=\"<?php print \$return; ?>\"");
});

test('auth change password encodes return location in onclick handler', function () use ($authChangepasswordPath) {
$contents = file_get_contents($authChangepasswordPath);

expect($contents)->toContain("document.location=<?php print json_encode((string) \$return); ?>");
expect($contents)->not->toContain("onClick='document.location=\\\"\$return\\\"'");
});