Drupal and Apache Web Site Security Checklist, part 1

Topics: Apache, Drupal
Technologies: Apache 2+, Drupal 5+, PHP 5+

Default configurations of Apache, PHP, and Drupal often serve files they shouldn't. In this first article in a series, I review configuration changes to tighten security by limiting access to files and directories for a Drupal web site served by Apache.

This is part 1 in a series of web site security checklists. Part 2 continues by reviewing Drupal settings to control access to content in Drupal's database, while Part 3 reviews Apache and Drupal settings that leak information about your site to would-be hackers.

Introduction

Automated attack tools are widely available. Running them takes little more than a mouse click. These tools are programmed to poke at every nook and crannie of a web site, so it's important that you get there first and configure even the obscure security settings.

In a 2009 report (PDF), White Hat Security reports that 82% of the web sites they tested had significant vulnerabilities. Using the Web Application Security Consortium's (WASC's) threat classification scheme, they estimate the top ten vulnerabilities by likelihood are:

Top Ten Vulnerabilities
by Likelihood
Vulnerability %
Cross-site scripting 65%
Information leakage 47%
Content spoofing 30%
Insufficient authorization 18%
SQL injection 17%
Predictable resource location 14%
Session fixation 11%
Cross-site request forgery 11%
Insufficient authentication 10%
HTTP response splitting 9%

Cross-site scripting, SQL injection, and most of the others are issues that must be handled by the developers of the software that runs your site. The Apache and Drupal organizations both have security teams that do so.

Information leakage problems, however, may be due to web site configuration errors. Directories are left open for anyone to browse and files are left available to the public instead of locked behind security checks.

In this article I review Apache and Drupal configuration changes to better control what files and directories are served and to whom they are served. I assume that you've already done the essentials. You've installed the latest versions and security patches for the OS, Apache, MySQL, PHP, and Drupal. You've picked good login passwords. You've disabled Apache and Drupal modules you don't use. You've configured your server and router firewalls to block everything but port 80, and you've run a port scanner to be sure. Finally, you've run security software like NESSUS, NMAP, or Acunetix to poke at your server and recommend configuration changes.

Please note:

  • I do not discuss downloading or installing software or setting up directories, permissions, and chroot. How you do these is important, but beyond the scope of this article (see the Further Reading section for articles that cover this).
  • I do not discuss basic Apache, PHP, MySQL, and Drupal configuration. I assume that you've already got things working and now you're ready to tighten security.
  • This list is not exhaustive. No list could be.
  • All of these settings have been tested on a Drupal site, but test them yourself before using them on a production site. Every site is different.

Restrict the files you list

The only files that users should see at your site are those that you explicitly choose to tell them about. Turn off automatic file listing features that can accidentally reveal internal files and content that isn't ready to publish, or never should be published.

WASC classifies file listing problems as a Directory Indexing threat, and one of several types of Information Disclosure issues. WASC's threat classification group has further information on Directory Indexing and other threats.

Disable Apache's directory listings

Apache's mod_autoindex module creates directory listings for any directory without an "index.html" or equivalent. This is sometimes enabled by default in "easy" configurations for beginning users. When enabled, though, it allows hackers to see parts of your site they shouldn't.

Disable the module in Apache by commenting out its "LoadModule" line in "httpd.conf", such as:

# Commented-out directory indexing module
#LoadModule autoindex_module libexec/apache2/mod_autoindex.so

However, if the module has been compiled into Apache, there may be no "LoadModule" directive and the module is always loaded. Check for this by listing available modules with the "apachectl" command:

apachectl -l

If the module is listed as compiled in, either rebuild Apache without it or ensure that it is never used. Look through your "httpd.conf" file, and any files it includes, and remove the "Indexes" option on "Options" directives.

Also look through all ".htaccess" files to be sure indexes are never enabled again. On a Mac OS X or Linux server you can use the "find" command to find all ".htaccess" files web site directories:

find WEBSITEDIRECTORY -name ".htaccess" -print

If you do not have access to "httpd.conf" for your site and indexing is already on, disable it with "-Indexes" on an "Options" directive in your site's ".htaccess" file. Drupal's default ".htaccess" file does this for you.

Options -Indexes  # Disable directory indexes

Restrict Drupal's file upload lists

By default, Drupal shows a list of a node's uploaded files at the bottom of the node's page. Unless you are certain that every uploaded file is suitable for publication, limit or block these automatic file lists:

  • Block uploaded file lists for everyone on the Administer > Site configuration > File uploads page. Set "List files by default" to "No".
  • Limit access to uploaded file lists by user role on the Administer > User management > Access control page. Check the "view uploaded files" permission only for user roles that should see the file list. Uncheck it for the "anonymous users" role.

In both cases, uploaded files are still accessible if you know the file path. To intentionally present users with these files, either author links to them within node content or modify the theme to add a file list for specific nodes, content types, and file types.

Beware Drupal file manager-style modules

As of this writing there are 22 pages of File Management modules listed at Drupal.org. It is not practical for me to list security issues with all of them. Check carefully (and probably avoid) any module that can list files on your server.

Restrict the files PHP scripts can access

By default, PHP scripts like Drupal can access any file anywhere on your server, including files outside of the directories you serve. To limit the damage that a malicious or hacked script can do, limit file system access from PHP.

WASC classifies unrestricted file access as one aspect of Path Traversal threats. Such threats trick server software into accessing files they shouldn't.

Limit PHP file access to specific directories

PHP's "open_basedir" directive limits PHP scripts to only access files in a list of allowed directories. In "httpd.conf", set this to your site's top-level directory, plus the temporary file directory you've configured for Drupal on the Administer > Site configuration > File system page. End both directory paths with "/" and separate them with a ":" (on Mac OS X and Linux) or a ";" (on Windows):

php_admin_value open_basedir "WEBSITEDIRECTORY/:TMPDIRECTORY/"

Use drupal_get_path( ) in PHP code that builds file paths

With "open_basedir" in use, all PHP file access should use unambiguous full paths. Those paths have to be for allowed directories. Core Drupal, principal modules, and the main themes all work fine with "open_basedir" set.

To avoid embedding full paths in your own PHP code, use Drupal's "drupal_get_path" function to get the path to a theme or module. For instance, if your theme uses "include" or "require" calls like this:

include 'my_utilities.php';

convert them to this:

include drupal_get_path( 'theme', 'YOURTHEME' ) . '/my_utilities.php';

Restrict the types of files you serve

Drupal distributions include a lot of internal files that don't need to be served. Most of these are innocuous. Others can reveal information about your site to hackers. It isn't practical to restrict access to these files one-by-one. Instead, restrict access to whole groups of files based upon their file type.

WASC classifies problems that reveal information they shouldn't as Information Leakage threats. White Hat Security found that information leakage problems were the 3rd hardest to fix — perhaps due to the difficulty of finding the holes the information is leaking through.

Block Apache from serving hidden files

By default, every file in your site's directories can be served. Some clearly shouldn't be, such as Apache's ".htaccess" and ".htpassword" files. In fact, no Linux or Mac OS X file with a name starting with "." should be served. Add the following lines to your "httpd.conf" or ".htaccess" file to block serving them:

# Block any file that starts with "."
<FilesMatch "^\..*$">
     Order allow,deny
</FilesMatch>

Restrict Apache to only serve files with safe file types

Served directories can become cluttered with files that shouldn't be served, such as temp files, backups, shell scripts, text file notes, and more. Drupal's modules also often include ".info", ".install", ".inc", and ".module" files that shouldn't be served directly.

To avoid serving files that shouldn't be, block everything by default, then unblock files with known safe extensions, such as:

Basics Images Multimedia Archives Documents
.css .gif .f4a .gz .doc
.htm .ico .f4b .rar .docx
.html .jpeg .f4p .sit .odf
.js .jpg .f4v .tar .ppt
.pdf .png .flac .zip .pptx
.txt   .flv   .xls
.xml   .mov   .xlsx
.xsl   .mp3    
    .qt    
    .swf    

Allow each of these only if your site uses them. Remember to allow ".xml" files if you enable RSS feeds, and allow ".xml" and ".xsl" files if your site uses a search engine site map, like that created by Drupal's XML sitemap module.

Don't give general permission to serve ".php" files (or Perl, Ruby, etc.). Instead, block access to all PHP files by default, then allow access file by file for specific PHP scripts (see the next section for how).

Add the following lines or similar to your "httpd.conf" or ".htaccess" file:

# Block all files with "." in their names
<FilesMatch "^.*\..*$">
     Order allow,deny
</FilesMatch>
 
# Allow "." files with safe content types
<FilesMatch "^.*\.(css|html?|txt|js|xml|xsl|gif|ico|jpe?g|png)$">
     Order deny,allow
</FilesMatch>

Restrict Drupal file uploads to only accept files with safe file types

As I noted earlier, Drupal's core upload module allows files to be uploaded and attached to nodes. If left unrestricted, malicious users can upload viruses and inappropriate or illegal content. Restrict uploaded files to file types in the same safe content list you allow in Apache (see above).

Drupal's list of allowed types is on the Administer > Site configuration > File uploads page. Set the "Default permitted file extensions field", and be sure to set general settings and those for each of your site's user roles.

Restrict the context in which you serve files

Apache serves any file that passes all allow/deny tests. However, some files, such as images, CSS, and JavaScript, are only relevant when used by one of your own web pages. There are two problems:

  • Bandwidth thieves can take advantage of your site by referencing your images or scripts from their pages. They get fast downloads and low bandwidth costs because the images come from your site, not theirs.
  • Hackers can probe your site and look for specific files to determine what software you're running. For example, sites with "http://YOURSITE.com/misc/druplicon.png" run Drupal.

Both of these problems can be reduced by using Apache's mod_rewrite module to reject requests for images, CSS, and Javascript unless the request has a "referer" URL for a page at your site. While referer URLs can be faked, it takes more effort than some thieves are willing to do.

# Return 404's for any access to CSS, JS, and images
# unless they come from your site's pages or a trusted host.
RewriteCond %{REMOTE_ADDR}      !^192.168.*$
RewriteCond %{REMOTE_ADDR} !^127.0.0.1$
RewriteCond %{HTTP_REFERER} !^http://(www\.)?YOURSITE.com/.*$ [NC]
RewriteRule .(css|js|ico|gif|jpe?g|png)$ - [R=404]

Apache's URL Rewriting Guide, in the Access Restriction section, gives similar rewrite rules. Minor differences include: (1) my rules allow direct access from trusted hosts, (2) my rules block access if the referer is empty (which can be the case for site probing scripts), (3) my rules also block CSS and Javascript access without a valid referer, and (4) my rules return a 404 "Not Found" error instead of a 403 "Forbidden" error. This hides the fact that the file exists at all.

Restrict the PHP scripts you execute

While there are many PHP files in Drupal, the following are the only ones ever executed directly via a URL. All other PHP files are loaded by these scripts or by the scripts that they call.

  • "cron.php" is invoked regularly to do search indexing and other scheduled jobs.
  • "index.php" serves almost everything.
  • "install.php" is used during site installation to set up database tables.
  • "update.php" is used after adding or updating a module.
  • "xmlrpc.php" is used for AJAX callbacks to Drupal.

At a Drupal site, there is normally no need for Apache to run any PHP script other than one of these. So, block execution of all PHP scripts generically (see the previous section on file type blocking), then explicitly allow only these PHP scripts under appropriate circumstances.

If your site uses additional special-purpose PHP scripts, you'll need to allow them here too. It is also possible that some add-on Drupal modules may have additional scripts that need to be allowed.

• Because Drupal is a standard product, the names and locations of its PHP scripts are well-known and easily checked by hacker scripts. WASC classifies problems involving well-known files as Predictable Resource Location threats. White Hat Security, in their Spring 2009 report, estimated that 14% of web sites are likely to have problems of this type.

Each of the configurations below use a "<DirectoryMatch>" directive like this:

<DirectoryMatch "WEBSITEDIRECTORYROOT/(?!(.+/)+)">

This matches your site's top-level directory (fill in WEBSITEDIRECTORYROOT) and only that directory, without any subdirectories. This insures that you enable access only to specific files in that top-level directory and nowhere else.

Allow access to Drupal's "index.php" and "xmlrpc.php" for anyone

Allow full access to "index.php" so that Drupal can create and deliver page content to any user. Also allow full access to "xmlrpc.php" so that AJAX features in Drupal will work. Add these lines to your "httpd.conf" file:

# Allow from anywhere
<DirectoryMatch "WEBSITEDIRECTORYROOT/(?!(.+/)+)">
    <FilesMatch "^(index|xmlrpc).php$">
        Order deny,allow
    </FilesMatch>
</DirectoryMatch>

Allow access to Drupal's "cron.php" from trusted hosts only

For Drupal to work properly, you need to set up a cron job (Linux) or LaunchDaemon (Mac OS X) to run "cron.php" regularly. Typically that job is run by the same host that runs Apache. So allow access to "cron.php" for that host only. Add these lines to your "httpd.conf" file:

# Allow access to cron from server and local network only
<DirectoryMatch "WEBSITEDIRECTORYROOT/(?!(.+/)+)">
    <FilesMatch "^cron.php$">
        Order allow,deny
        Allow from 127.0.0.1   # Localhost
        Allow from 192.168.    # Local network (use your local IP range)
   </FilesMatch>
</DirectoryMatch>

Allow access to Drupal's "install.php" and "update.php" from trusted hosts only

The "install.php" and "update.php" scripts should be runnable from whatever hosts you use for administration. This may be only the server itself or hosts within a local network. Both of these scripts can be dangerous for others to run, and "update.php" even tells hackers exactly what version of Drupal you're and provides them a list of installed modules and their versions. Add these lines to your "httpd.conf" file:

# Allow access to install and update from server and local network only
<DirectoryMatch "WEBSITEDIRECTORYROOT/(?!(.+/)+)">
    <FilesMatch "^(install|update).php$">
        Order allow,deny
        Allow from 127.0.0.1   # Localhost
        Allow from 192.168.    # Local network (use your local IP range)
   </FilesMatch>
</DirectoryMatch>

Since "install.php" isn't needed after Drupal is installed, you can move it to an unserved directory and simplify the above configuration.

Redirect access to unallowed Drupal files to 404 "Not Found" errors

The above configuration settings ensure that unallowed PHP scripts will not be executed. Attempts to access them return a 403 "Forbidden" error. However, a 403 error still tells hackers that the file exists. Use Apache's mod_rewrite module to return a 404 "Not found" error instead. Keep the above configuration too as a fail safe.

# Return 404's for all Drupal .php, .inc, .module, and .info files
# if served to hosts other than the local network hosts, but allow
# /index.php and /xmlrpc.php.
RewriteCond %{REMOTE_ADDR}      !^192.168.*$
RewriteCond %{REMOTE_ADDR}      !^127.0.0.1$
RewriteCond %{REQUEST_URI}      ^.*\.(php|inc|module|info)$
RewriteCond %{REQUEST_URI}      !^/(index|xmlrpc).php$
RewriteRule .*  - [R=404]

Further reading

Related articles at NadeauSoftware.com

Web articles and specifications

Books

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options

Nadeau software consulting
Nadeau software consulting