by Ivan Ristic
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:
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/apachectl stop
# /path/to/apache/bin/apachectl start
Before you do that you need to add few lines to the configuration file:
# Turn the filtering engine On or Off
# Make sure that URL encoding is valid
# Unicode encoding check
# Only allow bytes from this range
SecFilterForceByteRange 0 255
# Only log suspicious requests
# The name of the audit log file
# Debug level set to a minimum
# Should mod_security inspect POST payloads
# By default log and deny suspicious requests
# with HTTP status 500
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.
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:
Remove multiple forward slashes (//).
Remove self-referenced directories (./).
Treat \ and / equally (on Windows only).
Perform URL decoding.
Replace null bytes (%00) with spaces.
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:
URL encoding validation.
Unicode encoding validation.
Byte range verification, where only certain character values are allowed as part of a request.
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:
deny, deny the request
allow, stop rule processing and allow the request
status:nnn, respond with a HTTP status nnn
redirect:url, redirect the request to the absolute URL url
exec:cmd, execute a script cmd
log, log the request to the error log
nolog, do not log the request
pass, ignore the current rule match and go to the next rule
pause:nnn, stall the request for nnn milliseconds. Be very careful with this action; one Apache instance will be busy stalling the request. You could actually help the attackers in creating a denial of service attack.
Other actions affect the flow of the rules, similarly to how mod_rewrite works:
chain, go to evaluate the next rule in the chain. When one rule fails to trigger an alert the remaining rules from the chain will be skipped.
skipnext:n, skip the next n rules.
Rules come in two flavors. In the simplest form,
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:
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:
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.
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.
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.
This rule rejects file uploads. This is simple but effective protection, rejecting requests based on the content type used for file upload.
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.
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 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.
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.
SecFilterForceByteRange 32 126
# Use this directive not to inherit rules from the parent context
# 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 “!^$”
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.
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:
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):
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.
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.