Hardening Apache
PHP
PHP [HREF4] has configuration settings that affect the security of PHP-powered websites and the web server. There are global settings that should be the same on every website and set in php.ini. Examples are:
- register_globals = Off
- magic_quotes_gpc = Off
These two used to be on by default
There are site-specific settings that need to be explicitly set for each website:
-
Restrict file access of scripts to certain directories:
This is to prevent PHP scripts from accessing files that they aren't meant to, like files under another website's document root hosted on the same server. -
Hide errors on production sites:
By default, PHP prints all errors to the browser. This can leak sensitive system information. While it is convenient to have this on for debugging during development, it is strongly recommended that it is turned off for production sites. -
Log errors:
Although production sites should not display errors to the user, we should still log them for debugging. - Limit upload file size
The above can be implemented with the following Apache rules:
<Directory "/path/to/document/root">
# 1
php_admin_value open_basedir \
"/path/to/document/root:/tmp:/usr/share/php:."
# 2
php_admin_value display_errors 0
# 3
php_admin_value log_errors 1
php_admin_value error_log "/path/to/web/log/php-error.log"
# 4
php_admin_value post_max_size 10M
php_admin_value upload_max_filesize 10M
</Directory>Mod_rewrite - the Swiss army knife of the Apache family
Apache comes with a very powerful module named mod_rewrite [HREF5], which does URL manipulation on the fly. It is primarily used to implement user-friendly URLs for search engine optimisations. However, it is often overlooked that mod_write is a very handy tool for protecting web scripts.
Validating inputs and hiding variables & scripts
Here is a typical mod_rewrite rule:
RewriteRule ^/products/([-_a-z0-9]+)/([0-9]+)$ \
/prod_info.php?pcat=$1&pid=$2 [L]
What it primarily does is to let the developer use clean URLs in place of "ugly" ones. The above translates, for example, this nice URL:
http://example.com/products/specials-06/343
which is what the visitor sees and clicks on, to this:
http://example.com/prod_info.php?pcat=specials-06&pid=343
Apart from that, it also does three extra things:
- Hides the script name prod_info.php
- Hides the two variables: $_GET['pcat'] and $_GET['pid']
- Validates the inputs (to a certain extent) of the above variables using regular expression, before the script is called.
However, we need to beware that the above mod_rewrite rule does not prevent direct access to that script. In other words, if someone knows the script name and the variable names (e.g this script comes with an open source web application) then he/she can still load the "ugly" URL and change the inputs to whatever he/she likes without any mod_rewrite check being applied.
To truly hide the scripts from such direct access, we need to use mod_rewrite to redirect all URIs to one script that analyzes the PATH_INFO, determines the scripts that should be used to process the active URI and passes the inputs to those scripts. The following is a mod_rewrite rule that is a part of implementing nice URLs and denying direct script access in the open source content management system ezPublish [HREF6]:
RewriteRule !(^/design|^/var/.*/storage|^/var/storage|^/var/.*/cache| ^/var/cache|^/extension/.*/design|^/kernel/setup/packages|^/packages| ^/share/icons).*\.(gif|css|htc|jpg|png|jar|js|ico|pdf|swf)$ /index.php
The above rule redirects every request that isn't for a file of certain types (pictures, movies, pdf, stylesheets, javascripts etc.) to a request processing script (index.php). This script will then pass inputs to all other PHP scripts but direct access to them is denied by the rewrite rule with a 404 (file not found) error.
Force SSL/https
There are web scripts that require to be loaded with SSL on for security reasons, e.g login, registration or payment processing scripts. It is obviously inadequate to just link to them with the https prefix, and doing checks for the existence of https in the requested URL inside the script makes your code less elegant. It is preferable to enforce this in Apache, using mod_rewrite. The below mod_rewrite rule should be put under the port 80 VirtualHost configuration of the website:
RewriteCond %{SERVER_PORT} !^443$
RewriteRule ^/login.php|admin/login.php|register.php|payment.php)$ \
https://%{SERVER_NAME}/$1
The above rule redirects every request for the PHP scripts we want to be loaded with SSL encryption, that originates from a non-SSL URL (server port is not 443), to the HTTPS version.
If you don't have a wildcard SSL certificate, it's best to make sure that your SSL certified domain is always loaded. Assuming that www.example.com is a CNAME (alias) of example.com, and the SSL certificate is for www.example.com, the following RewriteRule should be in the port 443 VirtualHost configuration for this website:
RewriteCond %{HTTP_HOST} ^example.com$
RewriteRule ^/(.*) https://www.example.com/$1 [R=301,L]Block hot-linking
Hot-linking is the act of displaying a picture or multimedia file hosted under domain A on domain B, that has no affiliation with domain A. It is considered one of the most unethical acts on the Web, as it wastes a lot of bandwidth of the site hosting the hot-linked file yet gives it no visitor. Additionally, it could be exploited to bring down a website or web server as a form of a DOS attack.
There are various ways to deal with hot-linking, and they are all based on analyzing the HTTP_REFERER variable sent to the server from the request. What they do is: if the request doesn't originate from the host website, drops it. This is mod_rewrite's take on it - rewriting "bad" requests to an image:
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://example.com/.*$ [OR,NC]
RewriteCond %{HTTP_REFERER} !^http://www.example.com/.*$
[OR,NC]
RewriteRule .*\.(gif|jpg|png|swf|mov|wmv|wma|mp3|ogg)$ \
http://mysite.com/images/bloodyleechers.jpg [NC,L,R]
Warning: for the above to work, the user agent accessing the file needs to send a HTTP_REFERER. While most user agents adhere to this standard and ethical practice, some do not, and among those bad user agents is Windows Media Player, something we cannot block due to its popularity. This means that if you host streamable multimedia files, someone could hot-link your files on his site and visitors using Windows Media Player (all known versions at the time this article was written) will bypass any hot-link protection.
Change default paths and URIs
Many web applications come with an installer or installation guide that ask the user to put them in a default location. While it can be a good thing in the name of standardization, it also means that the application is exposed to scripted attacks on known vulnerabilities.
On servers that I manage, one of the most scanned locations by script kiddies is "/cgi-bin/awstats.pl". This is the default location for AWStats, a popular web statistics analyzer [HREF7]. Dated AWStats versions have some nasty security issues that could get the server compromised, and at the very least, expose sensitive information (web statistics) to unauthorized users. The best way to protect it is to use mod_access (see section #) but we will change the default location for AWStats to hide from those annoying bots, and give us some nice, easy to remember URLs.
Firstly, AWStats should be installed in a non-default location, then we apply the following rules (combination of mod_alias [HREF8] and mod_rewrite):
Alias /reports/awstatsclasses "/path/to/awstats/classes/"
Alias /reports/awstatscss "/path/to/awstats/css/"
Alias /reports/awstatsicons "/path/to/awstats/icon/"
ScriptAlias /reports/webstats/ "/path/to/awstats/cgi-bin/"
RewriteEngine On
RewriteRule ^/reports/webstats/([-a-zA-Z0-9]+)$ \
/reports/webstats/awstats.pl?config=$1 [PT]
Then, if we have an awstats configuration called awstats.example.conf, the following URI will be used to load it: /reports/webstats/example (which is rewritten to /reports/webstats/awstats.pl?config=$1). The script kiddy scan on /cgi-bin/awstats.pl will simply return a 404 error.
Mod_access
The mod_access module [HREF9] is Apache's way of managing access to files and directories by checking the visitor's IP address, host or environment variable. It can be combined with other authentication modules (e.g mod_auth, mod_auth_digest, mod_auth_ldap [HREF10]) to provide stricter or more flexible access restriction.
Protect sensitive files and locations
There are files under the web tree that are meant to be accessed only by the web server or web scripts, and not visitors. They are placed there mostly as a matter of convenience for packaging the application, but often have no default protection from being accessed by visitors. Before the web application is activated, we must use mod_access to deny access to them:
<Files ~ "^\.">
# Any file starting with a . is meant to be hidden
# and so we deny web access
Order allow,deny
Deny from all
</Files>
<Files ~ "\.inc$">
# Files with extension .inc are often used for storing web
# application configurations or libraries and classes. If
# web access is not denied, the browser can print their
# content out in plain text
Order allow,deny
Deny from all
</Files>
<Directory "/path/to/website/design/html">
# If you use a template system and store the HTML
# templates (.html, .tpl etc.) under design/html, you
# may want to deny web access to them too, as only
# scripts are meant to load them for variable parsing.
Order allow,deny
Deny from all
</Directory>
If all administrators of our website access the admin section from known IP addresses or hosts (e.g from the company network), we apply the principle of least privilege:
<Location "/admin">
Order allow,deny # Access is denied by default
Include /etc/apache2/users/admin
# The above file contains the IP or host list or subnet
# of admin computers.
# e.g "Allow from admin1.company.com.au"
# Good for reuse
# If combined with the below, it will allow access if the
# hostname above is valid, or if the user authenticates
# him/herself
AuthType Digest
AuthName "admin-only"
AuthDigestDomain /admin /admin/
AuthDigestFile "/etc/apache2/.htdigestpwd"
Require user itadmin
BrowserMatch "MSIE" AuthDigestEnableQueryStringHack=On
Satisfy Any
</Location>Block known spammers and attackers
Realtime Blackhole List is a list of open relays, known sources of spams, compromised systems, open proxies, etc. This sort of list is frequently used by mail servers to combat spams and worms. We can use the same list for web servers to block known troublemakers. All we need is a patch for mod_access (called mod_access_rbl [HREF11]) and insert this in the main server configuration:
Order allow,deny Allow from all Deny via sbl-xbl.spamhaus.org
Here we use the public blacklist provided by spamhaus.org [HREF12]. While this is free, it can be a significant performance hit due to DNS queries being performed for every visitor. To boost performance, it is possible to mirror this list in a local DNS server, but an annual service fee applies.
- « Previous: Introduction
- » Next: Mod-security: the open source web application firewall