263 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			263 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?PHP
 | |
| /**
 | |
|  * Provides class MD_JAIL.
 | |
|  */
 | |
| declare(strict_types = 1);
 | |
| 
 | |
| /**
 | |
|  * A class that, once initialized, forces the programmer to make security instructions implicit.
 | |
|  * If an object of the class has been created, not specifying security instructions
 | |
|  * leads to an error.
 | |
|  * A restriction on basic file operations is not practical in an md context because of
 | |
|  * the way transations are loaded through MDTlLoader.
 | |
|  */
 | |
| final class MD_JAIL {
 | |
| 
 | |
|     const STATUS_NONE      = 0;
 | |
|     const STATUS_STARTED   = 1;
 | |
|     const STATUS_SPECIFIED = 2; // Determines that everything is fine.
 | |
| 
 | |
|     /** @var integer */
 | |
|     private int $_status = self::STATUS_NONE;
 | |
| 
 | |
|     /**
 | |
|      * @var integer
 | |
|      * Maximum execution time in seconds.
 | |
|      */
 | |
|     public int $max_execution_time;
 | |
| 
 | |
|     /**
 | |
|      * @var string[]
 | |
|      * Specifies which paths may be used by this script.
 | |
|      */
 | |
|     private array $_open_basedir = [];
 | |
| 
 | |
|     /**
 | |
|      * @var string
 | |
|      * Specifies the maximum RAM the script may use.
 | |
|      */
 | |
|     public string $memory_limit;
 | |
| 
 | |
|     /**
 | |
|      * Static function providing an advisory on how to harden the php.ini or
 | |
|      * .user.ini.
 | |
|      *
 | |
|      * @param array{shell_access_whitelist: string[], sys_function_whitelist: string[], file_function_whitelist: string[], file_uploads: bool, allow_url_fopen: bool, max_input_vars: integer, max_input_nesting_level: integer, post_max_size: string, curl: bool} $requested_resources Requested resources.
 | |
|      *
 | |
|      * @return string
 | |
|      */
 | |
|     public static function check_server_setup(array $requested_resources):string {
 | |
| 
 | |
|         # ini_set("open_basedir", __DIR__ . "/../../");
 | |
| 
 | |
|         $disable_functions = [
 | |
|             "dl",                            # Loads a PHP extension at runtime
 | |
|             "opcache_get_status",            # Gets opcache status
 | |
|             "phpinfo",                       # For obvious reasons
 | |
|             "parse_ini_file",                # For obvious reasons
 | |
|             "show_source", "highlight_file", # Aliases; serve the content of a PHP file
 | |
|             "php_uname",                     # Returns information about the operating system PHP is running on
 | |
|             "phpcredits",                    # Credits page of PHP authors
 | |
|             "php_strip_whitespace",          # Return PHP source with stripped comments and whitespace
 | |
|             "popen", "pclose",               # Similar to fopen, but capable of shell access
 | |
|             "virtual",                       # Perform an Apache sub-request
 | |
|         ];
 | |
| 
 | |
|         $disable_aliases = [
 | |
|             "ini_set"                 => "ini_alter",       # Alias of ini_set(), ini_set is preferred
 | |
|             "disk_free_space"         => "diskfreespace",   # Alias of disk_free_space()
 | |
|             "PHP_SAPI"                => "php_sapi_name",   # Alias of constant PHP_SAPI
 | |
|             "stream_set_write_buffer" => "set_file_buffer", # Alias of stream_set_write_buffer()
 | |
|         ];
 | |
| 
 | |
|         $shell_access_functions = array_diff(["exec", "passthru", "proc_close", "proc_get_status", "proc_nice", "proc_open", "proc_terminate", "shell_exec", "system"], $requested_resources['shell_access_whitelist']);
 | |
|         $sys_functions = array_diff(["get_current_user", "getmyuid", "getmygid", "getmypid", "getmyinode", "getlastmod", "getenv", "putenv"], $requested_resources['sys_function_whitelist']);
 | |
|         $file_functions = array_diff(["chgrp", "chgrp", "lchgrp", "lchown", "link", "linkinfo", "symlink"], $requested_resources['file_function_whitelist']);
 | |
| 
 | |
|         if ($requested_resources['curl'] === false) {
 | |
|             $disable_functions[] = "curl_init";
 | |
|             $disable_functions[] = "curl_exec";
 | |
|             $disable_functions[] = "curl_multi_init";
 | |
|             $disable_functions[] = "curl_multi_exec";
 | |
|         }
 | |
| 
 | |
|         $disabledWOAliases = array_merge($disable_functions, $shell_access_functions, $sys_functions, $file_functions);
 | |
| 
 | |
|         $output = '## php.ini' . PHP_EOL . PHP_EOL;
 | |
|         $output .= PHP_EOL . "php_value[disable_functions] = \"" . implode(", ", array_merge($disabledWOAliases, $disable_aliases)) . "\"";
 | |
| 
 | |
|         if ($requested_resources['file_uploads'] === false) {
 | |
|             $output .= PHP_EOL . "php_value[file_uploads] = 0";
 | |
|         }
 | |
|         if ($requested_resources['allow_url_fopen'] === false) {
 | |
|             $output .= PHP_EOL . "php_value[allow_url_fopen] = 0";
 | |
|         }
 | |
| 
 | |
|         $output .= PHP_EOL . PHP_EOL . '## .user.ini' . PHP_EOL;
 | |
| 
 | |
|         if ($requested_resources['file_uploads'] === false) {
 | |
|             $output .= PHP_EOL . "upload_max_filesize = 1";
 | |
|         }
 | |
|         if ($requested_resources['max_input_vars'] != ini_get("max_input_vars")) {
 | |
|             $output .= PHP_EOL . "max_input_vars = " . $requested_resources['max_input_vars'];
 | |
|         }
 | |
|         if ($requested_resources['max_input_nesting_level'] != ini_get("max_input_nesting_level")) {
 | |
|             $output .= PHP_EOL . "max_input_nesting_level = " . $requested_resources['max_input_nesting_level'];
 | |
|         }
 | |
|         if ($requested_resources['post_max_size'] != ini_get("post_max_size")) {
 | |
|             $output .= PHP_EOL . "post_max_size = " . $requested_resources['post_max_size'];
 | |
|         }
 | |
| 
 | |
|         $output .= PHP_EOL . PHP_EOL . '## PHPStan Directives' . PHP_EOL . PHP_EOL . "    disallowedFunctionCalls:" . PHP_EOL;
 | |
|         foreach ($disable_aliases as $to_keep => $disabled) {
 | |
| 
 | |
|             $output .= '
 | |
|     -
 | |
|         function: \'' . $disabled . '()\'
 | |
|         message: \'use ' . $to_keep . ' instead\'';
 | |
| 
 | |
|         }
 | |
|         foreach ($disabledWOAliases as $disabled) {
 | |
| 
 | |
|             $output .= '
 | |
|     - function: \'' . $disabled . '()\'';
 | |
| 
 | |
|         }
 | |
| 
 | |
|         return $output;
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Registers an additional accessible directory for open_basedir.
 | |
|      *
 | |
|      * @param string $dir Directory to register.
 | |
|      *
 | |
|      * @return void
 | |
|      */
 | |
|     public function register_accessible_dir(string $dir):void {
 | |
| 
 | |
|         $this->_open_basedir[] = $dir;
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Applies the memory limit setting.
 | |
|      *
 | |
|      * @return void
 | |
|      */
 | |
|     private function _apply_memory_limit():void {
 | |
| 
 | |
|         if (!isset($this->memory_limit)) {
 | |
|             throw new MDJailSecurityOptionNotSetException("It has not been specified, which memory limit the script should hold. Set MD_JAIL->memory_limit = string.");
 | |
|         }
 | |
|         if (ini_set("memory_limit", $this->memory_limit) === false) {
 | |
|             throw new Exception('Failed to change memory_limit to ' . $this->memory_limit);
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Applies the maximum execution time setting.
 | |
|      *
 | |
|      * @return void
 | |
|      */
 | |
|     private function _apply_time_limit():void {
 | |
| 
 | |
|         if (!isset($this->max_execution_time)) {
 | |
|             throw new MDJailSecurityOptionNotSetException("It has not been specified, which maximum execution time the script should hold. Set MD_JAIL->max_execution_time = integer.");
 | |
|         }
 | |
|         if (set_time_limit($this->max_execution_time) === false) {
 | |
|             throw new Exception('Failed to change max_execution_time to ' . $this->max_execution_time);
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Applies basedir restrictions.
 | |
|      *
 | |
|      * @return void
 | |
|      */
 | |
|     private function _apply_basedir_restriction():void {
 | |
| 
 | |
|         if (empty($this->_open_basedir)) {
 | |
|             throw new MDJailSecurityOptionNotSetException("It has not been specified, which memory limit the script should hold. Set MD_JAIL->open_basedir = string.");
 | |
|         }
 | |
|         if (ini_set("open_basedir", implode(':', $this->_open_basedir)) === false) {
 | |
|             throw new Exception('Failed to set open_basedir restrictions');
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Enforces security options previously set.
 | |
|      *
 | |
|      * @return void
 | |
|      */
 | |
|     public function enforce():void {
 | |
| 
 | |
|         // Special instructions on CLI, so as to not disturb PHPUnit
 | |
|         if (PHP_SAPI === 'cli') {
 | |
| 
 | |
|             if (!isset($this->memory_limit)) {
 | |
|                 throw new MDJailSecurityOptionNotSetException("It has not been specified, which memory limit the script should hold. Set MD_JAIL->memory_limit = string.");
 | |
|             }
 | |
|             if (!isset($this->max_execution_time)) {
 | |
|                 throw new MDJailSecurityOptionNotSetException("It has not been specified, which maximum execution time the script should hold. Set MD_JAIL->max_execution_time = integer.");
 | |
|             }
 | |
| 
 | |
|             $this->_status = self::STATUS_SPECIFIED;
 | |
|             $this->__destruct();
 | |
|         }
 | |
| 
 | |
|         $this->_apply_memory_limit();
 | |
|         $this->_apply_time_limit();
 | |
| 
 | |
|         // Set accessible file paths
 | |
|         $this->_apply_basedir_restriction();
 | |
| 
 | |
|         $this->_status = self::STATUS_SPECIFIED;
 | |
|         $this->__destruct();
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Setup function. Registers a shutdown function that throws an error
 | |
|      * if the security specifications have not been made.
 | |
|      *
 | |
|      * @return void
 | |
|      */
 | |
|     public function __construct() {
 | |
| 
 | |
|         $this->_status = self::STATUS_STARTED;
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Destructor. Throws an exception if the settings have not been set.
 | |
|      */
 | |
|     public function __destruct() {
 | |
| 
 | |
|         if ($this->_status !== self::STATUS_SPECIFIED) {
 | |
| 
 | |
|             echo "Security specifications need to be set.";
 | |
| 
 | |
|             if (!isset($this->memory_limit)) {
 | |
|                 echo "Set memory limit";
 | |
|             }
 | |
| 
 | |
|             if (!isset($this->max_execution_time)) {
 | |
|                 echo "Set max_execution_time";
 | |
|             }
 | |
| 
 | |
|             if (empty($this->_open_basedir)) {
 | |
|                 echo "Set open_basedir";
 | |
|             }
 | |
| 
 | |
|             throw new MDJailSecurityOptionNotSetException("Security specifications need to be set.");
 | |
|         }
 | |
| 
 | |
|     }
 | |
| }
 | |
| 
 |