Hardening PHP from php.ini
PHP's default configuration file, php.ini (usually found in /etc/php.ini on most Linux systems) contains a host of functionality that can be used to help secure your web applications. Unfortunately many PHP users and administrators are unfamiliar with the various options that are available with php.ini and leave the file in it's stock configuration. By utilizing a few of the security related options in the configuration file you can greatly strengthen the security posture of web applications running on your server.
PHP safe mode is a comprehensive "attempt to solve the shared server security problem" that includes many useful features. Note that safe mode support is being removed in PHP 6. Safe mode effectively checks if functions in one file on the server that affect other files all have the same ownership. For instance, if you have a page script1.php that attempts to read the contents of a directory img/. Safe mode with check the UID of script1.php and the img/ directory. If they match then the script will be allowed access, if they don't match then safe mode will disable access. This is an interesting security mechanism that allows you to restrict access by scripts outside of the normal application installation directory. Safe mode may cause problems though when the web server ends up owning files (for instance when a new file is uploaded or created by an application it is usually owned by 'apache' or a similar web server account).
Safe mode will also restrict executables that may be run by scripts in the same way it restricts file and directory access. Safe mode can also be configured so that only executables in a certain directory can be run. This can help limit exposure of shell commands to certain scripts.
To enable safe mode, alter (or add) the safe mode directive in the php.ini to:
safe_mode = On
In some cases you'll want to use a group to check ownership (for instance in the case that you have multiple people deploying web application scripts). To have safe mode check group permissions use:
safe_mode_gid = On
If you want to limit directories that can contain included files or executables use the following php.ini directives respectively:
safe_mode_include_dir = /path/to/dir safe_mode_exec_dir = /path/to/exec/dir
Safe mode has several other useful features that are worth looking into. Browse the documentation at the PHP website and see if safe mode is right for your environment.
Using the open_basedir directive in PHP makes a lot of sense given most file include vulnerability vectors. This directive limits all PHP file operations to the listed directory and below. It is common for attackers to search for ways to include local files in PHP scripts to expose local filesystem files through the web server. For instance, if an attacker found a file inclusion vulnerability they might try to include the /etc/passwd file to enumerate all the user accounts on the system. With the open_basedir directive PHP can restrict file inclusion to the web root, for instance /var/www. Once set files outside that directory cannot be included in scripts, and thus the aforementioned attack would fail. To enable the open_basedir directive update your php.ini file to include:
open_basedir = /path/to/web/root
There are certain functions in PHP that you probably don't want your developers to use because of the danger they pose. Even if you know your users aren't utilizing certain functions it is wise to completely disable them so an attacker can't use them. This security precaution is especially effective at stopping an attacker who has somehow managed to upload a PHP script, write one to the filesystem, or even include a remote PHP file. By disabling functionality you ensure that you can limit the effectiveness of these types of attacks. It should be noted that it is virtually impossible to do something like preventing an attacker from executing a command at a shell by disabling functions, but it can certainly stop an attacker who isn't a skillful PHP programmer.
By disabling functions like shell_exec() and system() you can prevent users and attackers from utilizing these functions. It is important to restrict functionality for developers because use of these command opens the potential for a remote code execution vulnerability if not utilized with great care. There are certainly cases for operations such as executing a command at a shell, but PHP provides a drove of functions that are essentially the same. Developers can standardize on one such function and the rest can be disabled to help prevent attacks. While this isn't a foolproof solution it will probably prevent attacks like the dreaded c99 shell. To enable the disable_functions directive simply add it to your php.ini with a comma separated list of functions you want to restrict. For instance:
disable_functions = php_uname, getmyuid, getmypid, passthru, leak, listen, diskfreespace, tmpfile, link, ignore_user_abord, shell_exec, dl, set_time_limit, exec, system, highlight_file, source, show_source, fpaththru, virtual, posix_ctermid, posix_getcwd, posix_getegid, posix_geteuid, posix_getgid, posix_getgrgid, posix_getgrnam, posix_getgroups, posix_getlogin, posix_getpgid, posix_getpgrp, posix_getpid, posix, _getppid, posix_getpwnam, posix_getpwuid, posix_getrlimit, posix_getsid, posix_getuid, posix_isatty, posix_kill, posix_mkfifo, posix_setegid, posix_seteuid, posix_setgid, posix_setpgid, posix_setsid, posix_setuid, posix_times, posix_ttyname, posix_uname, proc_open, proc_close, proc_get_status, proc_nice, proc_terminate, phpinfo
Preventing Information Disclosure
Attackers will often use information that your web server exposes in order to gain information about the server configuration, application layout, and components. Error messages are some of the most common paths to information disclosure, often leaking information such as application installation path, database connectivity, data model details such as table and column names, and script details such as variables. While this debugging information is invaluable to developers it is useless to end users and dangerous to expose to attackers. PHP debugging output should be disabled in the php.ini using:
display_errors = Off
This prevents PHP from showing run time errors in pages served to users. PHP will continue to log the errors as normal, however, so they can be reviewed by developers. Be wary of developer tactics to end run PHP errors, however, as disabling this functionality does not prevent information disclosure. Some developers may use custom debugging output nested in HTML comments, third party tools like FirePHP, or writing PHP error logs to local directories using .htaccess files and the error_log directive. However, by preventing the display of errors by default you reduce the possibility of exposing information to attackers.
Global variables are a horrible hold over from the PHP 3 days. In most distributions register global variables is set to off (and thankfully it won't be supported in future versions of PHP). However, you should ensure that the directive is properly in place. You should find the following in your php.ini file:
register_globals = Off
Register globals allows various HTTP variables to be used without specifying their source. For instance, if a developer wants to use a URL variable named 'id', for instance from the URL request index.php?id=4, with globals they can simply use $id rather than $_GET['id']. This is a great convenience but it can cause collisions. For instance, if a form post uses a variable called 'id' and there is a variable $id defined in a script and a user alters the URL of the script to include an 'id=' in the URL which variable has precedence? Even more damaging is the ability of attackers to override configuration variables such as DOCUMENT_ROOT from the URL. This can cause no end of problems, especially if attackers are able to call scripts that are normally included in other scripts and expect predefined variables, which could be overwritten via GET variables by an attacker.
Many legacy applications may require globally registered variables. If this is the case at least limit the configuration to specific application directories rather than throughout your PHP installation. You can do this using PHP directives in .htaccess files included in specific directories. Ensure that register_globals is set to Off, however, in your php.ini configuration!
Disable Remote File Includes
Attackers will often attempt to identify file inclusion vulnerabilities in applications then use them to include malicious PHP scripts that they write. Even if an attacker doesn't have write access to the web application directories if remote file inclusion is enabled the attacker can host malicious PHP scripts on other servers and the web application will fetch them and execute them locally! This can have devastating consequences. To restrict remote file execution be sure the following appears in your php.ini file:
allow_url_fopen = Off allow_url_include = Off
This prevents remote scripts from being included and executed by scripts on your system.
Restrict File Uploads
If you're not utilizing file upload functionality in any of your PHP scripts then it's a good idea to turn it off. Attackers will attempt to (mis)use file uploads to quickly inject malicious scripts into your web applications. By disabling file uploads altogether this makes moving scripts onto your web server more difficult. To disable file uploads change the file_uploads directive in your php.ini to read:
file_uploads = Off
Even if you do allow file uploads you should change the default temporary directory used for file uploads. This can be done by changing the upload_tmp_dir directive. You may also want to restrict the size of files that can be uploaded. This is usually more of a system administration alteration than a security fix, but it can be useful. Use the upload_max_filesize directive for this purpose. To restrict upload directories and file sizes change your php.ini so that it reads:
upload_tmp_dir = /var/php_tmp upload_max_filezize = 2M
Session stealing is a popular attack that allows a malicious user to hijack the session of a legitimate user. Using session hijacking an attacker can bypass authorization and access portions of web applications without authorization. PHP uses strong (meaning long pseudo randomly generated) session identifiers so that guessing a session id is extremely difficult. When logging into a PHP application you can view your cookies and likely identify a cookie with an name like 'phpsessid' and a value similar to 'bbbca6bb7a23bdc8de3baef2b506e654'. The cookie is composed of 32 hexadecimal characters, making it extremely hard to predict. The flaw in this system, however, is that these session identifiers are written to the filesystem when they're created so PHP can keep track of them. Changing the default location of these session identifiers will confound some attempts to read them. To change the location where session information is written alter the session.save_path in the php.ini configuration so that it points to your desired location like so:
session.save_path = /var/lib/php
session.cookie_httponly = 1
session.referer_check = your_url.tld
For more information about session security see http://devzone.zend.com/manual/ref.session.html.
Implementing these security features within your PHP configuration isn't a recipe for complete security, but it does increase the overall security posture of your web applications. By combining these measures with others, such as Suhosin and an intrusion detection system like OSSEC you incrementally increase the security of your server and web applications. You must be careful to implement configurations that restrict functionality that could be used to the detriment of your installation but not to restrict developers. Frustrating developers is a sure fire recipe for home grown solutions to end run your restrictions and invariably these solutions weaken the overall security of your server and often introduce vulnerabilities. Take care to harden your servers as much as possible, but don't become over zealous. Beginning the process of server hardening with your php.ini configuration is a great step as it affects all the PHP web applications installed on the server and can be applied incrementally. Remember to restart your web server after making changes to the php.ini file so that those changes are put into effect.