Team LiB
Previous Section Next Section

3.2. Configuration

Configuring PHP can be a time-consuming task since it offers a large number of configuration options. The distribution comes with a recommended configuration file php.ini-recommended, but I suggest that you just use this file as a starting point and create your own recommended configuration.

3.2.1. Disabling Undesirable Options

Working with PHP you will discover it is a powerful tool, often too powerful. It also has a history of loose default configuration options. Though the PHP core developers have paid more attention to security in recent years, PHP is still not as secure as it could be.

3.2.1.1 register_globals and allow_url_fopen

One PHP configuration option strikes fear into the hearts of system administrators everywhere, and it is called register_globals. This option is off by default as of PHP 4.2.0, but I am mentioning it here because:

  • It is dangerous.

  • You will sometimes be in a position to audit an existing Apache installation, so you will want to look for this option.

  • Sooner or later, you will get a request from a user to turn it on. Do not do this.

I am sure it seemed like a great idea when people were not as aware of web security issues. This option, when enabled, automatically transforms request parameters directly into PHP global parameters. Suppose you had a URL with a name parameter:

http://www.apachesecurity.net/sayhello.php?name=Ivan

The PHP code to process the request could be this simple:

<? echo "Hello $name!"; ?>

With web programming being as easy as this, it is no wonder the popularity of PHP exploded. Unfortunately, this kind of functionality led to all sorts of unwanted side effects, which people discovered after writing tons of insecure code. Look at the following code fragment, placed on the top of an administration page:

<?
if (isset($admin) =  = false) {
    die "This page is for the administrator only!";
}
?>

In theory, the software would set the $admin variable to TRue when it authenticates the user and figures out the user has administration privileges. In practice, appending ?admin=1 to the URL would cause PHP to create the $admin variable where one is absent. And it gets worse.

Another PHP option, allow_url_fopen, allows programmers to treat URLs as files. (This option is still on by default.) People often use data from a request to determine the name of a file to read, as in the following example of an application that expects a parameter to specify the name of the file to execute:

http://www.example.com/view.php?what=index.php

The application then uses the value of the parameter what directly in a call to the include() language construct:

<? include($what) ?>

As a result, an attacker can, by sending a path to any file on the system as parameter (for example /etc/passwd), read any file on the server. The include( ) puts the contents of the file into the resulting web page. So, what does this have to do with allow_url_fopen? Well, if this option is enabled and you supply a URL in the what parameter, PHP will read and execute arbitrary code from wherever on the Internet you tell it to!

Because of all this, we turn off these options in the php.ini file:

allow_url_fopen = Off
register_globals = Off

3.2.1.2 Dynamic module loading

I have mentioned that, like Apache, PHP uses modules to extend its functionality dynamically. Unlike Apache, PHP can load modules programmatically using the dl( ) function from a script. When a dynamic module is loaded, it integrates into PHP and runs with its full permissions. Someone could write a custom extension to get around the limitations we impose in the configuration. This type of attack has recently been described in a Phrack article: "Attacking Apache with builtin Modules in Multihomed Environments" by andi@void (http://www.phrack.org/phrack/62/p62-0x0a_Attacking_Apache_Modules.txt).

The attack described in the article uses a custom PHP extension to load malicious code into the Apache process and take over the web server. As you would expect, we want this functionality turned off. Modules can still be used but only when referenced from php.ini:

enable_dl = Off

3.2.1.3 Display of information about PHP

I mentioned in Chapter 2 that Apache allows modules to add their signatures to the signature of the web server, and told why that is undesirable. PHP will take advantage of this feature by default, making the PHP version appear in the Server response header. (This allows the PHP Group to publish the PHP usage statistics shown at http://www.php.net/usage.php.) Here is an example:

Server: Apache/1.3.31 (Unix) PHP/4.3.7

We turned this feature off on the Apache level, so you may think further action would be unnecessary. However, there is another way PHP makes its presence known: through special Easter egg URLs. The following URL will, on a site with PHP configured to make its presence known, show the PHP credits page:

http://www.example.com/index.php?=PHPB8B5F2A0-3C92-11d3-A3A9-4C7B08C10000

There are three more special addresses, one for the PHP logo, the Zend logo, and the real Easter egg logo, respectively:

PHPE9568F34-D428-11d2-A769-00AA001ACF42
PHPE9568F35-D428-11d2-A769-00AA001ACF42
PHPE9568F36-D428-11d2-A769-00AA001ACF42

The Easter egg logo will be shown instead of the official PHP logo every year on April 1. Use the expose_php configuration directive to tell PHP to keep quiet. Setting this directive to Off will prevent the version number from reaching the Server response header and special URLs from being processed:

expose_php = Off

3.2.2. Disabling Functions and Classes

The PHP configuration directives disable_functions and disable_classes allow arbitrary functions and classes to be disabled.

One good candidate function is openlog( ). This function, with syslog( ), allows PHP scripts to send messages to the syslog. Unfortunately, the function allows the script to change the name under which the process is visible to the syslog. Someone malicious could change this name on purpose and have the Apache messages appear in the syslog under a different name. The name of the logging process is often used for sorting syslog messages, so the name change could force the messages to be missed. Fortunately, the use of openlog( ) is optional, and it can be disabled.

disable_functions = openlog

Some PHP/Apache integration functions (listed below and available only when PHP is used as an Apache module) can be dangerous. If none of your scripts require this functionality, consider disabling them using the disable_functions directive:

apache_child_terminate
apache_get_modules
apache_get_version
apache_getenv
apache_note
apache_setenv
virtual

3.2.3. Restricting Filesystem Access

The most useful security-related PHP directive is open_basedir. It tells PHP which files it can access. The value for the directive consists of a list of file prefixes, separated by a colon on Unix or a semicolon on Windows. The restrictions imposed by this directive apply to PHP scripts and (data) files. This option should be used even on servers with only one web site, and it should be configured to point one folder up from the web server root, which for the purposes of this book we set to /var/www/htdocs. Given that web server root, here is how open_basedir should be set:

open_basedir = /var/www/

The setting above will allow the PHP engine to run the scripts that are under the web server root (/var/www/htdocs) and to access the data files that are stored in a private area (/var/www/data). If you do not need nonpublic files, allow PHP to access the web server tree only by restricting PHP to /var/www/htdocs instead.

Know the difference between restrictions to a folder and restrictions to a prefix. For example, if were we to set the value of the directive to /var/www, scripts would be able to access the files in /var/www and /var/www2. By having the slash at the end (as in the example above), the scripts are prevented from going outside /var/www.


In Chapter 2, I described a method of restricting Apache into its own filesystem. That type of protection uses the operating system features and results in robust protection, so a process cannot access outside files even when it wants to. In contrast, the open_basedir restrictions in PHP are a form of self-discipline. The developers of PHP have attempted to add special checks wherever files are accessed in the source code. This is a difficult task, and ways to trick PHP are published online from time to time. Controlling third-party modules is nearly impossible. A good example is this Bugtraq message:

"PHP4 cURL functions bypass open_basedir" (http://www.securityfocus.com/archive/1/379657/2004-10-26/2004-11-01/0)

In the message, the author describes how the cURL PHP extension can be used to bypass open_basedir restrictions.

Another directive, doc_root, sounds suspiciously like a synonym for open_basedir, but it isn't. This one only works when PHP is used as a CGI script and only to limit which scripts will be executed. (Details are available at http://www.php.net/security.cgi-bin.)

3.2.4. Setting Logging Options

Not all PHP errors are logged by default. Many useful messages are tagged with the level E_NOTICE and overlooked. Always set error logging to the maximum:

error_reporting = E_ALL
log_errors = On

To see any errors, you need to turn error logging on. This is done using the error_log configuration option. If this option is left unspecified, the errors go to the standard error output, typically the Apache error log. Otherwise, error_log accepts the following values:


syslog

Errors are sent to the system's syslog.


<filename>

By putting an actual filename as the parameter, you tell PHP to write all errors to the specified separate log file.

When using a separate file for PHP logging, you need to configure permissions securely. Unlike the Apache logs, which are opened at the beginning when Apache is still running as root, PHP logs are created and written to later, while the process is running as the web server user. This means you cannot place the PHP error log into the same folder where other logs are. Instead, create a subfolder and give write access to the subfolder to the web server user (httpd):

# cd /var/www/logs
# mkdir php
# chown httpd php

In the php.ini file, configure the error_log option:

error_log = /var/www/logs/php/php_error_log

The option to display errors in the HTML page as they occur can be very useful during development but dangerous on a production server. It is recommended that you install your own error handler to handle messages and turn off this option. The same applies to PHP startup errors:

display_errors = Off
display_startup_errors = Off

3.2.5. Setting Limits

When PHP is compiled with a --enable-memory-limit (I recommend it), it becomes possible to put a limit on the amount of memory a script consumes. Consider using this option to prevent badly written scripts from using too much memory. The limit is set via the memory_limit option in the configuration file:

memory_limit = 8M

You can limit the size of each POST request. Other request methods can have a body, and this option applies to all of them. You will need to increase this value from the default value specified below if you plan to allow large file uploads:

post_max_size = 8M

The max_input_time option limits the time a PHP script can spend processing input. The default limit (60 seconds) is likely to be a problem if clients are on a slow link uploading files. Assuming a speed of 5 KBps, they can upload only 300 KB before being cut off, so consider increasing this limit:

max_input_time = 60

The max_execution_time option limits the time a PHP script spends running (excluding any external system calls). The default allowance of 30 seconds is too long, but you should not decrease it immediately. Instead, measure the performance of the application over its lifetime and decrease this value if it is safe to do so (e.g., all scripts finish way before 30 seconds expire):

max_execution_time = 30

3.2.6. Controlling File Uploads

File uploads can be turned on and off using the file_uploads directive. If you do not intend to use file uploads on the web site, turn the feature off. The code that supports file uploads can be complex and a place where frequent programming errors occur. PHP has suffered from vulnerability in the file upload code in the past; you can disable file uploading via the following:

file_uploads = Off

If you need the file upload functionality, you need to be aware of a parameter limiting the size of a file uploaded. More than one file can be uploaded to the server in one request. The name of the option may lead you to believe the limit applies to each separate file, but that is not the case. The option value applies to the sum of the sizes of all files uploaded in one go. Here is the default value:

upload_max_filesize = 2M

Remember to set the option post_max_size to a value that is slightly higher than your upload_max_filesize value.

As a file is uploaded through the web server before it is processed by a script, it is stored on a temporary location on disk. Unless you specify otherwise, the system default (normally /tmp on Unix systems) will be used. Consider changing this location in the php.ini configuration file:

upload_tmp_dir = /var/www/tmp

Remember to create the folder:

# cd /var/www
# mkdir tmp
# chown httpd tmp

3.2.7. Increasing Session Security

HTTP is a stateless protocol. This means that the web server treats each user request on its own and does not take into account what happened before. The web server does not even remember what happened before. Stateless operation is inconvenient to web application programmers, who invented sessions to group requests together.

Sessions work by assigning a unique piece of information to the user when she arrives at the site for the first time. This piece of information is called a session identifier (sessionid for short) The mechanism used for this assignment is devised to have the user (more specifically, the user's browser) return the information back to the server on every subsequent request. The server uses the sessionid information to find its notes on the user and remember the past. Since a session identifier is all it takes for someone to be recognized as a previous user, it behaves like a temporary password. If you knew someone's session identifier, you could connect to the application she was using and assume the same privileges she has.

Session support in PHP enables an application to remember a user, keeping some information between requests. By default, the filesystem is used to store the information, usually in the /tmp folder. If you take a look at the folder where PHP keeps its session information, you will see a list of files with names similar to this one:

sess_ed62a322c949ea7cf92c4d985a9e2629

Closer analysis will reveal that PHP uses session identifiers when it constructs file names for session data (the session identifier is the part after sess_). As a consequence, any system user who can list the contents of the /tmp folder can learn all the active session identifiers and hijack sessions of any of the active users. To prevent this, you need to instruct PHP to store session data in a separate folder, which only the Apache user (httpd) can access. Create the folder first:

# cd /var/www
# mkdir sessions
# chown httpd sessions

Then configure PHP to store session data at the new location:

session.save_path = /var/www/sessions

This configuration change does not solve all problems though. System users will not be able to learn about session identifiers if the permissions for the folder /var/www/sessions are configured to deny them access. Still, for any user that can write and execute a PHP script on the server, it will be trivial to write a program to retrieve the list of sessions because the script will run as the web server user.

Multiple applications, user groups, or web sites should never share the same session directory. If they do, they might be able to hijack each other's sessions. Create a separate session directory for each different purpose.


Casual session ID leaks and hijacking attempts can be prevented with the help of the session.referer_check option. When enabled, PHP will check the contents of the Referer request header for the string you provide. You should supply a part of the site domain name:

# comment
session.referer_check = apachesecurity.net

Since the Referer request header contains the URL of the user's previous page, it will contain the site's domain name for all legitimate requests. But if someone follows a link from somewhere else and arrives at your site with a valid session ID, PHP will reject it. You should not take this protection seriously. This option was designed to invalidate sessions that were compromised by users accidentally posting links that contained session IDs. However, it will also protect from simple cross-site request forgery (CSRF) attacks, where a malicious site creates requests to another site using the existing user session. When the attacker completely controls the request, he also controls the contents of the Referer header, making this feature ineffective.

When this option is enabled, then even users whose browsers support cookies (and are thus using cookies for session management) will have their sessions invalidated if they follow a link from somewhere else back to your site. Therefore, since session.referer_check does not solve any problem in its entirety, I recommend that a proper session hijack defense be built into the software, as described in Chapter 10.

3.2.8. Setting Safe Mode Options

Safe mode (http://www.php.net/manual/en/features.safe-mode.php) is an attempt of PHP developers to enhance security of PHP deployments. Once this mode is enabled, the PHP engine imposes a series of restrictions, making script execution more secure. Many developers argue that it is not the job of PHP to fix security problems caused by the flawed architecture of server-side programming. (This subject is discussed in detail in Chapter 6.) However, since there is no indication this model will be changed any time soon, the only choice is to go ahead and do what can be done now.

Safe mode is implemented as a set of special checks in the PHP source code, and checks are not guaranteed to exist in all places. Occasionally, someone reports a hole in the safe mode and PHP developers fix it. Furthermore, there may be ways to exploit the functionality of PHP modules included in the installation to gain unrestricted access.

That being said, the PHP safe mode is a useful tool. We start by turning on the safe mode:

safe_mode = On

3.2.8.1 File access restrictions

The biggest impact of safe mode is on file access. When in safe mode, an additional check is performed before each filesystem operation. For the operation to proceed, PHP will insist that the uid of the file owner matches the uid of the user account owning the script. This is similar to how Unix permissions work.

You can expect problems in the following cases:

  • If more than one user has write access for the web server tree. Sooner or later, a script owned by one user will want to access a file owned by another.

  • If applications create files at runtime.

This second case is the reason programmers hate the safe mode. Most PHP applications are content management systems (no surprise there since PHP is probably the best solution for web site construction), and they all create files. (These issues are covered in Chapter 6.)

The easiest solution is to have the developer and Apache accounts in the same group, and relax uid checking, using gid checking instead:

safe_mode_gid = On

Since all PHP scripts include other scripts (libraries), special provisions can be made for this operation. If a directory is in the include path and specified in the safe_mode_include_dir directive, the uid/gid check will be bypassed.

3.2.8.2 Environment variable restrictions

Write access to environment variables (using the putenv() function) is restricted in safe mode. The first of the following two directives, safe_mode_allowed_env_vars, contains a comma-delimited list of prefixes indicating which environment variables may be modified. The second directive, safe_mode_protected_env_vars, forbids certain variables (again, comma-delimited if more than one) from being altered.

# allow modification of variables beginning with PHP_
safe_mode_allowed_env_vars = PHP_
# no one is allowed to modify LD_LIBRARY_PATH
safe_mode_protected_env_vars = LD_LIBRARY_PATH

3.2.8.3 External process execution restrictions

Safe mode puts restrictions on external process execution. Only binaries in the safe directory can be executed from PHP scripts:

safe_mode_exec_dir = /var/www/bin

The following functions are affected:

  • exec( )

  • system( )

  • passthru( )

  • popen( )

Some methods of program execution do not work in safe mode:


shell_exec( )

Disabled in safe mode


backtick operator

Disabled in safe mode

3.2.8.4 Other safe mode restrictions

The behavior of many other less significant functions, parameters, and variables is subtly changed in safe mode. I mention the changes likely to affect many people in the following list, but the full list of (constantly changing) safe mode restrictions can be accessed at http://www.php.net/manual/en/features.safe-mode.functions.php:


dl( )

Disabled in safe mode.


set_time_limit( )

Has no effect in safe mode. The other way to change the maximum execution time, through the use of the max_execution_time directive, also does not work in safe mode.


header( )

In safe mode, the uid of the script is appended to the WWW-Authenticate HTTP header.


apache_request_headers( )

In safe mode, headers beginning with Authorization are not returned.


mail( )

The fifth parameter (additional_parameters) is disabled. This parameter is normally submitted on the command line to the program that sends mail (e.g., sendmail).


PHP_AUTH variables

The variables PHP_AUTH_USER, PHP_AUTH_PW, and AUTH_TYPE are unavailable in safe mode.

    Team LiB
    Previous Section Next Section