millions of servers worldwide. Serving dynamic pages means giving users
access to commands, files, and network connections on the server, opening up
many potential security risks. We can reduce these risks significantly by
correctly configuring the server, but programmers should also be responsible for making sure their scripts are secure.
In a previous article we looked at installing Apache in a chroot jail. This article looks at the extra steps needed to add PHP to that setup, and
goes on to discuss how to run PHP securely on your server.
Different installation methods
You can install PHP on a server either as a CGI interpreter or an Apache module. Each method has its advantages and disadvantages.
Installing PHP as a CGI interpreter offers the opportunity to have different
PHP processes running as different user IDs under different virtual hosts, using
Apache's suEXEC. However, this method of installation may be slow and is fraught
with risks if misconfigured.
Alternatively, you can install PHP as an Apache module, which is the method we
will concentrate on in this article. When installing as an Apache module you have
a further choice, whether to compile it as a static or dynamic Apache module. A static
module has the advantage of increased performance, but when you wish to upgrade, you
have to recompile both PHP and Apache. With a dynamic module, you lose some
performance and you need to have your Apache compiled to support dynamic modules
mod_so module. Patching and upgrading PHP, however,
is much easier when it is installed as a dynamic module. Your choice will depend upon your particular needs -- specifically, the importance of performance over the convenience of an easy
Compiling Apache and PHP
For a detailed description of installing Apache, refer to my earlier article, Chrooting Apache. In this article we will look only at the extra considerations needed to add PHP to that setup.
When deploying PHP on your Web server, one further consideration is to include mod_security in you Apache installation to provide you with an extra defense against cross-site scripting and SQL injection attacks.
If you are compiling PHP as a static module, you need to compile it
before compiling Apache with the commands:
./configure --with-mysql=/usr/local/mysql --with-apache=/path/to/apache_source --enable-safe-mode
apache1/mod_security.c from the mod_security source to src/modules/extra in your Apache source directory, then compile Apache as normal, adding the options
--activate-module=src/modules/extra/mod_security --enable-module=security to configure.
To install PHP as a dynamic module, install Apache first, then compile your PHP source with the commands:
./configure --with-mysql=/usr/local/mysql --with-apxs=/usr/sbin/apxs --enable-safe-mode
To install mod_security as a dynamic module, extract the sources and
cd. As root run
apxs -cia to install.
Whichever method you use to install PHP, enable only the fewest options you need. Including more functionality means increasing the risk of vulnerabilities. Additionally, especially in a
multiuser environment, I strongly recommend you use the
--enable-safe-mode option (Safe Mode is discussed in detail below).
Install in a chroot environment
If you compiled PHP and mod_security as dynamic modules you need to copy
the modules to your chroot:
cp /usr/libexec/apache/libphp4.so /chroot/httpd/usr/libexec/ cp /usr/libexec/apache/mod_security.so /chroot/httpd/usr/libexec/
You will also need libmysqlclient and some other necessary libraries:
cp /usr/local/mysql/lib/mysql/libmysqlclient.so.12 /chroot/httpd/usr/lib/ cp /usr/lib/libm.so.2 /chroot/httpd/usr/lib/ cp /usr/lib/libz.so.2 /chroot/httpd/usr/lib/
If you have installed PHP with additional features enabled you may need
more libraries. Check with
ldd /usr/libexec/apache/libphp4.so if you installed
as a dynamic module, or
ldd /usr/local/bin/httpd if static.
Now make a /tmp directory in /chroot:
mkdir /chroot/httpd/tmp chown root.root /chroot/httpd/tmp chmod 1777 /chroot/httpd/tmp
Finally, copy the PHP configuration file:
cp /etc/apache/php.ini /chroot/httpd/etc/
Configure Apache to handle PHP
Add the following lines to httpd.conf to enable PHP:
LoadModule php4_module libexec/libphp4.so AddModule mod_php4.c AddType application/x-httpd-php .php AddType application/x-httpd-php .php3 AddType application/x-httpd-php .inc AddType application/x-httpd-php .class
The AddType directives ensure that any files with the extensions .php, .php3,
.inc, and .class are processed as PHP scripts. Many programmers use *.class and *.inc
to name external files included by their scripts. Without these directives these
files would be displayed as plain text in the client's browser, potentially
revealing passwords or other sensitive data.
Another way to achieve the same affect is to use the
directive to deny access to files matching a particular pattern. For example,
<Files ~ "\.inc(.php)?$"> Order allow,deny Deny from all Satisfy All </Files>
denies access to all files ending in .inc or .inc.php, meaning they could
only be accessed using
require() in a
If you don't want it to be immediately obvious that you are running PHP on your server you can use a different extension for your scripts. Using
AddType will have Apache interpret files having the
.dhtml extension as PHP. Mind you, this is a fairly superficial
measure, as there are other ways users can ascertain that you are using PHP.
Defending against injection attacks with mod_security
mod_security is an intrusion detection and prevention engine for Web applications
that operates as an Apache module. It sniffs GET and POST requests to
the server and rejects potentially malicious requests according to the current
In order to enable the module, add the following lines to the httpd.conf file:
LoadModule security_module libexec/mod_security.so AddModule mod_security.c
To configure the module to scan GET and POST requests, add:
<IfModule mod_security.c> # Turn the filtering engine On or Off SecFilterEngine On # Make sure that URL encoding is valid SecFilterCheckURLEncoding On # Unicode encoding check SecFilterCheckUnicodeEncoding On # Only allow bytes from this range SecFilterForceByteRange 0 255 # Only log suspicious requests SecAuditEngine RelevantOnly # The name of the audit log file SecAuditLog logs/audit_log # Debug level set to a minimum SecFilterDebugLog logs/modsec_debug_log SecFilterDebugLevel 0 # Should mod_security inspect POST payloads SecFilterScanPOST On # By default log and deny suspicious requests # with HTTP status 500 SecFilterDefaultAction "deny,log,status:500" </IfModule>
The comments in the code should be enough to explain what each directive does.
This is the most basic configuration possible but will provide the following
Remove multiple forward slashes (//).
Remove self-referenced directories (./).
Perform URL decoding.
Replace null bytes (%00) with spaces.
URL encoding validation.
Unicode encoding validation.
Byte range verification, where only certain character values are allowed as part of a request.
We want to use mod_security particularly to prevent SQL and cross-site
scripting (XSS) attacks. In order to do so we add the following lines within
IfModule block given above:
SecFilter "\.\./" SecFilter "<(.|\n)+>" SecFilter "'" SecFilter "\""
The first line prevents attempted directory traversal attacks, which are
attempts to access files outside the directories containing the current Web
site. The second detects
> in requests,
causing the server to reject requests containing HTML tags and helping to protect
against XSS attacks. The next two lines filter single and double quotes
respectively, making SQL injection attacks difficult.
Using this filtering means that any of these characters
submitted through HTML forms will cause the request to be denied. Programmers will
have to convert forbidden characters to an appropriate special tag (i.e. >,
You can implement less strict detection of XSS attacks by using
SecFilter. This checks only for the
<script tag but
allows other HTML tags.
One of the major concerns of PHP security in a multi-user environment is that
scripts are run under the user ID of the server. This not only means that PHP
users have access to whatever resources the server has access to, but that all
PHP scripts have access to one another, as they all operate with the same
Architecturally this problem is best solved at the operating system level, and, although
various solutions exist, there is no entirely satisfactory solution. In one's absence, the PHP developers introduced safe_mode.
Safe mode ensures that any file that is being accessed by a script is owned
by the owner of the script itself. In order to enable Safe Mode, you must compile PHP with support enabled, as described above, and include the line
safe_mode = On in php.ini. When Safe Mode is turned
on, a slightly more relaxed version of safe mode can be enabled by setting
safe_mode_gid = On. With
safe_mode_gid set to On, PHP
compares not the user ID, but the group ID, of the script and the accessed
There are a number of further options you can set in Safe Mode. The most
safe_mode_exec_dir = /some/dir -- Function calls such as
system() which execute binaries on the server can execute only those
programs present in the specified directory.
safe_mode_allowed_env_vars = PHP_ -- Users can set only the values environment variables beginning with the PHP_ prefix. You can set this prefix to whatever you want (even multiple values, using a comma-delimited list). However, don't leave the option
blank, as this means that users may set any environment variable they wish.
Other PHP configuration options
Asides from Safe Mode there are a number of other security-related options you can set
in the php.ini configuration file:
open_basedir = /some/dir -- limits the files that PHP can open and read
to the specified directory tree. Using . as the directory limits
PHP to the directory in which the current script is contained. You can specify multiple
directories by separating them with colons. The default value of this
option is to allow access to any directory.
disable_functions = function1,function2 -- allows the
administrator to forbid the use of specific functions. Functions should be
listed by name in a comma-separated list.
expose_php = Off -- Normally PHP exposes the fact that it
is installed on a server by adding information to the headers. Setting this
directive to Off prevents this.
display_errors = Off -- Setting this option to Off prevents
PHP from outputting errors to clients' browsers. Such errors could reveal
important configuration information about the server, such as file paths and database schema. I strongly recommend you turn this feature off on a production server.
log_errors = On -- Turn this feature On to have errors logged
to a file. The log file to be used is specified by
error_log = /var/log/php_errors -- The file to write error messages to.
register_globals = Off -- prevents registering ENVIRONMENT, GET, POST,
Cookie, and Built-in variables as globals. Having these values as globals makes accessing them much easier for programmers but greatly increases the security risk if code is not
well-thought-out. I strongly recommend that you set this value to Off. (The default value
is Off in versions 4.2.0 onwards.)
The very nature of running dynamic Web pages on a server makes it a risky
prospect. The methods discussed here decrease the risk of a break-in and reduce the potential damage caused by a breach. As always, stay informed about new vulnerabilities as they arise
and apply security related patches as they become available. Nor should you
neglect the security of the underlying Web server and operating system.
Finally, remember that a portion of the responsibility for the
overall system security depends upon the Web application itself.