* @copyright Copyright (c) 2022, Robert Strutts. * @license MIT */ namespace bs_tts; final class requires { public static function is_valid_file(string $filename): bool { if (is_string($filename) && strlen($filename) < 64) { return (self::is_dangerous($filename) === false) ? true : false; } return false; } public static function is_dangerous(string $file): bool { // Make sure the file does not contain null bytes to avoid PHAR file attacks if (strpos($file, "\x00") !== false) { return true; } // Remove non-visible characters $file = preg_replace('/[\x00-\x1F\x7F]/u', '', $file); if (strpos($file, "..") !== false || strpos($file, "./") !== false) { return true; // .. Too dangerious, up path attack } /* * :// Too dangerious, PHAR file execution of serialized code injection, etc... * Also, prevent remote code execution from http://, ftp:// */ if (strpos($file, "://") !== false) { return true; } return false; } public static function filter_file_name(string $file): string { return preg_replace('/[^-a-z0-9._\\/\s]+/i', "", $file); } /** * Do not allow periods in folder names! * @param string $dir * @return string */ public static function filter_dir_path(string $dir): string { return preg_replace('/[^a-z0-9_\\/]+/i', "", $dir); } private static function get_PHP_Version_for_file(string $file): string|bool { if (file_exists($file . PHP_MAJOR_VERSION . PHP_MINOR_VERSION) && is_readable($file . PHP_MAJOR_VERSION . PHP_MINOR_VERSION)) { return $file . PHP_MAJOR_VERSION . PHP_MINOR_VERSION; } elseif (file_exists($file . PHP_MAJOR_VERSION) && is_readable($file . PHP_MAJOR_VERSION)) { return $file . PHP_MAJOR_VERSION; } elseif (file_exists($file) && is_readable($file)) { return $file; } else { return false; } } /** * @return Cleaned DIR or False */ public static function safer_dir_exists(string $dir): string|bool { if (self::is_dangerous($dir) || empty($dir)) { return false; } $dir = str_replace('_DS_', '/', $dir); $dir = self::filter_dir_path($dir); $realpath = realpath($dir); if ($realpath === false) { return false; } $dir = escapeshellcmd($realpath); return (file_exists($dir)) ? $dir : false; } /** * @return Cleaned FileName or False */ public static function safer_file_exists(string $file, string $dir=""): string|bool { if (self::is_dangerous($file)) { return false; } if (self::is_dangerous($dir)) { return false; } if (empty($file)) { return false; } $pos = \bs_tts\common::string_last_position($file, "."); if ($pos === false) { $file_type = ".php"; $file_name = $file; } else { $file_name = \bs_tts\common::get_string_left($file, $pos); $file_kind = \bs_tts\common::get_string_right($file, \bs_tts\common::string_length($file) - $pos); $file_type = match ($file_kind) { ".tpl" => ".tpl", default => ".php", }; } $filtered_file = self::filter_file_name($file_name); if (empty($dir)) { $file_plus_dir = $filtered_file; } else { $filtered_dir = rtrim(self::filter_dir_path($dir), '/') . '/'; $file_plus_dir = $filtered_dir . $filtered_file; } $escaped_file = escapeshellcmd($file_plus_dir . $file_type); return self::get_PHP_Version_for_file($escaped_file); } private static function ob_starter() { if (extension_loaded('mbstring')) { ob_start('mb_output_handler'); } else { ob_start(); } } private static function secure_file(bool $return_contents, string $file, string $path, $local = null, array $args = array(), bool $load_once = true) { $dir = match ($path) { "dir_fixed" => "", "framework", "tts" => \main_tts\TTS_FRAMEWORK, "on_error" => \bs_tts\site_helper::get_root() . \bs_tts\site_helper::get_project() . "views/on_error/", default => \bs_tts\site_helper::get_root(), // project }; $versioned_file = self::safer_file_exists($file, $dir); if ($versioned_file === false) { return false; } if (is_array($args)) { if (isset($args["return_contents"]) || isset($args["script_output"]) || isset($args["versioned_file"]) ) { return false; // Args are Dangerious!! Abort } extract($args, EXTR_PREFIX_SAME, "dup"); } if ($return_contents) { $script_output = (string) ob_get_clean(); self::ob_starter(); include $versioned_file; $script_output .= (string) ob_get_clean(); return $script_output; } else { return ($load_once) ? include_once($versioned_file) : include($versioned_file); } } /** * Use secure_include instead of include. * @param string $file script * @param string $path (framework or project) * @param type $local ($this) * @param array $args ($var to pass along) * @param bool $load_once (default true) * @return results of include or false if failed */ public static function secure_include(string $file, string $path = 'project', $local = null, array $args = array(), bool $load_once = true) { return self::secure_file(false, $file, $path, $local, $args, $load_once); } public static function secure_get_content(string $file, string $path = 'project', $local = null, array $args = array(), bool $load_once = true) { return self::secure_file(true, $file, $path, $local, $args, $load_once); } }