What Is Safe Mode?
Safe mode is an attempt to solve some of the problems that are introduced when running a PHP enabled web server in a shared hosting environment. The additional security checks imposed by Safe Mode are, however, performed at the PHP level, since the underlying web server and operating system security architecture is usually not sufficient to impose the necessary security restrictions for a multi-user environment, in which many users may be able to upload and execute PHP code.
The problem generally arises when PHP is run in a web server which hosts and executes scripts provided by multiple users. Since the web server process itself runs as a single system user, that user account must have access to each hosted user’s files. This means that any script running on the web server has access to each user’s files.
It is not possible to use operating system level security to restrict which files can be accessed, since the web server process (and hence PHP) needs access to all of them in order to serve user web pages. The only available solution is to address these issues at the PHP level.
PHP Safe Mode does just this; it imposes a set of restrictions on multi-user systems, within the core PHP engine, and scripts are run within those imposed restrictions. The full details of Safe Mode are explained below, but I would like to point out here that while Safe Mode restricts PHP scripts, those restrictions obviously do not (and cannot!) apply to external programs executed by PHP. It is therefore possible to specify a safe directory for executable programs, but even with this capability, if any of those programs allow access to files outside of the Safe Mode configured directories, it will still be possible for a malicious user to access another user’s files.
What Does Safe Mode Restrict?
Enabling Safe Mode imposes several restrictions on PHP scripts. These restrictions are mostly concerned with file access, access to environment variables and controlling the execution of external processes.
Restricting File Access
Additional checks are performed by PHP when running in Safe Mode, prior to any file operation taking place. In order for the file operation to proceed, the user ID of the file owner, for the file being operated on, must be the same as the user ID of the script owner, for the script performing the file operation.
There are problems which may be encountered when this mechanism is turned on, notably when attempting to work with files owned by different users, but in the same document tree, and files which have been created at runtime by the script (which will be owned by the owner of the web server process).
In order to work around these issues, a relaxed form of the file permission checking is also provided by Safe Mode. Using the php.ini directive (or setting in .htaccess or a virtual hosting section or directory section in httpd.conf) below, it is possible to relax the user ID check to a group ID check. That is, if the script has the same group ID as the file on which a file operation was requested, the operation will succeed. If the script owners and the web server are members of the same group, and all hosted files are owned by this group, the file operations will succeed regardless of user ID.
safe_mode_gid = On
The user and group ID restrictions are not enforced for files which are located within the PHP include directories, provided those directories are specified in the safe_mode_include_dir directive. This means that you should always specify the default PHP include directories in this directive in the php.ini configuration file.
Restricting Access To Environment Variables
When PHP is running in Safe Mode, it restricts access to environment variables based on two php.ini directives. Directives are provided for allowing write access to certain environment variables, and for restricting write access to certain environment variables. Each is a comma-delimited list of affected environment variables.
Restrictions On Running External Processes
Restrictions are also imposed on the execution of external processes (i.e. not PHP scripts). Binaries in the specified safe directory may be executed (See the Configuration Directives) section. exec(), system(), popen() and passthru()are affected by these settings. shell_exec() and the backtick operator do not work at all when Safe Mode has been enabled.
Other Imposed Restrictions
Several functions are restricted in Safe Mode. Some of the most important of these are listed later. Furthermore, the PHP_AUTH_USER, PHP_AUTH_PW and AUTH_TYPE variables are not made available in Safe Mode.
Safe Mode Configuration Directives
The following directives control the Safe Mode settings. These should be set in php.ini. Some may also be set (or overridden) in the httpd.conf file.
safe_mode = boolean
This directive enables PHP Safe Mode. The recommended strategy for configuring a shared hosting environment to use Safe Mode is to enable Safe Mode globally, in php.ini, and configure sensible default values here. Specific overriding values can then be made in httpd.conf for each host, location, or directory.
safe_mode_gid = boolean
This directive causes PHP to relax the user ID equality check between scripts and the files on which they operate to a group ID check. The reasons why this directive may be useful were explained in detail above.
safe_mode_include_dir = string
The user and group ID restrictions are ignored for files included from this directory and its subdirectories. The directory must be listed in the include_path directory, or a full path name given for include statements. The value of this directive may be a colon-separated list of directories for which inclusion is allowed without user or group ID checking being performed.
Note that this restriction acts as a directory prefix, rather than a complete directory name. As such, a value of /home/wwwroot/inc allows files within /home/wwwroot/inc, /home/wwwroot/incl, /home/wwwroot/include and /home/wwwroot/incriminating_evidence to be included without restriction. If in doubt, always end the directory path with a trailing / to prevent it being interpreted as a prefix such as those listed above.
safe_mode_exec_dir = string
This directive specifies the path under which executables may be run in Safe Mode. This restriction affects system(), exec(), popen() and passthru(). The directory separator must always be a /, even on a Windows server.
safe_mode_allowed_env_vars = string
This directive specifies prefixes for environment variables which may be altered by a script running in Safe Mode. The default action is to allow users to edit environment variables beginning with PHP_ (i.e. have a prefix of PHP_). If this directive is left empty, a script running under safe mode will be able to modify any environment variable.
safe_mode_protected_env_vars = string
Similarly to above, this directive allows you to specify environment variables which may not be edited by the script. Even if safe_mode_allowed_env_vars also includes an environment variable listed here, PHP will prevent a script changing that environment variable.
open_basedir = string
The open_basedir directive has been covered already in this series. It restricts all file operations to the specified directory tree. This directive works outside of Safe Mode also. The list of directories for which file access is allowed must be separated by a semicolon on Windows, or by a colon on all other systems.
disable_functions = string
This directive lists functions to disallow. A comma-delimited list of function names is used. Like open_basedir, this directive does not require that Safe Mode has been enabled.
Functions Restricted By Safe Mode
There is a full list of functions for which Safe Mode imposes certain restrictions at http://www.php.net/manual/en/features.safe-mode.functions.php
Below, I list some of the most important limitations.
putenv() takes into account the safe_mode_allowed_env_vars and safe_mode_protected_env_vars directives mentioned above.
Moving uploaded files is subject to the same User ID or Group ID checking imposed on all file operations under Safe Mode. The file being moved must have the same user ID or group ID (if relaxed group restrictions are enabled) as the script moving it. Generally, the file will be created with the user ID of the web server process, and as such the relaxed restrictions are likely to be required in order to move uploaded files.
chdir() mkdir() rmdir()
Changing the current working directory of the script depends on the requirements imposed by user and group ID restrictions and by open_basedir. Similar restrictions are imposed for mkdir() and rmdir().
The additional parameters (fifth argument) have no effect when running under Safe Mode, since these would allow arbitrary options to be passed to the mailer program.
Setting an execution time limit within a script is ignored when the script is running under Safe Mode.
Dynamically loading PHP extensions is disabled when running in Safe Mode.
Overriding Safe Mode Settings
As I said above, it is recommended to set default settings which will never cause security problems in php.ini, and enable Safe Mode there. Per-virtual-host or per-directory settings for values such as open_basedir, safe_mode_exec_dir, and safe_mode_include_dir may be specified within httpd.conf using the php_admin_value and php_admin_flag directives.
Consider the following example, which is a (slightly modified) section of an httpd.conf from a live web server I run.
ServerAdmin email@example.com DocumentRoot /home/wwwroot/andrew/ ServerName andrew.somehost.com php_admin_value open_basedir "/home/wwwroot/andrew" php_admin_value open_basedir "/home/wwwroot/:/home/photos/:/usr/local/ lib/php/" php_admin_flag safe_mode off
Here, within the Apache VirtualHost directive, an open_basedir value has been set for the entire virtual host, and overridden for a specific location which requires access to other directories. Safe Mode has been turned off for this location also, again because the gallery software installed there requires functionality which is disabled by Safe Mode.
As you can now clearly see, it is possible to set PHP configuration information on a per-host, per-directory or per-location basis within the httpd.conf file. You will notice also that the open_basedir directories all end with a trailing / so as to prevent them being interpreted as directory prefixes.
How to check for PHP vulnerabilities
The best way to check whether your web site & applications are vulnerable to PHP security attacks is by using a Web Vulnerability Scanner. A Web Vulnerability Scanner crawls your entire website and automatically checks for vulnerabilities to PHP attacks. It will indicate which scripts are vulnerable so that you can fix the vulnerability easily. Besides PHP security vulnerabilities, a web application scanner will also check for SQL injection, Cross site scripting & other web vulnerabilities.