diff --git a/inc/Main.php b/inc/Main.php index 7d5221c..7384477 100755 --- a/inc/Main.php +++ b/inc/Main.php @@ -6,7 +6,7 @@ use \Settings; /** * Main Class * @author franzz - * @version 2.3 + * @version 2.4 */ abstract class Main extends PhpObject { @@ -38,6 +38,7 @@ abstract class Main extends PhpObject //Variables protected $asMasks; protected $asContext; + protected string $sCsrfToken = ''; /** * Language Translator @@ -89,7 +90,7 @@ abstract class Main extends PhpObject $this->asContext['process_page'] = basename($sProcessPage); $sServerName = array_key_exists('SERVER_NAME', $_SERVER)?$_SERVER['SERVER_NAME']:$_SERVER['PWD']; - $sScheme = array_key_exists('HTTP_X_FORWARDED_PROTO', $_SERVER)?$_SERVER['HTTP_X_FORWARDED_PROTO']:$_SERVER['REQUEST_SCHEME']; + $sScheme = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? $_SERVER['REQUEST_SCHEME'] ?? 'https'; $sAppPath = $sScheme.'://'.str_replace(array('http://', 'https://'), '', $sServerName.dirname($_SERVER['SCRIPT_NAME'])); $this->asContext['serv_name'] = $sAppPath.(mb_substr($sAppPath, -1)!='/'?'/':''); @@ -105,6 +106,46 @@ abstract class Main extends PhpObject return file_exists($sCleanedFilePath)?$sCleanedFilePath.'?'.date("YmdHis", filemtime($sCleanedFilePath)):$sFilePath; } + protected function getCsrfToken() { + if($this->sCsrfToken === '') $this->initCsrfToken(); + return $this->sCsrfToken; + } + + protected function setCsrfToken() { + if(empty($_SESSION['csrf_token'])) $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); + $this->sCsrfToken = $_SESSION['csrf_token']; + } + + private function initCsrfToken() { + if(PHP_SAPI === 'cli') return; + + $bCloseSession = false; + if(session_status() !== PHP_SESSION_ACTIVE) { + $bSecure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https'); + session_set_cookie_params(array('httponly' => true, 'secure' => $bSecure, 'samesite' => 'Lax')); + session_start(); + $bCloseSession = true; + } + + $this->setCsrfToken(); + if($bCloseSession) session_write_close(); + } + + public function checkCsrfToken(string $sClientToken) { + $sServerToken = $this->getCsrfToken(); + return PHP_SAPI === 'cli' || ($sServerToken !== '' && is_string($sClientToken) && hash_equals($sServerToken, $sClientToken)); + } + + public function validateMutationRequest($sAction, $sCsrfToken='') { + return + PHP_SAPI === 'cli' //Ignore internal cron job + || + !in_array($sAction, static::MUTATING_ACTIONS, true) //Ignore non-sensitive requests + || + ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST' && $this->checkCsrfToken($sCsrfToken) //Only accept POST requests and valid CSRF token + ; + } + public function addUncaughtError($sError) { $this->addError('Uncaught errors:'."\n".$sError); @@ -196,4 +237,8 @@ abstract class Main extends PhpObject http_response_code(404); exit; } + + public static function validatePositiveInt($oValue=0) { + return filter_var($oValue, FILTER_VALIDATE_INT, array('options' => array('default' => 0, 'min_range' => 0))); + } }