ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


Introducing mod_security

by Ivan Ristic
11/26/2003

Running public web applications may seem like playing Russian roulette. Although achieving robust security on the Web is possible in theory, there's always a weak link in real life. It only takes one slip of the code to allow attackers unrestricted access to your data. If you have a public web application of modest complexity running, chances are good that is has some kind of security problem. Take this URL for example:
http://www.webapp.com/login.php?username=admin';DROP%20TABLE%20users--

If your application is vulnerable to SQL injection, invoking the URL above may very well delete all user data from your application. Do you make regular database backups?

Fortunately, the mod_security Apache module can protect you from this and other forms of web attacks.

Why Would You Use mod_security?

A year and a half ago, before I started working on mod_security, I used Snort to monitor my web traffic. It worked very well; I told Snort which keywords I was interested in and it alerted me every time one appeared in the data stream. But I wanted more. I wanted the freedom to specify complex rules and perform various HTTP related actions. Besides, having an IDS installed wherever a web server exists is very time consuming and expensive.

At the time I also tried the combination of mod_rewrite and mod_setenvif. Using mod_rewrite it is very easy to detect the words drop and table, and then redirect the client away from the original URL, preventing the attack. However, while that would certainly keep away less knowledgeable attackers, a determined attacker could simply invoke the same URL as above but use the POST method instead of GET. Since POST variables are not considered in the normal processing of most modules, the attack would go through.

Having established the need to build a new tool, I faced two choices: go with Java and create a full-blown reverse proxy and application gateway application, or create an Apache module, building on top of a large amount of existing code. Option one would require a lot of work and probably result in something very few people would want to use (hey, I wouldn't use it either). I wanted to build something flexible and easy to use, so I chose the latter. I've never looked back.

Going back to our URL example, to prevent the "drop table" SQL injection attack with mod_security, add the following to your Apache configuration:

SecFilter "drop[[:space:]]table"

The only parameter is a regular expression to be applied to the incoming request. This seems achievable with mod_rewrite, but the difference here is that mod_security will detect and prevent attacks performed using either GET or POST. As it turns out, adding the ability to monitor POST requests was a very big problem for Apache 1.3.x since it does not support a notion of filters.

Installation and Configuration

The best way to install mod_security is to compile it from the source code (or, if you are running Apache on Windows and don't have a compiler around go to the web site and download a pre-compiled dll):

$ /path/to/apache/bin/apxs -cia mod_security.c
# /path/to/apache/bin/apachectl stop
# /path/to/apache/bin/apachectl start

Before you do that you need to add few lines to the configuration file:

<IfModule mod_security.c>
    # Turn the filtering engine On or Off
    SecFilterEngine On
    
    # Make sure that URL encoding is valid
    SecFilterCheckURLEncoding On
    
    # Unicode encoding check
    SecFilterCheckUnicodeEncoding Off
    
    # Only allow bytes from this range
    SecFilterForceByteRange 0 255
    
    # Only log suspicious requests
    SecAuditEngine RelevantOnly
    
    # The name of the audit log file
    SecAuditLog logs/audit_log
    # Debug level set to a minimum
    SecFilterDebugLog logs/modsec_debug_log    
    SecFilterDebugLevel 0
    
    # Should mod_security inspect POST payloads
    SecFilterScanPOST On
    
    # By default log and deny suspicious requests
    # with HTTP status 500
    SecFilterDefaultAction "deny,log,status:500"
    
</IfModule>

I've left the comments in the code so it should be pretty evident what directives do. This configuration will activate mod_security but it won't do much. It is always a good idea to start with a relaxed configuration and build into a more restrictive one.

Related Reading

Apache Cookbook
By Ken Coar, Rich Bowen

So What Does this Do?

Even with the relaxed configuration, mod_security will still provide two benefits. First, it will perform a series of anti-evasive techniques and will canonicalize the input. This will help later when you start adding filtering rules to the configuration. Imagine you want to prevent people from executing a ps binary on the server, using a regular expression such as /bin/ps ax. This expression would catch simple invocations but perhaps not /bin//ps ax or /bin/ps%20ax or /bin/./ps ax. Here is a list of what mod_security does here:

I am also thinking about replacing all consecutive white space characters with spaces, but I am not yet sure about it.

The other benefit comes from certain built-in checks:

Actions

Whenever a rule match occurs a series of actions is performed. The default action list (configured through SecDefaultAction) is used in most cases. It is also possible to specify per-rule actions by supplying a second parameter to SecFilter or a third parameter to SecFilterSelective. Supported actions are:

Other actions affect the flow of the rules, similarly to how mod_rewrite works:

Filtering Rules

Rules come in two flavors. In the simplest form,

SecFilter keyword

will apply the keyword (a regular expression) to the first line of the incoming request (the one that looks like GET /index.php HTTP/1.0) and to the POST payload if it exists. It is a pretty broad rule whose purpose is mostly to be used as a first step when rules are introduced in articles like this one. You should instead use:

SecFilterSelective "variable list separated with |" keyword

as it allows much better control over what should be analysed (and spends less CPU cycles doing it). Instead of continuing to bore you with the syntax I will now present a series of interesting examples. Let them serve as inspiration; the most useful rules usually come from dealing with real-world problems.

This rule will allow all requests from a single IP address (representing my workstation) through. No other rules will be processed. Since such requests do not represent attacks this rule match will not be logged:

SecFilterSelective REMOTE_ADDR "^IP_ADDRESS_HERE$" nolog,allow

This rule allows me full access from my laptop when I am on the road. Because I don't know what your IP address will be, access is granted to all clients having a string Blend 42 in the User-Agent field. This is poor protection on its own but can be pretty interesting on top of some other authentication method.

SecFilterSelective HTTP_USER_AGENT "Blend 42"

This rule prevents SQL injection in a cookie. If a cookie is present, the request can proceed only if the cookie only contains one to nine digits.

SecFilterSelective COOKIE_sessionid "!^(|[0-9]{1,9})$"

This rule requires HTTP_USER_AGENT and HTTP_HOST headers in every request. Attackers often investigate using simple tools (even telnet) and don't send all headers as browsers do. Such requests can be rejected, logged, and monitored.

SecFilterSelective "HTTP_USER_AGENT|HTTP_HOST" "^$"

This rule rejects file uploads. This is simple but effective protection, rejecting requests based on the content type used for file upload.

SecFilterSelective "HTTP_CONTENT_TYPE" multipart/form-data

This rule logs requests without an Accept header to examine them later; again, manual requests frequently do not include all HTTP headers. The Keep-Alive header is another good candidate.

SecFilterSelective "HTTP_ACCEPT" "^$" log,pass

This rule will send me an email when the boss forgets his password again. We have two rules here. The first will trigger only when one specific file is requested (the one showing the "Login failed" message. The second rule will then check to see if the username used was ceo. If it was, it will then execute an external script.

SecFilterSelective REQUEST_URI "login_failed\.php" chain
SecFilterSelective ARG_username "^ceo$" log,exec:/home/apache/bin/notagain.pl

This rule sends Google back home by redirecting Googlebot somewhere else, based on the User-Agent header. It does not log rule matches.

SecFilter HTTP_USER_AGENT "Google" nolog,redirect:http://www.google.com

This rule checks all variables for JavaScript, allowing it in a variable named html. Disallowing JavaScript in all variables can be very difficult for some applications (most notably CMS tools). By using this rule we disallow JavaScript in all variables except in the one named html, where we know it can appear.

SecFilter "ARGS|!ARG_html" "<[:space:]*script"

Finally, this example shows how you can have multiple mod_security configurations. This means you can tailor rules for a specific application. Note the usage of the directive SecFilterInheritance. With it we tell mod_security to disregard all rules from the parent context and start with a clean slate.

<Location /anotherapp/>
    SecFilterForceByteRange 32 126
    # Use this directive not to inherit rules from the parent context   
    SecFilterInheritance Off
    
    # Developers often have special variables, which they use
    # to turn the debugging mode on. These two rules will
    # allow the use of a variable "debug" but only coming from
    # the internal network
    SecFilterSelective REMOTE_ADDR "!^192.168.254." chain
    SecFilterSelective ARG_debug "!^$"
</Location>

Performance Considerations

I have never had any performance problems with mod_security. In my performance tests the speed difference was around 10 percent. However, the practical performance penalty is smaller. On real web sites, a single page request may provoke many static requests for images, style sheets, and JavaScript libraries. Mod_security is smart enough not to look at those only if you tell it not to:

SecFilter DynamicOnly

The bottleneck is always in the IO operations. Make sure that the debugging mode is never turned on on a production server, and avoid using the full audit logging mode unless you really need to. In the configuration above, mod_security is configured to only log relevant requests, e.g., those that have triggered a filter.

Other Features

Internal chroot

If you have ever tried to chroot a web server you probably know that it is sometimes a complex task. With mod_security the complexity goes away. You are one configuration directive away from a chrooted server:

SecChrootPath /chroot/home/web/apache

The only requirement is that the web server path in the chroot be the same as the path outside of the chroot (in the example above, /home/web/apache). In addition to making chrooting very easy, this approach will allow you to have a chroot that contains only data files without binaries. This advantage comes from the fact that the chroot call is executed internally, after all the dynamic libraries are loaded and log files opened.

Changing Server Signature

Attackers and automated scripts frequently learn about the server and the version from the "Server" HTTP header that is delivered with every response. You can change only change this by changing the Apache source code, but you can also use this directive. (You should use this feature only if you're running Apache 1.x. Module mod_headers included with Apache 2.x should be able to intercept outgoing headers, and change them on the fly):

SecServerSignature "Microsoft-IIS/5.0"

What Next?

Although I compared mod_security to Snort at the beginning of this article, mod_security is just another tool in your security belt. It works best together with an IDS operating on a network level. Its biggest advantage is in filling the gap between the web server and the application, allowing you to protect your applications without actually touching the source code.

While you are reading this article I am busy working on a couple of new and very interesting features. First of all, I want to complete multipart/form-data encoding support. Once that is done, you will be able to intercept file uploads and run checks on files (using external binaries), with an option to reject them for any reason. Even more interesting is a feature called a "Application Armour," a special form of application lockdown where for each script you will be able to specify and verify every incoming parameter (you won't need to do it manually, don't worry).

In the meantime, please send me your comments and requirements to influence the way mod_security develops.

References

Ivan Ristic is a Web security specialist and the author of ModSecurity, an open source intrusion detection and prevention engine for web applications, and the author of O'Reilly's Apache Security.


Return to Apache DevCenter.

Copyright © 2009 O'Reilly Media, Inc.