mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-28 23:57:30 +09:00
* security: consolidate verified GHSA fixes for develop (batch 4) Develop counterparts of the 1.2.x batch-4 fixes in #8144, re-verified against current develop HEAD. - GHSA-4rmr-wvjq-qxc2: include/auth.php Basic Auth and guest branches fall through to the per-page realm check instead of returning early. - GHSA-j67j-wpm4-9g3x: the pre-dispatch update_policy branch in user_admin.php and user_group_admin.php requires a CSRF-token POST. The denylist -> allowlist redesign is tracked for 1.3 in #8143. - GHSA-2p3x-f5jm-rg6p: the View-Graphs -> poller RCE chain is broken by the realm check (4rmr), the update_policy guard (j67j) and shell-free webhits (x5pw). - GHSA-x5pw-cg7j-mj4r: scripts/webhits.pl runs wc through a shell-free list-form open. - GHSA-9737-rqh3-h7fh: names interpolated into raise_message() now use __esc() across the UI; URL- and HTML-bearing messages are excluded. - GHSA-9x9h-2577-9w86: the colordropdown input is built as a node and the label is no longer round-tripped through parseHTML (the list item already used createTextNode on develop). - GHSA-h96j-8xhc-2p3w: remote_agent.php authorizes on $_SERVER['REMOTE_ADDR'], not get_client_addr(), which honours client-settable proxy headers. Regression guards under tests/Unit/Security/{Auth,Csrf,Xss,ShellInjection}. php -l, check-changelog.py and the sink/architectural baselines all pass. * chore: reference PR #8145 on the batch-4 1.3.0-dev CHANGELOG entries * security: resolve develop merge - keep id-only xmlerror unescaped (matches #8141 h745 test) * security(GHSA-j67j): use REQUEST_METHOD POST guard instead of raw \ (hardening-pattern-guard) * security(GHSA-9737): fix review findings - drop double-encoding in __esc, escape missed name sinks, correct plugins format-arg count
409 lines
14 KiB
PHP
409 lines
14 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. |
|
|
| |
|
|
| This program is distributed in the hope that it will be useful, |
|
|
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
| GNU General Public License for more details. |
|
|
+-------------------------------------------------------------------------+
|
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
|
+-------------------------------------------------------------------------+
|
|
| This code is designed, written, and maintained by the Cacti Group. See |
|
|
| about.php and/or the AUTHORS file for specific developer information. |
|
|
+-------------------------------------------------------------------------+
|
|
| http://www.cacti.net/ |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
global $current_user;
|
|
|
|
require_once('global.php');
|
|
|
|
if (!isset($config['cacti_db_version'])) {
|
|
$version = get_cacti_db_version();
|
|
|
|
$config['cacti_db_version'] = $version;
|
|
|
|
if (!defined('CACTI_DB_VERSION')) {
|
|
define('CACTI_DB_VERSION', $version);
|
|
}
|
|
} else {
|
|
$version = $config['cacti_db_version'];
|
|
}
|
|
|
|
$auth_method = read_config_option('auth_method');
|
|
$cookie_user = false;
|
|
|
|
/**
|
|
* Check to see if Cacti authentication is disabled
|
|
* and force Local Authentication on if found
|
|
*/
|
|
check_reset_no_authentication($auth_method);
|
|
|
|
/**
|
|
* Check to see if the Database Cacti version is different
|
|
* from the installed Cacti version and start the install
|
|
* process if found to be different.
|
|
*/
|
|
if (is_install_needed() && !defined('IN_CACTI_INSTALL')) {
|
|
header('Location: ' . CACTI_PATH_URL . 'install/');
|
|
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* The logout page does not require authentication
|
|
* so, short cut the process. login_sso.php is the public SAML2/OpenID
|
|
* redirect+callback endpoint: every request to it is, by definition, still
|
|
* unauthenticated, so it must never hit the login-page redirect below.
|
|
*/
|
|
if (get_current_page() == 'logout.php' || get_current_page() == 'auth_changepassword.php' || get_current_page() == 'auth_resetpassword.php' || get_current_page() == 'login_sso.php') {
|
|
return true;
|
|
}
|
|
|
|
// handle alternate authentication realms
|
|
api_plugin_hook_function('auth_alternate_realms');
|
|
|
|
/**
|
|
* handle change password dialog and auth cookie if not using basic auth
|
|
*/
|
|
if ($auth_method != AUTH_METHOD_BASIC) {
|
|
if (isset($_SESSION[SESS_CHANGE_PASSWORD])) {
|
|
header('Location: ' . CACTI_PATH_URL . 'auth_changepassword.php?ref=' . rawurlencode(validate_redirect_url($_SERVER['HTTP_REFERER'] ?? '', 'index.php')));
|
|
exit;
|
|
}
|
|
|
|
// check for remember me functionality
|
|
if (!isset($_SESSION[SESS_USER_ID])) {
|
|
$cookie_user = check_auth_cookie();
|
|
|
|
if ($cookie_user !== false) {
|
|
if (cacti_auth_transition((int) $cookie_user, 'cookie_restore')) {
|
|
$_SESSION[SESS_USER_ID] = $cookie_user;
|
|
$_SESSION[SESS_USER_AGENT] = $_SERVER['HTTP_USER_AGENT'];
|
|
$_SESSION[SESS_CLIENT_ADDR] = get_client_addr();
|
|
} else {
|
|
// The account may have been locked after cookie validation.
|
|
clear_auth_cookie();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check for basic auth, and if the user has been logged in via the server
|
|
* but their user_id is not set, include the auth_login.php script to
|
|
* process their log in.
|
|
*/
|
|
if ($auth_method == AUTH_METHOD_BASIC && !isset($_SESSION[SESS_USER_ID])) {
|
|
$username = get_basic_auth_username();
|
|
|
|
if ($username !== false) {
|
|
$current_user = db_fetch_row_prepared('SELECT *
|
|
FROM user_auth
|
|
WHERE realm = 2
|
|
AND username = ?',
|
|
[$username]);
|
|
|
|
if (cacti_sizeof($current_user)) {
|
|
if ($current_user['enabled'] != 'on') {
|
|
auth_display_custom_error_message(__('Access Denied! User account disabled.'));
|
|
|
|
exit;
|
|
}
|
|
|
|
if (!auth_user_has_access($current_user)) {
|
|
auth_display_custom_error_message(__('You do not have access to any area of Cacti. Contact your administrator.'));
|
|
|
|
exit;
|
|
}
|
|
|
|
if (!cacti_auth_transition((int) $current_user['id'], 'basic_auth')) {
|
|
auth_display_custom_error_message(__('Access Denied! User account locked.'));
|
|
|
|
exit;
|
|
}
|
|
|
|
$_SESSION[SESS_USER_ID] = $current_user['id'];
|
|
$_SESSION[SESS_USER_AGENT] = $_SERVER['HTTP_USER_AGENT'];
|
|
$_SESSION[SESS_CLIENT_ADDR] = get_client_addr();
|
|
|
|
$client_addr = get_client_addr();
|
|
|
|
cacti_log("LOGIN: User '" . $current_user['username'] . "' authenticated via Basic Authentication from IP Address '" . $client_addr . "'", false, 'AUTH');
|
|
|
|
db_execute_prepared('INSERT IGNORE INTO user_log
|
|
(username, user_id, result, ip, time)
|
|
VALUES (?, ?, 1, ?, NOW())',
|
|
[$username, $current_user['id'], $_SESSION[SESS_CLIENT_ADDR]]);
|
|
|
|
// GHSA-4rmr-wvjq-qxc2: fall through to the per-page realm check below
|
|
// instead of returning, so a Basic Auth login still has its
|
|
// authorization verified for the requested page.
|
|
} else {
|
|
require_once(CACTI_PATH_BASE . '/auth_login.php');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check to see if Cacti is locked out and if it is, block
|
|
* users from accessing the site until lockout is cleared.
|
|
*/
|
|
if (CACTI_WEB && isset($_SESSION[SESS_USER_ID])) {
|
|
$lockout = read_config_option('cacti_lockout_status', true);
|
|
|
|
if ($lockout != '') {
|
|
$lockout = json_decode($lockout, true);
|
|
$admin_user = read_config_option('admin_user');
|
|
$enabled = read_config_option('auth_maint_lockout_type', true);
|
|
|
|
if ($lockout['session'] != session_id()) {
|
|
if (($enabled == 0 && $admin_user != $_SESSION[SESS_USER_ID]) || $enabled == 1) {
|
|
general_header();
|
|
print '<h1>' . __('Cacti is Locked Out for Maintenance') . '</h1>';
|
|
print '<p>' . __('The primary Cacti administrator has locked out the Cacti user interface for maintenance for activities such as upgrades. If you feel this is in error, please contact your Cacti adminnistrator. This lockout will stay in place for approximately 30 minutes.') . '</p>';
|
|
bottom_footer();
|
|
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If the special boolean $guest_account is set for a page, then the guest
|
|
* account can be used. Where this may not be the case is with basic auth
|
|
* where to enter the Cacti website, you must first have a valid account.
|
|
* if that is the case, then use that valid accounts permissions and not
|
|
* the guest account.
|
|
*/
|
|
if (isset($guest_account)) {
|
|
$guest_user_id = get_guest_account();
|
|
|
|
// find guest user
|
|
if (!empty($guest_user_id)) {
|
|
if (empty($_SESSION[SESS_USER_ID])) {
|
|
$_SESSION[SESS_USER_ID] = $guest_user_id;
|
|
$_SESSION[SESS_USER_AGENT] = $_SERVER['HTTP_USER_AGENT'];
|
|
$_SESSION[SESS_CLIENT_ADDR] = get_client_addr();
|
|
}
|
|
|
|
$current_user = db_fetch_row_prepared('SELECT *
|
|
FROM user_auth
|
|
WHERE id = ?',
|
|
[$_SESSION[SESS_USER_ID]]);
|
|
|
|
// GHSA-4rmr-wvjq-qxc2: fall through to the per-page realm check below
|
|
// instead of returning, so the guest account's access to this page is
|
|
// authorized rather than assumed.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If we are a guest user in a non-guest area, wipe credentials
|
|
* user will be redirected back to the login page.
|
|
*/
|
|
if (!isset($guest_account) && isset($_SESSION[SESS_USER_ID])) {
|
|
if (get_guest_account() === $_SESSION[SESS_USER_ID]) {
|
|
kill_session_var(SESS_USER_ID);
|
|
cacti_session_destroy();
|
|
cacti_session_start();
|
|
}
|
|
}
|
|
|
|
if (empty($_SESSION[SESS_USER_ID])) {
|
|
if (isset($auth_json) && $auth_json == true) {
|
|
print json_encode(
|
|
[
|
|
'status' => '500',
|
|
'statusText' => __('Not Logged In'),
|
|
'responseText' => __('You must be logged in to access this area of Cacti.')
|
|
]
|
|
);
|
|
} elseif (isset($auth_text) && $auth_text == true) {
|
|
// handle graph_image.php to respond with text.
|
|
print __('FATAL: You must be logged in to access this area of Cacti.');
|
|
} else {
|
|
require_once(CACTI_PATH_BASE . '/auth_login.php');
|
|
}
|
|
|
|
exit;
|
|
} else {
|
|
if (empty($_SESSION[SESS_USER_2FA]) && db_column_exists('user_auth', 'tfa_enabled')) {
|
|
if (read_config_option('secpass_2fa_enabled') == 'on') {
|
|
if (is_2fa_enabled($_SESSION[SESS_USER_ID])) {
|
|
header('Location: ' . CACTI_PATH_URL . 'auth_2fa.php');
|
|
exit;
|
|
} else {
|
|
$_SESSION[SESS_USER_2FA] = time();
|
|
}
|
|
}
|
|
}
|
|
|
|
$realm_id = 0;
|
|
|
|
if (isset($user_auth_realm_filenames[get_current_page()])) {
|
|
$realm_id = $user_auth_realm_filenames[get_current_page()];
|
|
}
|
|
|
|
// Are we upgrading from a version before 1.2 which has the Install/Upgrade realm 26
|
|
if ($realm_id == 26) {
|
|
// See if we can find any users that are allowed to upgrade
|
|
$install_sql_query = '
|
|
SELECT COUNT(*)
|
|
FROM (
|
|
SELECT realm_id
|
|
FROM user_auth_realm AS uar
|
|
WHERE uar.realm_id = ?';
|
|
|
|
$install_sql_params = [$realm_id];
|
|
|
|
// See if the group realms exist and if so, check if permission exists there too
|
|
if (db_table_exists('user_auth_group_realm') &&
|
|
db_table_exists('user_auth_group') &&
|
|
db_table_exists('user_auth_group_members')) {
|
|
$install_sql_query .= '
|
|
UNION
|
|
SELECT realm_id
|
|
FROM user_auth_group_realm AS uagr
|
|
INNER JOIN user_auth_group_members AS uagm
|
|
ON uagr.group_id=uagm.group_id
|
|
INNER JOIN user_auth_group AS uag
|
|
ON uag.id=uagr.group_id
|
|
WHERE uag.enabled="on"
|
|
AND uagr.realm_id = ?';
|
|
|
|
$install_sql_params = array_merge($install_sql_params, [$realm_id]);
|
|
}
|
|
|
|
$install_sql_query .= '
|
|
) AS authorized';
|
|
|
|
$has_install_user = db_fetch_cell_prepared($install_sql_query, $install_sql_params);
|
|
|
|
if (!$has_install_user) {
|
|
/* We did not find any existing users who can upgrade/install so add any admin *
|
|
* who has access to the system settings (realm 15) by default */
|
|
db_execute('INSERT INTO `user_auth_realm` (realm_id, user_id)
|
|
SELECT 26 as realm_id, ua.id
|
|
FROM user_auth ua
|
|
INNER JOIN user_auth_realm uar
|
|
ON uar.user_id=ua.id
|
|
LEFT JOIN user_auth_realm uar2
|
|
ON uar2.user_id=ua.id
|
|
AND uar2.realm_id=26
|
|
WHERE uar.realm_id=15
|
|
AND uar2.user_id IS NULL');
|
|
}
|
|
}
|
|
|
|
if ($realm_id > 0) {
|
|
$auth_sql_query = '
|
|
SELECT COUNT(*)
|
|
FROM (
|
|
SELECT realm_id
|
|
FROM user_auth_realm AS uar
|
|
WHERE uar.user_id = ?
|
|
AND uar.realm_id = ?';
|
|
|
|
$auth_sql_params = [$_SESSION[SESS_USER_ID], $realm_id];
|
|
|
|
/* Because we now expect installation to be done by authorized users, check the group_realm *
|
|
* exists before using it as this may not be present if upgrading from pre-1.x */
|
|
if (db_table_exists('user_auth_group_realm') &&
|
|
db_table_exists('user_auth_group') &&
|
|
db_table_exists('user_auth_group_members')) {
|
|
$auth_sql_query .= '
|
|
UNION
|
|
SELECT realm_id
|
|
FROM user_auth_group_realm AS uagr
|
|
INNER JOIN user_auth_group_members AS uagm
|
|
ON uagr.group_id = uagm.group_id
|
|
INNER JOIN user_auth_group AS uag
|
|
ON uag.id = uagr.group_id
|
|
WHERE uag.enabled = "on"
|
|
AND uagm.user_id = ?
|
|
AND uagr.realm_id = ?';
|
|
|
|
$auth_sql_params = array_merge($auth_sql_params, [$_SESSION[SESS_USER_ID], $realm_id]);
|
|
}
|
|
|
|
$auth_sql_query .= '
|
|
) AS authorized';
|
|
|
|
$authorized = db_fetch_cell_prepared($auth_sql_query, $auth_sql_params);
|
|
} else {
|
|
$authorized = false;
|
|
}
|
|
|
|
if ($realm_id != -1 && !$authorized) {
|
|
if (api_plugin_hook_function('custom_denied', OPER_MODE_NATIVE) == OPER_MODE_RESKIN) {
|
|
exit;
|
|
}
|
|
|
|
if (isset($_SERVER['HTTP_REFERER'])) {
|
|
$goBack = "<td colspan='2' class='center'>[<a href='" . validate_redirect_url($_SERVER['HTTP_REFERER'], $_SERVER['SCRIPT_NAME']) . "'>" . __('Return') . "</a> | <a href='" . CACTI_PATH_URL . "logout.php'>" . __('Login Again') . '</a>]</td>';
|
|
} elseif ($auth_method != AUTH_METHOD_BASIC) {
|
|
$goBack = "<td colspan='2' class='center'>[<a href='" . CACTI_PATH_URL . "logout.php'>" . __('Login Again') . '</a>]</td>';
|
|
} else {
|
|
$goBack = '';
|
|
}
|
|
|
|
raise_ajax_permission_denied();
|
|
|
|
$title_header = __('Permission Denied');
|
|
$title_body = '<p>' . __('You are not permitted to access this section of Cacti.') . '</p><p>' . __('If you feel that this is an error. Please contact your Cacti Administrator.');
|
|
|
|
if ($realm_id == 26) {
|
|
$title_header = __('Installation In Progress');
|
|
$title_body = '<p>' . __('There is an Installation or Upgrade in progress.') . '</p><p>' . __('Only Cacti Administrators with Install/Upgrade privilege may login at this time') . '</p>';
|
|
}
|
|
|
|
print "<!DOCTYPE html>\n";
|
|
print "<html>\n";
|
|
print "<head>\n";
|
|
html_common_header($title_header);
|
|
print "</head>\n";
|
|
print "<body class='logoutBody'>
|
|
<div class='logoutLeft'></div>
|
|
<div class='logoutCenter'>
|
|
<div class='logoutArea'>
|
|
<div class='cactiLogoutLogo'></div>
|
|
<legend>" . $title_header . "</legend>
|
|
<div class='logoutTitle'>
|
|
" . $title_body . '
|
|
</p>
|
|
<center>' . $goBack . "</center>
|
|
</div>
|
|
<div class='logoutErrors'></div>
|
|
</div>
|
|
<div class='versionInfo'>" . __('Version') . ' ' . $version . ' | ' . COPYRIGHT_YEARS_SHORT . "</div>
|
|
</div>
|
|
<div class='logoutRight'></div>
|
|
<script type='text/javascript' " . CactiSecureHeaders::getNonceAttribute() . ">
|
|
$(function() {
|
|
$('.loginLeft').css('width',parseInt($(window).width()*0.33)+'px');
|
|
$('.loginRight').css('width',parseInt($(window).width()*0.33)+'px');
|
|
});
|
|
</script>\n";
|
|
include_once('global_session.php');
|
|
print "</body>
|
|
</html>\n";
|
|
|
|
exit;
|
|
}
|
|
|
|
$current_user = db_fetch_row_prepared('SELECT *
|
|
FROM user_auth
|
|
WHERE id = ?',
|
|
[$_SESSION[SESS_USER_ID]]);
|
|
}
|