Anyone Using Textmate To Work With PHP?

Posted by Stuart Herbert on November 30th, 2007 in Toolbox.

My editor of choice for PHP for the last year or two has been phpEclipse. It’s the best compromise so far between IDE-like features (especially being able to search the entire code base, being able to have multiple projects open at once, and the absolute best class/function inspector in any PHP editor I’ve used so far – I can’t live without these features) and a text editor with acceptable performance (which is where Zend Studio has always lost out; can’t abide an editor that can’t keep up with my typing), syntax highlighting and code layout.

Unfortunately, phpEclipse doesn’t get released all that often (the last official release was 18 months ago now, although there are nightly CVS builds for folks who can afford to risk a broken PHP dev environment, and unfortunately I can’t afford that in my day job). That’s a long time to go without bug fixes and useful new features! It also suffers from that annoying Eclipse-ism of being unable to do anything for 10 minutes or so when you first open a project, whilst the workspace is rebuilt.

So I’m currently auditioning Textmate to see whether it can replace phpEclipse as my environment of choice. First impressions are pretty favourable (it supports projects, its fast, and the syntax highlighting is close enough) but it seems to lack a few useful features that phpEclipse has (like a class inspector – grrr, and being able to use phpdoc to provide context-sensitive help) and the performance seems to suck something awful when working on remote filesystems over 100mbit ethernet.

I was wondering if anyone else who reads Planet PHP has switched to Textmate, and if you’ve got any tips you can share on how to make Textmate a great PHP editing environment. If you do, please leave a comment below.

25 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:

 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;
}

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 »