PHP web applications are one of the most commonly attacked pieces of software on the Internet today. Anyone who has looked at their web server logs can attest to the frequency of probes for vulnerable PHP applications. PHP's easy learning curve has lead to its popularity and breadth of applications, but not without some hard lessons on the way. The ability to treat a remote HTTP URL as a local file, the auto-instantiation of variables based on client input, and the prevalence of free-form SQL queries have opened up a wide range of attack vectors in PHP applications. Over the years, the interpreter has been improved, dangerous settings have been disabled by default, and a setting called "Safe Mode" has been introduced to limit the impact of a malicious or subverted web application.
The PHP Safe Mode setting is a blacklist approach that restricts certain functions when it is enabled. According to the PHP manual: "safe mode is an attempt to solve the shared-server security problem. It is architecturally incorrect to try to solve this problem at the PHP level, but since the alternatives at the web server and OS levels aren't very realistic, many people, especially ISP's, use safe mode for now". The next major version of PHP (6.0.0) removes Safe Mode completely, which should be a clear sign that it should not be relied upon for shared server security. The core problem with Safe Mode is its inconsistency; in many situations, it works great and limits access to dangerous functions, however, all it takes is one allowed dangerous function to negate it completely.
The current best practice is to combine Safe Mode with a long list of functions for the "disabled_functions" parameter in the php.ini configuration file. This approach applies the Safe Mode restrictions to PHP as a whole and then specifically limits functions that can be used to work around it. Again, the problem with this approach is inconsistency; if even a single dangerous function is missed, the entire process is moot. Depending on where you look on the Internet, the list of functions to disable is completely different. If you combine as many of these lists as possible, you end up with something like the following:
disable_functions = escapeshellarg, escapeshellcmd, exec, passthru, proc_close, proc_get_status, proc_open, proc_nice, proc_terminate, shell_exec, system, ini_restore, popen, dl, disk_free_space, diskfreespace, set_time_limit, tmpfile, fopen, readfile, fpassthru, fsockopen, mail, ini_alter, highlight_file, openlog, show_source, symlink, apache_child_terminate, apache_get_modules, apache_get_version, apache_getenv, apache_note, apache_setenv, parse_ini_file
This list blocks all of the "standard" methods of executing a command or loading arbitrary code. This list also prevents some resource attacks and the ability to open any arbitrary local file. However, if certain extensions are enabled, even this massive list is not enough. Take the Expect module as an example, the expect_popen() function can be used to execute arbitrary commands and is not affected by Safe Mode or the normal list of disabled functions. Other extensions also offer ways around Safe Mode and the function blacklist above. The Apache functions in the list above are rarely disabled; since many administrators either don't realize that the module is available, or underestimate the things that can be done with it. The sample PHP code below uses the apache_setenv() function to force an unrelated CGI script to execute arbitrary native code:
Sometimes a function supports Safe Mode, but is implemented in a way that allows for the restrictions to be bypassed. For example, a vulnerability in the CURL extension (CVE-2007-4850) allowed a user to access any file on the file system by embedding a NULL byte into the URL passed to curl_init(). Over the years, a number of these bypass techniques have been published and subsequently fixed, but the end result is that Safe Mode has rarely been enforced correctly.
To make things even more interesting, the PHP filesystem API allows a handful of different protocols to be used in a path name. This allows the fopen() call to access files over HTTP, FTP, SSH2, stdin/stdout, compressed formats, and a even popen pipes (via Expect).
On Windows servers, the interpreter supports CIFS paths, which can leak authentication credentials and domain information to a third-party. These protocols can be chained together, so a path consisting of "compress.zlib://http://someurl/" would first download a file via HTTP, decompress it with ZLIB, and then return the decompressed content as a file stream. These protocol handlers can be used to evade the disabled function list and access files outside of the Safe Mode constraints.
Securing PHP from malicious and subverted scripts is not easy; even though Safe Mode and the disabled function blacklist can help in some cases, they should be not considered a replacement for OS-level security. Kernel solutions such as GRSecurity and SELinux can be used to isolate the PHP interpreter from other processes on the system and projects such as suPHP make it easy to isolate users from each other. A major benefit of using OS and web server hardening is that many common applications depend on being able to access potentially dangerous functions. Locking down PHP via a lengthy disable_function list will cause problems that may be easier to solve by limiting the privileges of the PHP interpreter through other means.
For these reasons, I believe Safe Mode is harmful, as it can lead to a false sense of security and rarely prevents access by a determined attacker. This will become a non-issue in PHP 6, but until then, any production use of Safe Mode should be considered a symptom of a larger problem with the web infrastructure.