* @copyright Copyright (c) 2022, Robert Strutts. * @license https://mit-license.org/ */ namespace tts; final class security { use traits\security\csrf_token_functions; use traits\security\session_hijacking_functions; /** * Get unique IDs for database * @return int */ public static function get_unique_number(): int { return abs(crc32(microtime())); } /** * Get token * @return string */ public static function get_unique_id(): string { $more_entropy = true; $prefix = ""; // Blank is a rand string return md5(uniqid($prefix, $more_entropy)); } public static function use_hmac(string $algo, string $pepper) { if (!function_exists("hash_hmac_algos")) { throw new \Exception("hash_hmac not installed!"); } if (strpos($algo, "md") !== false) { throw new \Exception("MD is too weak!"); } $allowed = hash_hmac_algos(); if (in_array(strtolower($algo), $allowed)) { return hash_hmac($algo, $pepper); } throw new \Exception("hmac algo not found!"); } public static function find_default_hash_algo() { if (defined("PASSWORD_ARGON2ID")) return PASSWORD_ARGON2ID; if (defined("PASSWORD_ARGON2")) return PASSWORD_ARGON2; if (defined("PASSWORD_DEFAULT")) return PASSWORD_DEFAULT; if (defined("PASSWORD_BCRYPT")) return PASSWORD_BCRYPT; return false; } public static function do_password_hash(string $password): bool | string { $pwd_peppered = self::make_hash($password); $hash_algo = \main_tts\configure::get( "security", "hash_algo" ); if (! empty($hash_algo)) { return password_hash($pwd_peppered, $hash_algo); } return password_hash($pwd_peppered, self::find_default_hash_algo()); } public static function do_password_verify( string $input_pwd, $db_password ): bool { $pwd_peppered = self::make_hash($input_pwd); return password_verify($pwd_peppered, $db_password); } /** * Make a secure Hash * @param string $text to encode * @param string $level (weak, low, high, max) * @return string new Hashed */ public static function make_hash(string $text): string { $level = \main_tts\configure::get('security', 'hash_level'); if (empty($level)) { $level = "high"; } $pepper = \main_tts\configure::get('security', 'pepper_pwd'); if (strlen($pepper) < 12) { throw new \Exception("Pepper Password, too short!"); } $salt = \main_tts\configure::get('security', 'salt_pwd'); if (strlen($salt) < 5) { throw new \Exception("Salt Password, too short!"); } switch (strtolower($level)) { case 'max': // Prefer computing using HMAC if (function_exists("hash_hmac")) { return hash_hmac("sha512", $text, $pepper); } // Sha512 hash is the next best thing if (function_exists("hash")) { return hash("sha512", $salt . $text . $pepper); } case 'high': // Prefer computing using HMAC if (function_exists("hash_hmac")) { return hash_hmac("sha256", $text, $pepper); } // Sha256 hash is the next best thing if (function_exists("hash")) { return hash("sha256", $salt . $text . $pepper); } case 'weak': return sha1($salt . $text . $pepper); case 'low': return md5($salt . md5($text . $pepper)); default: break; } return self::use_hmac($level, $pepper); } /** * @method filter_class * @param type $class * Please NEVER add a period or SLASH as it will allow BAD things! * IT should be a-zA-Z0-9_ and that's it. * @retval string of safe class name */ public static function filter_class(string $class): string { if (\bs_tts\requires::is_dangerous($class)) { throw new \Exception("Dangerious URI!"); } return preg_replace('/[^a-zA-Z0-9_]/', '', $class); } /** * Filter possible unsafe URI, prevent ../up-level hackers * @param string $uri * @return string Safe URI */ public static function filter_uri(string $uri): string { if (\bs_tts\requires::is_dangerous($uri) === true) { throw new \Exception("Dangerious URI!"); } return \bs_tts\requires::filter_file_name($uri); } public static function id_hash(): string { return crc32($_SESSION['user_id']); } /** * LocalHost / Private IP - Check * @return bool is LocalHost */ public static function is_localhost(): bool { $whitelist = array('127.0.0.1', '::1', 'localhost', 'dev'); if (!isset($_SERVER['SERVER_NAME'])) { return false; } if (\bs_tts\common::get_bool(\main_tts\configure::get('important', 'private_ip_as_local')) === true) { if (!filter_var($_SERVER['SERVER_NAME'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE)) { // | FILTER_FLAG_NO_RES_RANGE return true; // Private IP } } if (in_array($_SERVER['SERVER_NAME'], $whitelist)) { return true; } else { return false; } } /** * Check if same Domain as Server * @return bool */ public static function request_is_same_domain(): bool { if (self::is_localhost()) { return true; } if (!isset($_SERVER['HTTP_REFERER'])) { // No referer send, so can't be same domain! return false; } else { $referer_host = parse_url($_SERVER['HTTP_REFERER'] . PHP_URL_HOST); $server_host = $_SERVER['HTTP_HOST']; return ($referer_host === $server_host) ? true : false; } } public static function get_client_ip_server() { $ipaddress = ''; if (isset($_SERVER['HTTP_CLIENT_IP'])) { $ipaddress = $_SERVER['HTTP_CLIENT_IP']; } else if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR']; } else if (isset($_SERVER['HTTP_X_FORWARDED'])) { $ipaddress = $_SERVER['HTTP_X_FORWARDED']; } else if (isset($_SERVER['HTTP_FORWARDED_FOR'])) { $ipaddress = $_SERVER['HTTP_FORWARDED_FOR']; } else if (isset($_SERVER['HTTP_FORWARDED'])) { $ipaddress = $_SERVER['HTTP_FORWARDED']; } else if (isset($_SERVER['REMOTE_ADDR'])) { $ipaddress = $_SERVER['REMOTE_ADDR']; } else { $ipaddress = 'UNKNOWN'; } return $ipaddress; } /** * Make sure uploads (LIKE Images, etc...) do NOT run PHP code!! * Checks for PHP tags inside of file. * @param string $file * @return bool true if PHP was found */ public static function file_contains_php(string $file): bool { $file_handle = fopen($file, "r"); while (!feof($file_handle)) { $line = fgets($file_handle); $pos = strpos($line, '