Refactor web application security measures, closes https://github.com/opnsense/core/issues/5481

This commit is contained in:
Ad Schellevis 2022-01-17 17:14:02 +01:00
parent df7882cfa9
commit 62f1a9d811
6 changed files with 78 additions and 160 deletions

View File

@ -34,128 +34,9 @@
require_once("interfaces.inc");
require_once("util.inc");
// Will be changed to false if security checks fail
$security_passed = true;
/* If this function doesn't exist, we're being called from Captive Portal or
another internal subsystem which does not include authgui.inc */
if (function_exists("display_error_form") && !isset($config['system']['webgui']['nodnsrebindcheck'])) {
/* DNS ReBinding attack prevention */
$found_host = false;
/* either an IPv6 address with or without an alternate port */
if (strstr($_SERVER['HTTP_HOST'], "]")) {
$http_host_port = explode("]", $_SERVER['HTTP_HOST']);
/* v6 address has more parts, drop the last part */
if (count($http_host_port) > 1) {
array_pop($http_host_port);
$http_host = str_replace(array("[", "]"), "", implode(":", $http_host_port));
} else {
$http_host = str_replace(array("[", "]"), "", implode(":", $http_host_port));
}
} else {
$http_host = explode(":", $_SERVER['HTTP_HOST']);
$http_host = $http_host[0];
}
if (
is_ipaddr($http_host) || $_SERVER['SERVER_ADDR'] == "127.0.0.1" ||
strcasecmp($http_host, "localhost") == 0 or $_SERVER['SERVER_ADDR'] == "::1"
) {
$found_host = true;
}
if (
strcasecmp($http_host, $config['system']['hostname'] . "." . $config['system']['domain']) == 0 ||
strcasecmp($http_host, $config['system']['hostname']) == 0
) {
$found_host = true;
}
if (!empty($config['system']['webgui']['althostnames']) && !$found_host) {
$althosts = explode(" ", $config['system']['webgui']['althostnames']);
foreach ($althosts as $ah) {
if (strcasecmp($ah, $http_host) == 0 or strcasecmp($ah, $_SERVER['SERVER_ADDR']) == 0) {
$found_host = true;
break;
}
}
}
if ($found_host == false) {
if (!security_checks_disabled()) {
display_error_form(sprintf(gettext("A potential %sDNS Rebind attack%s has been detected.%sTry to access the router by IP address instead of by hostname."), '<a href="http://en.wikipedia.org/wiki/DNS_rebinding">', '</a>', '<br />'));
exit;
}
$security_passed = false;
}
}
// If the HTTP_REFERER is something other than ourselves then disallow.
if (function_exists("display_error_form") && !isset($config['system']['webgui']['nohttpreferercheck'])) {
if (isset($_SERVER['HTTP_REFERER'])) {
if (file_exists('/tmp/setupwizard_lastreferrer')) {
if ($_SERVER['HTTP_REFERER'] == file_get_contents('/tmp/setupwizard_lastreferrer')) {
unlink('/tmp/setupwizard_lastreferrer');
header("Refresh: 1; url=index.php");
echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
echo "<html><head><title>" . gettext("Redirecting...") . "</title></head><body>" . gettext("Redirecting to the dashboard...") . "</body></html>";
exit;
}
}
$found_host = false;
$referrer_host = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST);
$referrer_host = str_replace(array("[", "]"), "", $referrer_host);
if ($referrer_host) {
if (
strcasecmp($referrer_host, $config['system']['hostname'] . "." . $config['system']['domain']) == 0 ||
strcasecmp($referrer_host, $config['system']['hostname']) == 0
) {
$found_host = true;
}
if (!empty($config['system']['webgui']['althostnames']) && !$found_host) {
$althosts = explode(" ", $config['system']['webgui']['althostnames']);
foreach ($althosts as $ah) {
if (strcasecmp($referrer_host, $ah) == 0) {
$found_host = true;
break;
}
}
}
if (!$found_host) {
$found_host = isAuthLocalIP($referrer_host);
if ($referrer_host == "127.0.0.1" || $referrer_host == "localhost") {
// allow SSH port forwarded connections and links from localhost
$found_host = true;
}
}
}
if ($found_host == false) {
if (!security_checks_disabled()) {
display_error_form(sprintf(
gettext('The HTTP_REFERER "%s" does not match the predefined settings. You can disable this check if needed under System: Settings: Administration.'),
html_safe($_SERVER['HTTP_REFERER'])
));
exit;
}
$security_passed = false;
}
} else {
$security_passed = false;
}
}
if (function_exists("display_error_form") && $security_passed) {
/* Security checks passed, so it should be OK to turn them back on */
restore_security_checks();
}
unset($security_passed);
$groupindex = index_groups();
$userindex = index_users();
/**
* check if $http_host is a local configured ip address
*/

View File

@ -61,7 +61,69 @@ function set_language()
bind_textdomain_codeset($textdomain, $lang_encoding);
}
function session_auth(&$Login_Error)
/* DNS ReBinding attack prevention, return true when rebind detected*/
function check_security_dns_rebind()
{
global $config;
if (!isset($config['system']['webgui']['nodnsrebindcheck'])) {
/* either an IPv6 address with or without an alternate port */
if (strstr($_SERVER['HTTP_HOST'], "]")) {
$http_host_port = explode("]", $_SERVER['HTTP_HOST']);
/* v6 address has more parts, drop the last part */
if (count($http_host_port) > 1) {
array_pop($hfttp_host_port);
$http_host = str_replace(["[", "]"], "", implode(":", $http_host_port));
} else {
$http_host = str_replace(["[", "]"], "", implode(":", $http_host_port));
}
} else {
$http_host = explode(":", $_SERVER['HTTP_HOST']);
$http_host = $http_host[0];
}
$this_host = [
$config['system']['hostname'] . "." . $config['system']['domain'],
$config['system']['hostname'],
"localhost"
];
if (!empty($config['system']['webgui']['althostnames'])) {
$this_host = array_merge($this_host, explode(" ", $config['system']['webgui']['althostnames']));
}
if (is_ipaddr($http_host) || in_array($_SERVER['SERVER_ADDR'], ["127.0.0.1", "::1"])) {
return false;
} elseif (in_array($http_host, $this_host)) {
return false;
}
return true;
}
return false;
}
/* HTTP referer detection, return true when being forwarded from an unknown referer*/
function check_security_http_referer_enforement()
{
global $config;
if (!isset($config['system']['webgui']['nohttpreferercheck']) && isset($_SERVER['HTTP_REFERER'])) {
$referrer_host = str_replace(["[", "]"], "", parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST));
$this_host = [$config['system']['hostname'] . "." . $config['system']['domain'], $config['system']['hostname']];
if (!empty($config['system']['webgui']['althostnames'])) {
$this_host = array_merge($this_host, explode(" ", $config['system']['webgui']['althostnames']));
}
if ($referrer_host) {
if (in_array($referrer_host, $this_host)) {
return false;
} elseif (isAuthLocalIP($referrer_host)) {
return false;
} elseif ($referrer_host == "127.0.0.1" || $referrer_host == "localhost") {
// allow SSH port forwarded connections and links from localhost
return false;
}
}
return true;
}
return false;
}
function session_auth()
{
global $config;
@ -90,6 +152,19 @@ function session_auth(&$Login_Error)
session_write_close();
return false;
}
// check additional security measures
if (empty($_SESSION['Username'])) {
if (check_security_dns_rebind()) {
display_error_form(sprintf(gettext("A potential %sDNS Rebind attack%s has been detected.%sTry to access the router by IP address instead of by hostname."), '<a href="http://en.wikipedia.org/wiki/DNS_rebinding">', '</a>', '<br />'));
exit;
} elseif (check_security_http_referer_enforement()) {
display_error_form(sprintf(
gettext('The HTTP_REFERER "%s" does not match the predefined settings. You can disable this check if needed under System: Settings: Administration.'),
html_safe($_SERVER['HTTP_REFERER'])
));
exit;
}
}
/* Validate incoming login request */
if (isset($_POST['login']) && !empty($_POST['usernamefld']) && !empty($_POST['passwordfld'])) {
@ -134,7 +209,6 @@ function session_auth(&$Login_Error)
exit;
} else {
auth_log("Web GUI authentication error for '{$_POST['usernamefld']}' from {$_SERVER['REMOTE_ADDR']}");
$Login_Error = true;
}
}
@ -189,13 +263,10 @@ function session_auth(&$Login_Error)
return true;
}
/* XXX spagehtti code :( */
$Login_Error = false;
/* Authenticate user - exit if failed */
if (!session_auth($Login_Error)) {
if (!session_auth()) {
set_language();
display_login_form($Login_Error ? gettext('Wrong username or password.') : null);
display_login_form(!empty($_POST['usernamefld']) ? gettext('Wrong username or password.') : null);
exit;
}

View File

@ -173,8 +173,6 @@ function config_restore($conffile)
$cnf->backup();
$cnf->restoreBackup($conffile);
disable_security_checks();
$config = parse_config();
write_config(sprintf('Reverted to %s', array_pop(explode('/', $conffile))), false);
@ -182,31 +180,6 @@ function config_restore($conffile)
return 0;
}
/*
* Disable security checks for DNS rebind and HTTP referrer until next time
* they pass (or reboot), to aid in preventing accidental lockout when
* restoring settings like hostname, domain, IP addresses, and settings
* related to the DNS rebind and HTTP referrer checks.
* Intended for use when restoring a configuration or directly
* modifying config.xml without an unconditional reboot.
*/
function disable_security_checks()
{
touch('/tmp/disable_security_checks');
}
/* Restores security checks. Should be called after all succeed. */
function restore_security_checks()
{
@unlink('/tmp/disable_security_checks');
}
/* Returns status of security check temporary disable. */
function security_checks_disabled()
{
return file_exists('/tmp/disable_security_checks');
}
function &config_read_array()
{
global $config;

View File

@ -1150,8 +1150,6 @@ function system_login_configure($verbose = false)
function reset_factory_defaults($sync = true)
{
mwexec('/bin/rm -fr /conf/* /var/log/* /root/.history');
disable_security_checks();
mwexec('/usr/local/sbin/opnsense-beep stop');
/* as we go through a special case directly shut down */

View File

@ -63,8 +63,6 @@ function restore_config_section($section_name, $new_contents)
write_config(sprintf('Restored section %s of config file', $section_name));
convert_config();
disable_security_checks();
return true;
}

View File

@ -247,9 +247,6 @@ function redirect_url()
$urlhost = $config['wizardtemp']['system']['hostname'] . '.' . $config['wizardtemp']['system']['domain'];
}
}
if ($urlhost != $http_host) {
file_put_contents('/tmp/setupwizard_lastreferrer', $proto . '://' . $http_host . $urlport . $_SERVER['REQUEST_URI']);
}
return $proto . '://' . $urlhost . $urlport;
}