diff --git a/plist b/plist index 5d1773c8a1..531de6908c 100644 --- a/plist +++ b/plist @@ -563,6 +563,7 @@ /usr/local/opnsense/mvc/app/library/OPNsense/System/Status/LiveMediaStatus.php /usr/local/opnsense/mvc/app/library/OPNsense/System/Status/MonitOverrideStatus.php /usr/local/opnsense/mvc/app/library/OPNsense/System/Status/OpensshOverrideStatus.php +/usr/local/opnsense/mvc/app/library/OPNsense/System/Status/RootLockStatus.php /usr/local/opnsense/mvc/app/library/OPNsense/System/Status/SystemBootingStatus.php /usr/local/opnsense/mvc/app/library/OPNsense/System/Status/UnboundOverrideStatus.php /usr/local/opnsense/mvc/app/library/OPNsense/System/SystemStatus.php diff --git a/src/etc/inc/plugins.inc.d/webgui.inc b/src/etc/inc/plugins.inc.d/webgui.inc index 612b803533..04f3d81ced 100644 --- a/src/etc/inc/plugins.inc.d/webgui.inc +++ b/src/etc/inc/plugins.inc.d/webgui.inc @@ -101,6 +101,7 @@ function webgui_configure_do($verbose = false, $interface_map = null) } chdir('/usr/local/www'); + chown('/var/run/booting', 'wwwonly'); /* booting flag should be owned by wwwonly user*/ /* defaults */ $portarg = '80'; @@ -148,8 +149,14 @@ function webgui_configure_do($verbose = false, $interface_map = null) /* regenerate the php.ini files in case the setup has changed */ configd_run('template reload OPNsense/WebGui'); + /* arrange configuration ownership, migh need to arrange this elsewhere eventually */ + $todo = array_merge(glob('/conf/backup/*.xml'), ['/conf', '/conf/config.xml', '/conf/backup']); + foreach ($todo as $item) { + chown($item, 'wwwonly'); + } + /* flush Phalcon volt templates */ - foreach (glob('/usr/local/opnsense/mvc/app/cache/*.php') as $filename) { + foreach (glob('/var/lib/php/cache/*.php') as $filename) { unlink($filename); } @@ -251,8 +258,12 @@ EOD; if (!empty($config['system']['webgui']['httpaccesslog'])) { $lighty_use_syslog .= 'accesslog.use-syslog="enable"' . "\n"; } - - $fast_cgi_path = "/tmp/php-fastcgi.socket"; + $change_user = ''; + if (!empty($config['system']['webgui']['noroot']) || file_exists('/var/run/www_non_root')) { + $change_user .= "server.username = \"wwwonly\"\n"; + $change_user .= "server.groupname = \"www\"\n"; + touch('/var/run/www_non_root'); /* create file as root, lock going back to root */ + } $fastcgi_config = << ( "localhost" => ( - "socket" => "{$fast_cgi_path}", + "socket" => "/var/lib/php/tmp/php-fastcgi.socket", "max-procs" => 8, "bin-environment" => ( "PHP_FCGI_CHILDREN" => "5", - "PHP_FCGI_MAX_REQUESTS" => "100" + "PHP_FCGI_MAX_REQUESTS" => "100", + "TMPDIR" => "/var/lib/php/tmp" ), "bin-path" => "/usr/local/bin/php-cgi" ) @@ -295,6 +307,8 @@ include "{$confdir}/conf.d/*.conf" server.max-keep-alive-requests = 15 server.max-keep-alive-idle = 30 +{$change_user} + ## a static document-root, for virtual-hosting take look at the ## server.virtual-* options server.document-root = "/usr/local/www/" diff --git a/src/etc/rc.subr.d/var b/src/etc/rc.subr.d/var index 9ad6744a24..d3a0874e95 100755 --- a/src/etc/rc.subr.d/var +++ b/src/etc/rc.subr.d/var @@ -64,11 +64,17 @@ for RUNDIR in /var/run /var/dhcpd/var/run /var/unbound/var/run; do fi done -# setup output directory for php sessions -mkdir -p /var/lib/php/sessions -chown root:wheel /var/lib/php/sessions -chmod 750 /var/lib/php/sessions -rm -f /var/lib/php/sessions/sess_* +# setup output directory for various php ui components +for PHPDIR in /var/lib/php/sessions /var/lib/php/tmp /var/lib/php/cache; do + mkdir -p ${PHPDIR} + chown wwwonly:wheel ${PHPDIR} + chmod 750 ${PHPDIR} + rm -f ${PHPDIR}/* +done + +# tmp needs sticky +chmod +t /var/lib/php/tmp + if [ ${USE_MFS_VAR} -ne 0 ]; then MAX_MFS_VAR=$(grep 'max_mfs_var' /conf/config.xml | sed 's/[^>]*>\([^<]*\)<.*/\1/') diff --git a/src/opnsense/mvc/app/config/config.php b/src/opnsense/mvc/app/config/config.php index afe9287f11..2fa57e5982 100644 --- a/src/opnsense/mvc/app/config/config.php +++ b/src/opnsense/mvc/app/config/config.php @@ -35,7 +35,7 @@ return new OPNsense\Core\AppConfig([ 'viewsDir' => __DIR__ . '/../../app/views/', 'pluginsDir' => __DIR__ . '/../../app/plugins/', 'libraryDir' => __DIR__ . '/../../app/library/', - 'cacheDir' => __DIR__ . '/../../app/cache/', + 'cacheDir' => '/var/lib/php/cache/', 'contribDir' => __DIR__ . '/../../../contrib/', 'baseUri' => '/opnsense_gui/', ], diff --git a/src/opnsense/mvc/app/library/OPNsense/Core/Config.php b/src/opnsense/mvc/app/library/OPNsense/Core/Config.php index 8b57cc0132..587f7af590 100644 --- a/src/opnsense/mvc/app/library/OPNsense/Core/Config.php +++ b/src/opnsense/mvc/app/library/OPNsense/Core/Config.php @@ -348,6 +348,7 @@ class Config extends Singleton // in case there are no backups, restore defaults. $logger->error(gettext('No valid config.xml found, attempting to restore factory config.')); $this->restoreBackup('/usr/local/etc/config.xml'); + chown('/conf/config.xml', 'wwwonly'); /* frontend owns file */ } } diff --git a/src/opnsense/mvc/app/library/OPNsense/System/Status/RootLockStatus.php b/src/opnsense/mvc/app/library/OPNsense/System/Status/RootLockStatus.php new file mode 100644 index 0000000000..1cd92b09d9 --- /dev/null +++ b/src/opnsense/mvc/app/library/OPNsense/System/Status/RootLockStatus.php @@ -0,0 +1,57 @@ +internalPriority = 2; + $this->internalPersistent = true; + $this->internalIsBanner = true; + $this->internalTitle = gettext('Root lock'); + } + + public function collectStatus() + { + $file = '/var/run/www_non_root'; + if (!(file_exists($file) && empty(Config::getInstance()->object()->system->webgui->noroot))) { + return; + } + $this->internalStatus = SystemStatusCode::ERROR; + $this->internalMessage = sprintf(gettext( + 'Strict security mode disabled, but requires "%s" to be removed manually from disk. ' . + 'Refusing to run as root in the meantime.' + ), $file); + } +} diff --git a/src/opnsense/service/templates/OPNsense/Captiveportal/lighttpd-api-dispatcher.conf b/src/opnsense/service/templates/OPNsense/Captiveportal/lighttpd-api-dispatcher.conf index c3e9b3b839..150ab80f16 100644 --- a/src/opnsense/service/templates/OPNsense/Captiveportal/lighttpd-api-dispatcher.conf +++ b/src/opnsense/service/templates/OPNsense/Captiveportal/lighttpd-api-dispatcher.conf @@ -8,6 +8,8 @@ server.modules = ( "mod_access", "mod_expire", "mod_deflate", "mod_ "mod_cgi", "mod_fastcgi","mod_alias", "mod_rewrite" ) +server.username = "wwwonly" +server.groupname = "www" server.max-keep-alive-requests = 15 server.max-keep-alive-idle = 30 @@ -55,7 +57,7 @@ server.max-request-size = 2097152 fastcgi.server = ( ".php" => ( "localhost" => ( - "socket" => "/tmp/php-fastcgi-cp.socket", + "socket" => "/var/lib/php/tmp/php-fastcgi-cp.socket", "max-procs" => 4, "bin-environment" => ( "PHP_FCGI_CHILDREN" => "2", diff --git a/src/opnsense/service/templates/OPNsense/WebGui/php.ini b/src/opnsense/service/templates/OPNsense/WebGui/php.ini index 051432cc3a..a8cbcf9dc9 100644 --- a/src/opnsense/service/templates/OPNsense/WebGui/php.ini +++ b/src/opnsense/service/templates/OPNsense/WebGui/php.ini @@ -31,7 +31,7 @@ error_reporting = E_ALL display_errors=on display_startup_errors=off log_errors=on -error_log=/tmp/PHP_errors.log +error_log=/var/lib/php/tmp/PHP_errors.log date.timezone="{{system.timezone|default('Etc/UTC')}}" session.save_path=/var/lib/php/sessions session.gc_maxlifetime={{system.webgui.session_timeout|default(240)|int * 60}} diff --git a/src/www/crash_reporter.php b/src/www/crash_reporter.php index 3bb705ce8a..c50ba5041b 100644 --- a/src/www/crash_reporter.php +++ b/src/www/crash_reporter.php @@ -33,10 +33,10 @@ require_once 'guiconfig.inc'; function has_crash_report() { $skip_files = ['.', '..', 'minfree', 'bounds', '']; - $PHP_errors_log = '/tmp/PHP_errors.log'; + $PHP_errors_log = '/var/lib/php/tmp/PHP_errors.log'; $count = 0; - if (file_exists($PHP_errors_log) && !is_link('/tmp/PHP_errors.log')) { + if (file_exists($PHP_errors_log) && !is_link($PHP_errors_log)) { if (intval(shell_safe('/bin/cat %s | /usr/bin/wc -l | /usr/bin/awk \'{ print $1 }\'', $PHP_errors_log))) { $count++; } @@ -150,10 +150,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } file_put_contents('/var/crash/crashreport_header.txt', $crash_report_header); - if (file_exists('/tmp/PHP_errors.log')) { + if (file_exists('/var/lib/php/tmp/PHP_errors.log')) { // limit PHP_errors to send to 1MB - exec('/usr/bin/tail -c 1048576 /tmp/PHP_errors.log > /var/crash/PHP_errors.log'); - @unlink('/tmp/PHP_errors.log'); + exec('/usr/bin/tail -c 1048576 /var/lib/php/tmp/PHP_errors.log > /var/crash/PHP_errors.log'); + @unlink('/var/lib/php/tmp/PHP_errors.log'); } @copy('/var/run/dmesg.boot', '/var/crash/dmesg.boot'); exec('/usr/bin/gzip /var/crash/*'); @@ -171,7 +171,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { foreach ($files_to_upload as $file_to_upload) { @unlink($file_to_upload); } - @unlink('/tmp/PHP_errors.log'); + @unlink('/var/lib/php/tmp/PHP_errors.log'); } elseif ($pconfig['Submit'] == 'new') { /* force a crash report generation */ $has_crashed = true; @@ -184,21 +184,21 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($has_crashed) { $crash_files = glob("/var/crash/*"); $crash_reports['System Information'] = trim($crash_report_header); - if (file_exists('/tmp/PHP_errors.log') && !is_link('/tmp/PHP_errors.log')) { - $php_errors_size = @filesize('/tmp/PHP_errors.log'); + if (file_exists('/var/lib/php/tmp/PHP_errors.log') && !is_link('/var/lib/php/tmp/PHP_errors.log')) { + $php_errors_size = @filesize('/var/lib/php/tmp/PHP_errors.log'); $max_php_errors_size = 1 * 1024 * 1024; // limit reporting for PHP_errors.log to $max_php_errors_size characters if ($php_errors_size > $max_php_errors_size) { // if file is to large, only display last $max_php_errors_size characters $php_errors .= @file_get_contents( - '/tmp/PHP_errors.log', + '/var/lib/php/tmp/PHP_errors.log', NULL, NULL, ($php_errors_size - $max_php_errors_size), $max_php_errors_size ); } else { - $php_errors = @file_get_contents('/tmp/PHP_errors.log'); + $php_errors = @file_get_contents('/var/lib/php/tmp/PHP_errors.log'); } if (!empty($php_errors)) { $crash_reports['PHP Errors'] = trim($php_errors); diff --git a/src/www/system_advanced_admin.php b/src/www/system_advanced_admin.php index 7105001ae9..70c2f1bc99 100644 --- a/src/www/system_advanced_admin.php +++ b/src/www/system_advanced_admin.php @@ -68,6 +68,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { $pconfig['user_allow_gen_token'] = isset($config['system']['user_allow_gen_token']) ? explode(",", $config['system']['user_allow_gen_token']) : []; $pconfig['nodnsrebindcheck'] = isset($config['system']['webgui']['nodnsrebindcheck']); $pconfig['nohttpreferercheck'] = isset($config['system']['webgui']['nohttpreferercheck']); + $pconfig['noroot'] = isset($config['system']['webgui']['noroot']); $pconfig['althostnames'] = $config['system']['webgui']['althostnames'] ?? null; $pconfig['serialspeed'] = $config['system']['serialspeed']; $pconfig['serialusb'] = !empty($config['system']['serialusb']); @@ -171,6 +172,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { $config['system']['webgui']['compression'] != $pconfig['compression'] || $config['system']['webgui']['ssl-ciphers'] != $newciphers || $config['system']['webgui']['interfaces'] != $newinterfaces || + empty($config['system']['webgui']['noroot']) != empty($pconfig['noroot']) || empty($pconfig['httpaccesslog']) != empty($config['system']['webgui']['httpaccesslog']) || empty($pconfig['ssl-hsts']) != empty($config['system']['webgui']['ssl-hsts']) || !empty($pconfig['disablehttpredirect']) != !empty($config['system']['webgui']['disablehttpredirect']) || @@ -280,6 +282,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { unset($config['system']['webgui']['nohttpreferercheck']); } + if (!empty($pconfig['noroot'])) { + $config['system']['webgui']['noroot'] = true; + } elseif (isset($config['system']['webgui']['noroot'])) { + unset($config['system']['webgui']['noroot']); + } + if (!empty($pconfig['althostnames'])) { $config['system']['webgui']['althostnames'] = $pconfig['althostnames']; } elseif (isset($config['system']['webgui']['althostnames'])) { @@ -1079,6 +1087,16 @@ $(document).ready(function() { + + + + /> + + + +