This week I have been thinking about some sanity checking for web scripting languages. A malicious person who gains some level of access on a web-server may not be able to do much due to heavy firewalling. However as long as port 80 is open and the machine has scripting such as PHP turned on, the malicious person may be able to hide a PHP-based backdoor in the document root.

S/he may not even have to look very hard for a place to put them: many of the popular web-script applications such as wikis and photo tools such as gallery have a world-writeable and/or apache-owned directory (for data such as thumbnails and photos) as a matter of course.

So it occured to me, why not sanity check the permissions of a script before allowing it to be executed? The following might be a good start. Do not execute the script if:

  • the script can be written to by the apache user
  • the containing directory can be written to by the apache user

[In practise you would probably need to look right down the filesystem tree for directories that match the criteria]

My first attempts involved using .htaccess files. Unfortunately, this approach suffers as the apache user could either write over a .htaccess file if they have write access; delete it if they have write access to the parent directory; or counteract it if they have write access to a sub-directory.

I'm not entirely sure if this kind of logic belongs in apache or the scripting language interpreter. There's a precedent for apache: the xbithack, plus a solution here might work across a range of scripting languages. However, for something like PHP, you might want to check each included script file too, which implies the checks taking place inside the interpreter.

Anyway I knocked together a partial solution using mod_rewrite, thanks to Rich Bowen and Rici Lake from #apache for help. The rewrite rules look like this:

# define map to check permissions
ReWriteMap check-perms prg:/var/www/checkperms.pl

# file or parent dir writeable by apache? forbid php
RewriteCond $1 \.php$
RewriteCond ${check-perms:%{DOCUMENT_ROOT}%{REQUEST_FILENAME}} false
RewriteRule (.*) - [F]

Immediate problem: a malicious person could easily request a file which was a symlink to a PHP script or write a .htaccess file associating another extention with PHP and get around the first RewriteCond. But this is a proof-of-concept. The checkperms.pl we refer to is this:

#!/usr/bin/perl -w
use strict;
use File::stat;
use File::Basename;

$| = 1; # output buffering off!

sub is_safe {
    my $path = shift;
    my $info = stat($path);

    return 1 unless $info;
    # owner apache (i.e. me) == bad
    if ($info->uid == $<)  { return 0; }
    if ($info->mode & 022) { return 0; }

    return 1;
}

sub check {
    my $path = shift;
    return 1 unless -f $path;

    return is_safe($path) &&
           is_safe(dirname($path));
}
    
while(<STDIN>) {
	chomp;
	if(check($_)) { print "true\n";  }
	else          { print "false\n"; }
}