Getting Started with mod_security
What is more important than Web security? No matter how advanced your firewall, how compartmentalized your network and how strong your encryption, it all comes crashing down if your Web applications are vulnerable. On the one hand, there's no substitute for stringent user-input validation and other secure programming practices. But on the other hand, the stakes are too high to operate without some sort of safety net.
Ivan Ristic has given us just such a safety net: his excellent Apache module mod_security acts as an application-layer proxy between users and your Web applications. The mod_security module can stop SQL injection, cross-site scripting and other input-based Web attacks dead in their tracks, with only minimal effort on your part, and with no impact at all on either your Web developers or your users.
In this article, I tell you what you need to know to install and begin configuring mod_security on your own Apache-based Web server.
Space doesn't permit a comprehensive explanation of the entire range of threats that mod_security was designed to help mitigate. If you're new to Web security, your first stop should be the Open Web Application Security Project (OWASP) Web site (see the on-line Resources), home of the OWASP Top Ten Most Critical Web Application Security Vulnerabilities. A reasonable second stop is Chapter 10, “Securing Web Servers”, of my book Linux Server Security, 2nd edition, or Ivan Ristic's book Apache Security.
For our purposes here, suffice it to say that of the different types of vulnerabilities in Web servers, by far the most typical is poor or incomplete user-input validation. In fact, many of the items on the OWASP Top Ten list are really just subsets of this family of problems; command injection and cross-site scripting, for example, are types of user-input abuse. User input, of course, includes not only the URLs requested in HTTP GET requests, but also the data sent in POST commands.
The mod_security module gives your Apache Web server increased ability to inspect and process input from Web clients before it's acted on by the scripts or processes waiting for the input. The mod_security module even lets you inspect Web server output before it's transmitted back to clients. I love this feature: it allows you to watch out for server responses that might indicate that other filters have failed and an attack has succeeded!
The mod_security module also lets you automatically log events and session data that Apache wouldn't ordinarily log. This is useful not only for forensics purposes, but also for fine-tuning your mod_security rules. If you create stringent mod_security filters that you're worried may be triggered by legitimate traffic, you can set those filters only to log rather than actually dropping or redirecting the requests that trigger them.
But wait, there's more: mod_security works against encrypted Web traffic too! Because mod_security has access to transaction data before SSL encryption and after SSL decryption, mod_security can filter HTTPS traffic just as effectively as it filters HTTP.
Why wouldn't you need mod_security? Arguably, if you have a “brochure-ware” Web site that involves no databases or cgi scripts, serving up instead only static Web pages, mod_security might not be worth the trouble of setting up. I would suggest, however, that even on such a server, mod_security still might do some good for you, for example, in inhibiting certain types of information-gathering attacks. Read on, and decide for yourself.
The mod_security module runs on both Apache 1.3 and Apache 2.0. Although for most Linux distributions, you'll need to install mod_security from source, Debian has its own binary packages for mod_security.
If you run Debian, install the package mod-security-common, plus either libapache2-mod-security or libapache-mod-security, depending on whether you run Apache version 2 or 1, respectively. Although Debian's mod_security packages are for mod_security version 1.8.7, rather than the more-advanced version 1.9, this article is sufficiently basic to apply equally to versions 1.8.7 and 1.9.
If you run SUSE or Red Hat Enterprise Linux, you need to download the latest source code from www.modsecurity.org and compile it using the apsx or apsx2 command (part of SUSE's apache-devel and apache2-devel packages, respectively, and RHEL's httpd-devel package). All you need to do, once you've got apsx or apsx2 installed and have obtained the source code file mod_security.c, is issue one of these two commands from within the directory containing mod_security.c:
/usr/sbin/apxs -cia mod_security.c
or
/usr/sbin/apxs2 -cia mod_security.c
See the ModSecurity User Guide, or the mod_security source code's INSTALL file, for more information on installing mod_security from source.
The mod_security module, like all other Apache modules, is controlled from httpd.conf in Apache 1.3, or apache2.conf in Apache 2.x. On the one hand, mod_security's configuration parameters are straightforward to use and well documented. But on the other hand, as of this writing, there is no default configuration; the assumption is that you know enough about your environment and about Web security to create your own configuration from scratch.
And indeed, only you (and your Web developers) know what sorts of input are legitimate for the Web applications on your particular server. However, a minimum default configuration would be nice to start out with, wouldn't it? Luckily, one is provided, in the ModSecurity User Guide.
The rest of this article consists of a dissection of this minimum configuration, which should give you a taste of mod_security's power. For more a complete reference on mod_security configuration parameters and more-advanced examples, see the on-line Resources for this article.
Rather than presenting the entire configuration in one imposing list, let's break it up into manageable chunks. Listing 1 contains some basic settings.
Listing 1. Beginning of mod_security Parameters in apache2.conf/httpd.conf
<IfModule mod_security.c> SecFilterEngine On SecFilterDefaultAction "deny,log,status:403" SecFilterScanPOST On SecFilterCheckURLEncoding On SecFilterCheckUnicodeEncoding Off SecFilterForceByteRange 1 255
The first line in Listing 1 simply checks to see whether mod_security even has been enabled; if it isn't, the subsequent parameters are ignored. The parameter SecFilterEngine controls whether mod_security's filtering engine is enabled. The default value is Off, so you need to set this explicitly either to On, which causes mod_security to inspect all data, or DynamicOnly, which turns filtering on but tells mod_security to ignore requests for static content (specifically, it ignores requests with null handlers). Note that the DynamicOnly setting may not behave precisely how you expect; although it can save CPU cycles, some testing is in order if you use DynamicOnly.
SecFilterDefaultAction is very important. It defines the default action to take on filter matches. In Listing 1, this is set both to log the matching request and deny it with a status code 403 message. Obviously, you can specify multiple actions, separated by commas.
SecFilterScanPOST, if set to On, tells mod_security to inspect not only GET requests, but POST payloads as well.
Setting SecFilterCheckURLEncoding to On causes hexadecimal-encoded values within URLs to be checked for valid values (0-9, A-F).
SecFilterCheckUnicodeEncoding can be set to On if your Web server understands Unicode and uses UTF-8 encoding.
Finally, SecFilterForceByteRange specifies the range of allowable ASCII values in GET requests and in form data within POST requests.
On to our next set of parameters—Listing 2 shows some settings related to logging.
Listing 2. Logging-Related Parameters
SecUploadDir /tmp SecUploadKeepFiles Off SecAuditEngine RelevantOnly SecAuditLog logs/audit_log SecFilterDebugLog logs/modsec_debug_log SecFilterDebugLevel 0
SecUploadDir specifies a place for mod_security to store files uploaded via POST requests for processing, but it won't actually use this unless SecUploadKeepFiles is set to On. You probably don't want to enable this feature unless you've got a script, specified by a SecUploadApproveScript directive, that's ready to scan such files, for example, a script that invokes ClamAV and can return the results to mod_security. See the ModSecurity User Guide for more information on the SecUploadApproveScript parameter.
Setting SecAuditEngine to On, RelevantOnly or DynamicOrRelevant enables mod_security's powerful logging facility, which captures much more information than Apache's default logs. On causes all requests to be logged by mod_security, RelevantOnly logs only those requests that trigger mod_security filters and DynamicOrRelevant logs both relevant requests and requests with non-null handlers. SecAuditLog specifies the file to which mod_security should write its logs.
SecFilterDebugLog, obviously enough, specifies the file to which mod_security should log internal debugging information. Setting SecFilterDebugLevel to 0 turns off debug-logging; if you're actually having problems with mod_security, or are fine-tuning its configuration, you can set this to 1 for significant events (which will also be written to the audit log), 2 for info messages or 3 for still-more-detailed info messages.
Now, at last, we arrive at the real power of mod_security: customized filters. Listing 3 shows three such filters.
Listing 3. Some Custom Filters
SecFilterSelective REQUEST_METHOD ↪"!^(GET|HEAD)$" chain SecFilterSelective HTTP_Content-Type ↪"!(^application/x-www-form-urlencoded$|^multipart/form-data;)" SecFilterSelective REQUEST_METHOD "^POST$" chain SecFilterSelective HTTP_Content-Length "^$" SecFilterSelective HTTP_Transfer-Encoding "!^$" </IfModule>
Note the blank lines between filter groups. I inserted these to illustrate that appending the string chain to the end of a filter links it to the next one, such that the last filter in the chain will be evaluated only if the request first matches all prior filters in the chain. In this sense, chain is a little like an if-then statement.
The first pair of filters in Listing 3 checks to see whether the request is not a GET or a HEAD request; if not, it then checks to see if the request contains anything other than form data (content-type "application/x-www-form-urlencoded") or an uploaded file (encoding-type "multipart/form-data"), which are the only two types of encoding mod_security can parse. If both filters match, that is, the request isn't form data or a file, the request is denied (see SecFilterDefaultAction in Listing 1).
Note that our actual filter values ("!^(GET|HEAD)$" and "!(^application/x-www-form-urlencoded$|^multipart/form-data;)") are regular expressions. It's impossible for you to create your own custom filters unless you're comfortable with regular expressions; if you aren't, you may want to see Jeffrey Friedl's book Mastering Regular Expressions, 2nd edition (O'Reilly Media, 2002).
The second pair of filters in Listing 3 first checks to see if the request is a POST request. If so, it then checks to see whether the HTTP parameter Content-Length is set to null; if so, the request is rejected. POST requests are supposed to have proper lengths; if the length is null, this almost certainly suggests an attack of some kind.
Our last example filter, which unlike the first two is a single-line filter, protects us from non-null values for the HTTP parameter Transfer-Encoding. In other words, we want Transfer-Encoding to be set to null in HTTP requests, because the most common thing to set this to is chunked, which is practically never necessary but has been associated with attacks in the past.
Finally, we end with an </IfModule tag to indicate that we're done specifying mod_security parameters. In practice, the statements in Listings 1–3 would be in a single contiguous block; I split them into three groups only for readability.
If you prefer to maintain your mod_security settings in a special file, such as mod_security.conf, you can use an include statement within httpd.conf or apache2.conf, for example:
Include /etc/apache2/mod_security.conf
Once you've configured mod_security in httpd.conf or apache2.conf, you're ready to enable it. In the case of Apache 1.x, your httpd.conf needs to contain the line:
LoadModule security_module libexec/mod_security.so
and possibly also:
AddModule mod_security.c
If you run Apache 2.x, your apache2.conf file needs the line:
LoadModule security_module modules/mod_security.so
This is true unless you run Debian and installed its mod_security deb packages, in which case you need to run only the following command (as root) from a command prompt:
a2enmod mod-security
Once mod_security is enabled, you need to restart Apache in order to load the module. After you do this, be sure to test your Web applications to make sure you didn't Denial-of-Service attack yourself via your mod_security configuration!
With that, you should be ready to explore some more-advanced filters that watch specifically for requests that your site shouldn't expect to see. I strongly encourage you to take this next step; although I think this article should have given you a good starting point, you can find examples of much more powerful filters in the “ModSecurity For Apache User Guide” and other documents on the modsecurity.org Web site. Good luck, and stay safe!
Resources for this article: /article/8744.
Mick Bauer (darth.elmo@wiremonkeys.org) is Network Security Architect for one of the US's largest banks. He is the author of the O'Reilly book Linux Server Security, 2nd edition (formerly called Building Secure Servers With Linux), an occasional presenter at information security conferences and composer of the “Network Engineering Polka”.