The challenge with securing a shared hosting server is how to secure the website from attack both from the outside and from the inside. PHP has built-in features to help, but ultimately it s the wrong place to address the problem.

I’ve already written about a number of solutions that work, but one option I’ve been asked time and time again to look at is using PHP + FastCGI. The belief is that using FastCGI will overcome the performance issues of Apache’s suexec or mod_suphp, because FastCGI processes persist between page views.

But before we can look at performance, the first question is: how exactly do we get PHP and FastCGI running as different users on the one web server in the first place?

Installing And Configuring mod_fcgid for Apache

Stock Apache does not ship with built-in support for FastCGI. You need to download and install a third-party module. There are two choices – the original mod_fastcgi, and the more recent mod_fcgid, which I’ll look at in this article. Before we start, make sure that you have built and configured Apache to use suexec. We will reuse suexec to ensure that our FastCGI PHP processes run as different users.

Most Linux distributions already include a package for mod_fcgid; you should be able to install it using your distro’s package manage. The version I’ve tested for this article is mod_fcgid 2.2 running on Seed Linux (a Gentoo-based distro).

After installing mod_fcgid, make sure that you edit your Apache config files, and comment out any lines that load mod_php. They will look like this:

LoadModule php5_module modules/libphp5.so

Then, add the following lines to your virtual host.

SuexecUserGroup myuser mygroup

<Directory /var/www/localhost/htdocs>
AddHandler fcgid-script .php
Options ExecCGI
Allow from all
FCGIWrapper /var/www/localhost/cgi-bin/php.fcgi .php
</Directory>

Replace “myuser” with the user who owns the website, and replace “mygroup” with the group that the user belongs to. This sets the privileges that PHP will run as when this website is visited.

Because suexec is understandably paranoid about what CGI programs it will run for you, to make mod_fcgid work in a shared hosting environment, we need to create a FastCGI wrapper script owned by the same user that owns the website:

#!/bin/bash
PHPRC=/etc/php/apache2-php5
export PHPRC
PHP_FCGI_CHILDREN=4
export PHP_FCGI_CHILDREN
PHP_FGCI_MAX_REQUESTS=5000
export PHP_FCGI_MAX_REQUESTS
exec /usr/lib/php5/bin/php-cgi

Each website needs its own copy of the script. Place this script in the website’s dedicated cgi-bin directory. This should be a directory that you control to make sure that malicious scripts cannot be uploaded to take advantage of suexec. Make sure that the script is owned by the user and group who owns the website, otherwise suexec will refuse to run the script, and you’ll spend quite a bit of time scratching your head wondering what the problem is!

The FastCGI wrapper script gives us an opportunity to set limits on how PHP works as a FastCGI process. We can tell it how many FastCGI scripts are allowed to run (to make sure one website doesn’t use up all of the web server’s free capacity), and also how many HTTP requests each FastCGI process should handle before terminating (to limit the impact of memory leaks).

At this point, you can restart Apache, and you should find that your websites are now using suexec + FastCGI to run as separate users.

Making Apache Go Even Faster

One of the major benefits of using Apache 2.2 over Apache 1.3 is the ability to switch how Apache works at the fundamental level. Apache MPMs (multi-processing modules) can emulate Apache 1.3’s behaviour (mpm-prefork) … but it can also provide new options. By default, most (if not all) Linux distributions install Apache 2.2 built with mpm-prefork, but by switching to another MPM, can we make our websites go even faster?

If you are using suexec + mod_fcgid on Linux, there are two MPMs available to you that have the potential to boost performance further: mpm-worker and mpm-event. Both MPMs turn Apache into a multi-threaded server. On Linux systems, it is usually much quicker to create new threads than it is to create new processes. The downside is that software has to be specially written to work correctly in a multi-threaded application (known as being thread-safe). mod_php doesn’t work reliably with mpm-worker and mpm-event, because it reuses a lot of third-party code that may or may not be thread-safe. But because we’re running PHP in a separate FastCGI process, we can safely turn Apache into a multi-threaded server.

Some Benchmarks

To benchmark PHP + FastCGI + suexec, I used Apache s ab benchmark to load a simple phpinfo() page 1,000 times. I ran the benchmark five times, and averaged the results. To compare the results, I repeated the tests against mpm-worker, mpm-event, and mpm-prefork both with and without mod_php.

  • mpm-worker + mod_fcgid + PHP/FastCGI + suexec: 7.36 seconds, 0.2% failure rate
  • mpm-event + mod_fcgid + PHP/FastCGI + suexec: 7.75 seconds, 0.2 % failure rate
  • mpm-prefork + mod_fcgid + PHP/FastCGI + suexec: 7.92 seconds, 0.2% failure rate
  • mpm-prefork + mod_fastcgi + PHP/FastCGI + suexec: 8.52 seconds, 0.2% failure rate
  • mpm-prefork + mod_php: 7.38 seconds, 0% failure rate

Other Considerations

The performance is good, especially if you switch Apache MPMs. These benchmarks are extremely simplistic, and what they don’t show is that switching to mpm-worker and mpm-event will probably speed up your websites even further, because these Apache MPMs handle downloading images more efficiently than mpm-prefork can. You may also be able to scale your websites better before having to upgrade your servers or add additional ones, especially if you use a bytecode cache such as APC or xcache.

But what are the downsides?

  • As with straight suexec, you can’t use HTTP authentication in your application. Hardly any apps rely on this functionality any more (probably because so many shared hosting servers use suexec).
  • Your server may require extra RAM to cope with the number of FastCGI processes running simultaneously. You may need to switch to a 32-bit Linux kernel that supports PAE or to a 64-bit Linux distro.
  • Apache + PHP/FastCGI is not 100% reliable in my testing.

Conclusions

It is possible to combine PHP, FastCGI and suexec to produce a solution that secures a shared hosting server and at the same time provides good performance compared to the alternatives. If you’re prepared to compile Apache from source and switch MPMs, you can squeeze even more performance from this combination, and perhaps even out-perform the venerable mod_php.

Unfortunately, my experience was that the PHP + FastCGI combination cannot be trusted to serve pages 100% of the time. The average failure rate was 2 requests per 1000, and the failure rate was consistent no matter which Apache MPM was used, which Apache FastCGI module was used, and how many thousands of requests I used in my testing. At the time of writing, I haven’t tracked down the cause of this failure, and it may not appear in your own environment, but none of the previous solutions I’ve looked at in this series have displayed this problem, so it’s something to think about before chosing PHP + FastCGI to serve your websites. I’m hoping to find time in the future to get to the bottom of this problem, if no-one gets there first.

As a result, I can’t recommend using PHP/FastCGI + suexec at this time. My current recommendation is mpm-itk, which has successfully served millions of page hits for me in production over the last few months.

References

This article was made possible by information already on the internet:

This article is part of The Web Platform, an on-going series of blog posts about the environment that you need to create and nurture to run your web-based application in. If you have any topics that you d like to see covered in future articles, please leave them in the comments on this page.

14 comments »

The challenge with securing a shared hosting server is how to secure the website from attack both from the outside and from the inside. PHP has built-in features to help, but ultimately it s the wrong place to address the problem. Apache has built-in features too, but the performance cost of these features is prohibitive.

This has created a gap that a number of third-party solutions have attempted to fill. One solution you may have heard of is mpm-itk, by Steinar H. Gunderson. How well does it work, and how well does it perform?

  • mpm-itk: Running Apache As A Specified User
  • Installing mpm-itk
  • Configuring Apache
  • Some Benchmarks
  • Other Considerations
  • Conclusions

mpm-itk: Running Apache As A Specified User

Like mpm-peruser, mpm-itk is an alternative multi-processing module (MPM) for Apache 2.x. It also allows each website’s PHP scripts to run as a separate user. But the main difference is that it doesn’t maintain separate pools of processes for each user. Instead, after the PHP request has completed, each process is terminated, and new processes must be created to handle new requests.

Until I researched mpm-itk for this article, I didn’t realise that it didn’t recycle processes after each request. This means that there’s no chance at all of it matching mpm-peruser for performance (something I suggested was possible), but that doesn’t mean that mpm-itk is entirely without merit.

Installing mpm-itk

mpm-itk needs to be compiled into your Apache installation. It cannot be loaded as a module.

First of all, download the Apache source code, and then download either the mpm-itk patch for Apache 2.0, or the mpm-itk patch for Apache 2.2. For this article, I’m going to focus on Apache 2.2, but the same instructions should apply for Apache 2.0.

Unpack the Apache source code, apply the mpm-itk patch, and rebuild Apache’s build scripts:

$ mkdir -p /tmp/apache-itk
$ cd /tmp/apache-itk
$ wget http://www.mirrorservice.org/sites/ftp.apache.org/httpd/httpd-2.2.8.tar.gz
$ wget http://mpm-itk.sesse.net/apache2.2-mpm-itk-20080105-00.patch
$ tar -zxf httpd-2.2.8.tar.gz
$ cd httpd-2.2.8
$ patch -p1 < ../apache2.2-mpm-itk-20080105-00.patch
$ autoconf

Then, configure the Apache source code to build with mpm-itk as the chosen MPM. Make sure that you run configure with any other configuration switches that you need:

$ ./configure --with-mpm=itk

After that, compile and install Apache:

$ make ; make install

Configuring Apache

mpm-itk is very easy to configure. For each of your virtual hosts, simply add the AssignUserId entry:

<VirtualHost *:80>
ServerName www.example.com
...

<IfModule mpm_itk_module>
AssignUserId stuart stuart
</IfModule>
</VirtualHost>

AssignUserId takes two parameters:

  • The first parameter is the user ID to run Apache under for this website.
  • The second parameter is the group ID to run Apache under for this website.

Remember to restart Apache after adding AssignUserId, and you should be all set.

Some Benchmarks

To benchmark mpm-itk, I used Apache s ab benchmark to load a simple phpinfo() page 1,000 times. I ran the benchmark five times, and averaged the results.

  • mpm-itk: average of 37.01 seconds
  • mpm-prefork: average of 6.21 seconds

mpm-itk benchmarks much better than suexec and suphp, but is still quite a bit slower than mpm-peruser.

Other Considerations

It isn’t just about performance. Both suexec and suphp bring limitations to your PHP applications, but mpm-itk does not. Because mpm_itk puts the job of switching users in the right place – at the heart of Apache – it allows your code to run under mod_php. As a result, your code is free to take advantage of any Apache features that aren’t available to PHP/CGI, such as HTTP authentication support.

Another consideration is the impact on RAM and CPU. Whilst you can definitely use mpm-peruser to provide a faster solution, it does involve a lot of effort in tuning the size of the process pools for each of the websites on a shared server. On a shared hosting server, you can’t necessarily find one tuned configuration that always suits demand – and it may not be worth your time to put the effort in anyway. Although mpm-itk is slower, it doesn’t need tuning for each individual website. It’s more of a fire-and-forget solution that might appeal to hosting providers who don’t know (and don’t really need to care) what your customers websites are.

Conclusions

Although it needs to be compiled from source, mpm-itk provides the security of suexec and suphp with much greater performance than either of these solutions. Although it performs worse than mpm-peruser, mpm-itk doesn’t require as much effort to configure and tune for best performance, and its greater simplicity probably makes it better suited to shared hosting servers running a random collection of websites.

mpm-itk is an option that you should seriously consider when designing your shared hosting server solution.

This article is part of The Web Platform, an on-going series of blog posts about the environment that you need to create and nurture to run your web-based application in. If you have any topics that you d like to see covered in future articles, please leave them in the comments on this page.

Be the first to leave a comment »

The challenge with securing a shared hosting server is how to secure the website from attack both from the outside and from the inside. PHP has built-in features to help, but ultimately it s the wrong place to address the problem. Apache has built-in features too, but the performance cost of these features is prohibitive.

This has created a gap that a number of third-party solutions have attempted to fill. One solution you may have heard of is mpm-peruser, by Telana Internet Services. How well does it work, and how well does it perform?

  • A Bit of History
  • Installing mpm-peruser
  • Configuring Apache
  • Some Benchmarks
  • Other Considerations
  • Conclusions

(more…)

Comments Off on Using mpm-peruser To Secure A Shared Server

I’ve been working my way through the different solutions available for securing a shared hosting server. So far, I’ve looked at:

The solutions that are left on my todo list are:

Are there any other solutions for securing shared hosting that you know about, and would like me to include? If there are, let me know by leaving a comment below.
Be the first to leave a comment »

The challenge with securing a shared hosting server is how to secure the website from attack both from the outside and from the inside. PHP has built-in features to help, but ultimately it s the wrong place to address the problem. Apache has built-in features too, but the performance cost of these features is prohibitive.

This has created a gap that a number of third-party solutions have attempted to fill. One of the oldest of these is suphp, created by Sebastian Marsching. How well does it work, and how well does it perform?

  • suphp: Running PHP As A Specified User
  • Installing suphp
  • Configuring Apache
  • Some Benchmarks
  • Other Considerations
  • Conclusions

suphp: Running PHP As A Specified User

Like Apache’s own suexec, suphp is a solution that allows PHP to run as the user and group that owns any particular website on a shared hosting server.

suphp consists of two components:

  • mod_suphp, an Apache module that replaces mod_php
  • suphp, a setuid binary that replaces Apache’s suexec

It relies on PHP/CGI having been installed onto the server first.

Installing suphp

suphp is compiled and installed in the same way as any other Apache module. These instructions are for Apache 2.2, but they will work fine for Apache 2.0 as well. (You can run suphp on Apache 1.3 too). (Gentoo Linux and Seed Linux users can skip these instructions; you can install suphp using ‘emerge mod_suphp‘).

Download the suphp source code from the website. Unpack the tarball somewhere, like this:

tar -zxf suphp-0.6.2.tar.gz
cd suphp-0.6.2

Next, run the configure script:

./configure --with-setid-mode=paranoid 
    --with-min-uid=1000 
    --with-min-gid=100 
    --with-apache-user=www 
    --with-logfile=/var/log/apache2/suphp_log 
    --with-apxs=/usr/sbin/apxs 
    --with-apr=/usr/bin/apr_config

You’ll need to adjust some of these settings to suit your local operating system:

  • –with-min-uid sets the lowest user id that PHP is allowed to run as. Check your /etc/passwd file to see just how low this needs to be set.
  • –with-min-gid sets the lowest group id that PHP is allowed to run as. Check your /etc/group file to see just how low this needs to be set.
  • –with-apache-user tells suphp which user Apache will be running as.
  • –with-logfile tells suphp where to write log messages to. I recommend that you configure suphp to write its logfile in the same directory that Apache would normally write its log files.
  • –with-apxs tells suphp where to find the Apache util that’s used to help build Apache modules.
  • –with-apr tells suphp where to find the Apache Portable Runtime (apr) config util.

It’s worth pointing out that there are several different options to choose from for the –with-setid-mode config. Check out the suphp documentation (included in the source tarball) for the details on what each mode does.

Next, run make to compile the software.

Assuming everything compiles just fine, the next step is to install mod_suphp by copying it to your Apache modules directory:

cp  src/apache2/.libs/mod_suphp.so /usr/lib/apache2/modules/

After that, you need to install the suphp binary:

cp src/suphp /usr/sbin/suphp
chmod 4755 /usr/sbin/suphp

mod_suphp is hard-coded to expect the suphp binary to be installed into /usr/sbin. If you put it anywhere else, mod_suphp won’t be able to run PHP for you!

To finish, download the suphp configuration file from Gentoo’s CVS, and install it as /etc/suphp.conf. Then, edit the file, updating all the settings to match the values you passed to the configure script.

Configuring Apache

The first thing you need to do to your Apache config files is to comment out mod_php. mod_suphp is a replacement for mod_php; you cannot run both at the same time.

Then, in your main httpd.conf file, add the following:

LoadModule suphp_module modules/mod_suphp.so
AddType application/x-httpd-php   .php
AddHandler application/x-httpd-php .php

suPHP_Engine On

<Location />
SuPHP_AddHandler x-httpd-php
</Location>

DirectoryIndex index.php index.htm index.html default.htm default.html

This tells Apache to load mod_suphp, to associate it with PHP scripts, and to look for index.php et al when a URL only specifies a folder name instead of a file.

The final step is to go into each of your virtual hosts, and tell mod_suphp which user owns the virtual host. This will the user and group that PHP will run as.

<VirtualHost www.example.com>
SuPHP_UserGroup stuart mybusiness
</VirtualHost>

Now you’re ready to restart Apache and to run some tests to make sure that suphp is up and running.

Some Benchmarks

suphp works the same way as Apache’s suexec. Every time a PHP script is run, suphp has to fork Apache and then execute another copy of the PHP/CGI binary. This approach provides the absolute security benefits that we seek, but is much slower than using mod_php.

To benchmark suphp, I used Apache’s ab benchmark to load a simple phpinfo() page 1,000 times. I ran the benchmark five times, and averaged the results.

  • suphp: average of 164.677 seconds
  • mod_php: average of 6.422 seconds

suphp is some 25 times slower than mod_php. This is a substantial performance hit, but it’s better than suexec, which benchmarked at 36 times slower than mod_php. I admit to being surprised that suphp performs better than suexec; I plan to put all of the alternatives covered here in a “head to head” article soon!

Other Considerations

One neat feature of suphp is that it can support both PHP 4 and PHP 5 running on the same box at the same time. Hopefully, you’ve already made the move to PHP 5 and you don’t need this feature – but it’s there if you do.

The same feature can be used to support both PHP 5 and PHP 6 (when it’s released) at the same time.

Be aware that the last release of suphp was in 2006. There is an active mailing list you can join for community help and support.

Conclusions

suphp is an easy-to-install, easy-to-configure and easy-to-maintain alternative to Apache’s own suexec. If you are running a shared server and the horrific performance penalty doesn’t put you off, then suphp is well worth looking at instead of using suexec.

But the question is – is there anything better out there, something that provides both security and performance? In the next article, I’ll take a look at a third-party Apache mod that attempts to answer that.

This article is part of The Web Platform, an on-going series of blog posts about the environment that you need to create and nurture to run your web-based application in. If you have any topics that you d like to see covered in future articles, please leave them in the comments on this page.

26 comments »

The challenge with securing a shared hosting server is how to secure the website from attack both from the outside and from the inside. PHP has built-in features to help, but ultimately it’s the wrong place to address the problem.

So what can Apache do to help?

It turns out that there are quite a few alternative ways that Apache can help. This article will look at what we can do with stock Apache, and the next few articles will look at what we can do with some interesting third-party Apache modules.

  • suexec: Running CGI Programs As A Specified User
  • Configuring Apache With PHP/CGI
  • Configuring suexec With PHP/CGI
  • Configuring suexec For Shared Servers
  • Some Benchmarks
  • Other Considerations
  • Conclusions

suexec: Running CGI Programs As A Specified User

To secure a shared hosting server, we want to be able to run PHP as the user who owns that particular website. One way to do this with stock Apache is with suexec.

suexec is a standard Apache module which allows you to run a CGI executable as a specified user and group. CGI executables date back to the very early days of the web, back when we all had to use Perl to create dynamic websites. Although PHP is commonly run as an Apache module, it still provides support for CGI.

Check with your Linux vendor to make sure that you have PHP/CGI installed on your box.

Configuring Apache With PHP/CGI

The first step for getting suexec working is to configure Apache to run PHP as a CGI executable, instead of using mod_php. Add the following configuration to your httpd.conf file:

ScriptAlias /php5-cgi /usr/bin/php-cgi
Action php5-cgi /php5-cgi
AddHandler php5-cgi .php
AddDirectoryIndex index.php

… and add the following line to your virtual host:

AddHandler php5-cgi .php

In your httpd.conf file (or in one of the files that httpd.conf includes), there will be a <Directory> entry for the directory on disk where your virtual host is stored. Inside that <Directory> entry, there should be an “Options” line, which might look like this:

Options Indexes FollowSymLinks

Add “ExecCGI” to the end of your Options line.

Make sure to comment out mod_php from Apache. Then, restart Apache, and do some testing to make sure that PHP 5 is working.

For reference, here is the Apache config from my test system:

ScriptAlias /php5-cgi /usr/bin/php-cgi
Action php5-cgi /php5-cgi
AddHandler php5-cgi .php
AddDirectoryIndex index.php index.phtml

<VirtualHost *:80>
        DocumentRoot /var/www/localhost/htdocs
        <Directory /var/www/localhost/htdocs>
                Options Indexes FollowSymLinks ExecCGI
                AllowOverride All
                Order allow,deny
                Allow from all
       </Directory>
        AddHandler php5-cgi .php
</VirtualHost>

Configuring suexec For PHP/CGI

With Apache now running PHP as a CGI executable, we’re ready to get Apache running PHP as the owner of each website.

In your test virtual host, add the following:

SuexecUserGroup stuart users

Replace “stuart” with the user who owns the website, and replace “users” with the group that the user belongs to. This sets the privileges that PHP will run as.

To ensure the security of your server, suexec is very particular about what conditions must be met before it will execute your PHP scripting engine. A full list of conditions can be found in the Apache docs. To make sense of the conditions, you’ll need to know what settings your copy of suexec has been compiled with. Run the command suexec -V to find out your system’s settings. This is the output from my Seed Linux LAMP Server system:

belal vhosts.d # suexec -V
 -D AP_DOC_ROOT="/var/www"
 -D AP_GID_MIN=100
 -D AP_HTTPD_USER="apache"
 -D AP_LOG_EXEC="/var/log/apache2/suexec_log"
 -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
 -D AP_SUEXEC_UMASK=077
 -D AP_UID_MIN=1000
 -D AP_USERDIR_SUFFIX="public_html"

The first condition (and one that isn’t obvious from the Apache manual!) is that the PHP CGI executable must be installed under AP_DOC_ROOT. Chances are that it isn’t installed there at the moment, so go ahead and copy it there.

mkdir /var/www/localhost/cgi-bin
cp /usr/bin/php-cgi /var/www/localhost/cgi-bin

The second condition is that the PHP CGI executable must be owned by the same user and group you listed in the SuexecUserGroup statement earlier. This causes problems for shared hosting; I’ll show you how to fix that later in this article.

chown stuart users /var/www/localhost/cgi-bin/php-cgi

Update your Apache httpd.conf file to use this copy of PHP:

ScriptAlias /php5-cgi /var/www/localhost/cgi-bin/php-cgi

Restart Apache, and test to make sure that PHP 5 is still working. You should also start to see log messages appearing in AP_LOG_EXEC. This is the first place to look if PHP isn’t working (although the log messages can be a little terse and cryptic).

For reference, here is the Apache config from my test system:

ScriptAlias /php5-cgi /var/www/localhost/cgi-bin/php-cgi
Action php5-cgi /php5-cgi
AddHandler php5-cgi .php
AddDirectoryIndex index.php index.phtml

<VirtualHost *:80>
        DocumentRoot /var/www/localhost/htdocs
        <Directory /var/www/localhost/htdocs>
                Options Indexes FollowSymLinks ExecCGI
                AllowOverride All
                Order allow,deny
                Allow from all
        </Directory>
        SuexecUserGroup stuart users
        AddHandler php5-cgi .ph
</VirtualHost>

Configuring suexec For Shared Servers

I mentioned earlier that there was a problem with using suexec + PHP/CGI on shared servers – the very environment where suexec is needed the most 🙁 In one of the steps above, we created a copy of the PHP CGI executable, and changed its ownership on disk to match the ownership of the website.

chown stuart users /var/www/localhost/cgi-bin/php-cgi

What happens when we have two websites, each owned by a different user? Or five, or ten, or hundreds? Apache’s suexec will refuse to re-use this copy of the PHP CGI executable for each of the websites, because it isn’t owned by the right user and group.

Each website needs its own copy of the PHP CGI executable, owned by the user and group that owns the website itself. We don’t want to create hundreds of copies of the actual PHP CGI executable (it’s a large waste of space, and a pain for managing PHP upgrades), so instead we can point each website at its own copy of a simple bash script:

#!/bin/bash

/usr/bin/php-cgi "$@"

This script simply executes our central copy of the PHP CGI executable, passing through whatever parameters Apache has called the bash script with.

To configure Apache to use this script, simply move the ScriptAlias statement from outside the VirtualHost config to inside.

Some Benchmarks

Because Apache is having to execute a new suexec process every page hit (and suexec executes a new PHP CGI process every page hit), it’s going to be slower than running mod_php. But how much slower? To find out, I used Apache’s ab benchmarking program to load a phpinfo() page 1000 times. I ran the benchmark five times and averaged out the results.

  • suexec: average of 127.219 seconds
  • suexec + bash script: average of 134.836 seconds
  • mod_php: average of 3.753 seconds

suexec on its own is some 34 times slower than using mod_php; suexec + the bash script needed for shared hosting environments is even worse, at 36 times slower than using mod_php.

This benchmark doesn’t provide the full picture. Once you take into account the extra memory used by the suexec method, and the extra memory and CPU (and process context switches!) required to transfer output from PHP/CGI to Apache to send back to the website’s user, the final cost of using suexec + PHP/CGI will be substantially higher.

Other Considerations

Performance isn’t the only thing to think about when evaluating suexec + PHP/CGI.

  • suexec + PHP/CGI does solve the security challenge, without requiring your application to support safe_mode.
  • HTTP authentication is only supported by mod_php, not PHP/CGI. If your application relies on this, then suexec + PHP/CGI is not for you.

Conclusions

Apache’s suexec mechanism does secure a shared hosting server from attack from within. However, this is achieved at a heavy performance cost, which inevitably will translate into needing lots of extra servers – which is expensive.

So, if Apache itself doesn’t come with a solution that’s worth a damn, maybe there are third-party solutions out there that can do a better job? The next article in the series will take a look at what others have done to try and plug this gap.

This article is part of The Web Platform, an on-going series of blog posts about the environment that you need to create and nurture to run your web-based application in. If you have any topics that you d like to see covered in future articles, please leave them in the comments on this page.

19 comments »

In my last article, I covered the fundamental security problem that exists when you have multiple websites owned by different people on the same box. The challenge is to secure the box not just from outside attack (something you have to do anyway, and which I’ll cover later in this series), but also to make sure that code running on one website can’t steal confidential data like MySQL passwords from any of the other websites.

This isn’t a problem caused by, or unique to, PHP. It has been a problem with websites ever since the original NCSA httpd was released back in the early nineties with the ability to run Perl scripts via the CGI interface. Over fifteen years later, NCSA httpd has given way to Apache and Perl has given way to mod_php, but the problem is still the same.

(On Windows, the problem is a little different, because Windows handles process permissions differently. I’ll look at that in a later article).

PHP 4 & 5 ship with two features which were designed to tackle this issue – safe_mode, and open_basedir.

Introducing safe_mode

Safe mode is an optional PHP feature aimed at restricting which files any PHP script can access. It works like this:

  • When your PHP script is executed, PHP makes a note of which user owns your PHP script. On a shared hosting server, this will be your user account – the account you log into to FTP files up to the server.
  • Whilst your PHP script is running, if your script wants to access any other files, PHP checks to see who owns those files first. If the file isn’t owned by the same user who owns the running script, PHP refuses to open the file.

Sounds like a great solution to the problem. Your PHP script can open your files – which is what you want – but it cannot open the files of any of the other customers on the box. So how do we switch it on?

Configuring safe_mode

To switch on safe_mode, set “safe_mode=1” in your php.ini file, and restart Apache. Then test your website, and make sure it still works.

If you have PEAR (or a.n.other PHP libraries) installed in a central location on your server and listed in the include_dir setting, you should also add this location to safe_mode_include_dir as well. This tells PHP to skip the owning user test when accessing the PEAR libraries.

There are also other things you can configure, right down to enabling / disabling specific PHP functions and classes. Full details, as always, are in the excellent PHP Manual. I won’t be going into them in any detail in this article.

The Problems With safe_mode

Alas, if it sounds too good to be true, then all too often it is 🙁

Any moderately-complicated PHP application will create files on the server. The obvious example is uploaded images to a blog, but it’s just as likely to be cache files for RSS feeds or to reduce database overhead, or a friendly web-based installer like the one that comes with WordPress. When your script creates these files, the files on disk will be owned by Apache, not by your user account. Remember, Apache doesn’t run with your user account’s privileges; it runs as its own user!

With safe_mode enabled, PHP can’t read any of the files created by Apache. To use safe_mode, your PHP script can never ever write brand new files to disk. (It can write out existing files that you own). You’ll have to store all of your data in the database, which isn’t always convenient or the fastest solution.

It’s also (theoretically) possible to get around safe_mode. safe_mode is a PHP feature, not a security policy enforced by the underlying operating system. A PHP extension (one written in C, not in PHP) could ignore safe_mode and just open any files that it chooses (and that Apache can see). The PHP developers audit the official PHP extensions, to make sure none of them can be abused like this, but when it comes to third-party extensions, you’re on your own.

Sadly, PHP is just the wrong place architecturally to solve this security problem, and as a result safe_mode will not be part of PHP 6. If you currently rely on safe_mode to secure your servers, it’s time to start looking at other ways to secure your shared hosts. I hope you’ll find my next article or two about alternatives both useful and timely 🙂

Restricting Access With open_basedir

The second PHP feature that helps is open_basedir. Although it’s documented as part of the safe_mode section of the PHP Manual, to all intents and purposes it is a separate feature that can be switched on and off without requiring safe_mode.

safe_mode doesn’t care where a file on disk is; all it cares about is who owns the file. open_basedir is the orthogonal feature. It doesn’t care who owns a file, only where the file exists on disk. You tell PHP which directory it is allowed to open files from, and PHP makes sure that all attempts to access files outside that directory will fail.

The idea is to setup each website so that PHP is only allowed to open PHP files installed for that website.

Switching On open_basedir

The open_basedir setting can be edited in php.ini, but to be honest that makes little sense on a shared hosting server. You’re much better off putting this configuration into the httpd.conf entry for each individual website:

<VirtualHost*:80>
	ServerName www.example.com
	DocumentRoot /home/customer1/public_html/www.example.com/

	php_admin_flag open_basedir /home/customer1/public_html/www.example.com/

	...
</VirtualHost>

There’s one gotcha with open_basedir that you need to pay close attention to. Despite the name, PHP doesn’t expect open_basedir to be the name of a directory; it treats it as a prefix. The check PHP uses is something like this:

[code lang="php"]
 function check_open_basedir($file)
{
    // resolve any symlink
    $file = realpath($file);
    $open_basedir = ini_get("open_basedir");

    // check to ensure file is inside open_basedir
    if (substr($file, 0, strlen($open_basedir)) === $open_basedir)
    {
        return false;
    }
    return true;
}
[/code]

To make sure that PHP treats open_basedir as a real directory, always put a slash at the end of the value for open_basedir.

open_basedir and PHP 6

For the moment at least, open_basedir will continue to be supported in PHP 6. There’s a slight change to how it is configured (with PHP 5, you can set open_basedir in .htaccess files; with PHP 6 you have to put it in httpd.conf or php.ini) but the actual functionality stays the same.

open_basedir is vulnerable to the same theoretical circumvention as safe_mode, so be careful when installing third party PHP extensions onto a shared server.

Where Do We Go From Here?

I’ve looked at two solutions implemented by PHP 4 & 5 to help make a shared hosting server more secure.

  • safe_mode stops you opening up files owned by other customers, but it has the side effect that your web application cannot create files of its own. This feature has been removed from PHP 6.
  • open_basedir stops you opening up files outside the specified directory on disk. This feature is still in PHP 6, but can now only be configured from phi.ini and Apache’s httpd.conf.
  • Both features rely on third party extensions supporting them. It’s perfectly possible for a third party extension to choose to bypass both features, thus re-creating the security hole we’re trying to close.

In terms of our challenge, both features come close to solving it, but neither is 100% guaranteed to do so. Data security isn’t just a legal obligation, it’s also a moral one, and you can’t meet your moral obligation using these features alone.

Fundamentally, PHP is the wrong place to solve this problem. PHP is trying to overcome a security weakness that it has inherited from Apache (and all other web servers; this isn’t a problem specific to Apache), and in turn they are constrained by the security model implemented by UNIX systems themselves.

Moving up the stack, if the problem can’t be fixed in PHP, maybe Apache can offer some help? I’ll take a look at that in the next article.

This article is part of The Web Platform, an on-going series of blog posts about the environment that you need to create and nurture to run your web-based application in. If you have any topics that you d like to see covered in future articles, please leave them in the comments on this page.

8 comments »

(Many thanks to everyone for their feedback on my first post in this series.)

Most of us started out hosting our code on shared hosting, whether it was on a box provided by an ISP, something we rented ourselves, or something we built so that we had somewhere to host the websites we built for our customers. Love it or loathe it, shared hosting has some unique security challenges, and understanding those challenges is a good way to learn the fundamentals of how your web server actually works.

This article is looking squarely at Linux systems running Apache, which is by far the most common shared hosting platform, but the principles involved apply to Lighttpd or any other Apache alternative running on Linux.

What Is A Shared Server?

For many web developers, their first experience of hosting code on the Internet comes on a shared server. Shared servers offer cheap hosting, but that’s because there are many different people sharing the same server and therefore sharing the costs.

A shared server is a single server that hosts more than one website. Each website may be owned by a different company, group, or person. Typically, each customer on the box has a user account which they log into to upload new files for the website. Each file that the customer uploads is owned by the customer’s user account:

ls -lh ~thecube/public_html
drw-r--r--  thecube  public   4K     images
-rw-r--r--  thecube  public   1.2K   index.php

Apache Needs Access To Your Files

On your classic shared hosting server, there’s one copy of the Apache web server running, and PHP is installed either as mod_php, or as a CGI executable. That one copy of Apache handles all the incoming HTTP requests for all the websites that are sharing the server. When Apache is running in this way, it runs as a specific user – normally www, or apache (or nobody on badly-configured systems).

In order to serve up your website, Apache needs to be able to read your HTML files, CSS files, images, PHP scripts and so on. Some web applications (blogs, content management systems and so on) also need write access to your website’s directories.

Read and write access is normally granted by setting the group permissions on a file or directory. Each customer’s user account, and Apache, are members of the same group. By default, the FTP daemon will be set up to ensure that the group has read access to all of the files that are uploaded, so that Apache can serve the website.

Apache Has Access To Everyone’s Files

There is one copy of Apache, and it runs as a single user – no matter which website is being served. This single user has read access to every single website on the shared hosting server, and it probably has write access to most (if not all) of the websites too.

An attacker from the outside only needs to break into one website on the server, and that will give him access to every other website hosted on the same box!

But here is the rub. The attacker doesn’t need to break into the box. He can just as easily become a customer, get a legitimate account on the box, and then just upload PHP scripts to access the other websites hosted on the box. Provided he’s careful and doesn’t change anything, he can steal whatever data he wants, and no-one will even notice.

Why does that work? It’s possible because the PHP code is executed by Apache – and Apache has access to all of the files from all the websites on the box. That includes all the PHP scripts that contain the usernames and passwords for all the MySQL databases.

This is the worse-case scenario – but it’s also the default scenario. Slap Apache + mod_php on a box, start putting websites on it, and these security problems will exist, unless you (as the server administrator) take additional steps to prevent them.

The Challenge

The challenge with securing a shared hosting server is this: how do we put as many websites as possible onto the one machine without each customer being able to steal sensitive information, or interfere with, any of the other customers that they are sharing with?

There are a few ways to tackle this, which I’ll cover in the next article or two.

This article is part of The Web Platform, an on-going series of blog posts about the environment that you need to create and nurture to run your web-based application in. If you have any topics that you d like to see covered in future articles, please leave them in the comments on this page.

15 comments »

I’ve just seen Wez’s post about Microsoft’s new Community Technology Preview for their SQL Server 2005 extension for PHP.

Why Is It Important For Microsoft To Support PHP On Windows?

I can’t speak for the rest of the world, but Windows is an important platform here in the UK. About half the customers I work with run their websites on Windows Server. Every customer who prefers Windows Server over Linux automatically wants to use the full Microsoft stack, and that means IIS and Microsoft SQL Server.

Starting with Windows Server 2003 Service Pack 1, IIS became a viable platform for running production PHP sites on. SP1 has a fix for a nasty problem in the Windows kernel which stopped PHP working well as a CGI program under IIS. It’s true, IIS has serious limitations compared to Apache which cost time and money to overcome – chiefly no out-the-box equivalent to mod_rewrite – but it performs well and is simple enough for many folks to admin more easily than Apache (although the new IIS7 admin interface might be a step backwards there). With the new FastCGI support recently released, it’s now possible to further optimise an IIS setup too.

SQL Server is a true heavyweight database server. The most commonly deployed version that I come across is SQL Server 2000, although SQL Server 2005 is starting to appear in various enterprises. It is fast, scalable (in the old-fashioned way unfortunately, which is expensive), and when it comes to old-fashioned replication (as opposed to scale-out) I think that it leaves MySQL for dead. Although expensive to buy outright, if you buy your hosting boxes from places like Rackspace, the cost tends to be much more reasonable.

PHP comes with an MSSQL extension that’s based on the old TDS data protocol, which I believe is a legacy protocol from the old Sybase days (SQL Server is based on Sybase). TDS is also supported on Linux through the FreeTDS project, which allows PHP running on Linux boxes to connect to Microsoft SQL Server running on Windows.

What’s Wrong With The Existing MSSQL Extension For PHP?

… or, why do we need an improved SQL Server extension for PHP? 🙂

The existing MSSQL extension works well, but has a few practical limitations that have to be worked around.

  • Limited to varchar(255) support. SQL Server 2000 and later support varchar columns longer than 255 bytes in size, but unfortunately the old TDS-based MSSQL extension can only support up to varchar(255).
  • No support for unicode columns like nvarchar. The size of a varchar column is specified in bytes, not characters. If you’re working with UTF8 or UTF16 encoded data, one non-ASCII character takes up multiple bytes of space. This cuts down on the amount of characters you can store in a varchar field, and it makes things like HTML form validation – er – interesting. nvarchar, by contrast, is advertised as a variable-size datatype for storing multi-byte characters. nvarchar(255) holds 255 characters, not 255 bytes.
  • No PDO drivers. Although there’s some debate about the performance merits of PDO, PDO’s prepared statement support is a real boon when it comes to preventing SQL injection attacks.
  • Poor error reporting. The MSSQL extension doesn’t provide an equivalent to mysql_error() et al, which is a bit of a pain.

At the moment, I’ve no idea whether Microsoft’s extension addresses any of these issues. There’s no documentation online, just a .exe file that isn’t going to run under OS X 🙂 I’ll have a look at it when I get to my Windows PC at work, and see what it can – and can’t – do.

15 comments »

Many thanks to everyone who commented on my recent article and said they’d be interested in a series of posts about more server-oriented PHP topics. There were quite a few requests for a “ten point”-type article introducing the subject, so that seems to be a good place to kick things off.

Building Blocks

There are six classic ways to group and organise the servers that your web-based application runs on.

Shared Hosting

Shared hosting, like the name implies, is where you cram many different websites (normally owned by many different people) onto the same physical box. The upside is that they’re very cheap (because you’re not paying for an entire server, only a slice of one), but the downsides are that you can quickly outgrow a shared hosting server, and shared hosting servers are difficult to make really secure. Use them when you have to, but do your best to trade up to something that you can have all to yourself whenever you can.

Dedicated Server

A dedicated server is a box all to yourself. So long as you can keep the bad guys outside, you have more peace of mind when it comes to security. They cost a bit more than shared hosting, but there are many affordable solutions both in Europe and the US.

A PHP application normally needs no changes at all to move from shared hosting to a dedicated server.

What might need to change is the way you look after the installed application. Unless you go for a managed server, or you are using a server looked after by your customer, it will become your responsibility to look after the operating system installed on the server. You will be responsible for ensuring the server is patched with the latest security fixes. This can eat up quite a bit of time every week, so make sure your customer is paying for this time one way or another!

Two-Tier Architecture

When you outgrow a single server, the next step is to consider moving to a separate database server and web server, also known as a two-tier architecture. This is a popular choice because it’s quick and very painless (just order an additional box, and then move your database onto it), and it buys you the time you need to prepare for scaling up to the next architecture. Even on commodity Intel/AMD servers (I’m talking Xeons and Opterons here, not desktop CPUs!), a two-tier architecture is often enough to handle a public website for a medium-to-large organisation.

The only change a PHP application should need is to update the hostname passed to mysql_connect() et al. Consider moving any background batch jobs you have onto the database server, to further reduce the load on the web server.

It’s a good idea at this point to split your PHP application into separate publishing and admin components, so that you can move the admin website onto the database server. Splitting these up allows the admin site to function well when the main website is being hit hard, but to make it work you’ve got to start thinking about how to share data between the publisher and admin components – the data that your website publishes, and your sessions too.

Once you’ve outgrown the two tier architecture, you can add more capacity on the publishing side by moving to a web farm, and you can make your database server more resilient by upgrading to a cluster. If you don’t mind the extra complexity and the reworking involved, you can also scale further by moving to an n-tier architecture.

(more…)

Be the first to leave a comment »

This Month

September 2017
M T W T F S S
« Jul    
 123
45678910
11121314151617
18192021222324
252627282930