Reprinted with changes and updates from Introducing mod_security - O'Reilly Media
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.
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.
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 Index Sample Chapter Read Online--Safari Search this book on Safari: |
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:
//
). ./
). \
and /
equally (on Windows only). %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:
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,
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>
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.
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.
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"
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 mod_security, an open source intrusion detection and prevention engine for web applications, and the author of O'Reilly's upcoming Apache Security.
Return to Apache DevCenter.
Showing messages 1 through 14 of 14.
Because there was very little interest for a Java version of
ModSecurity I never posted it on the site. Eventually, I probably will,
but at the moment I am still adding features to the original.
SecFilterUnicodeEncoding
should be
SecFilterCheckUnicodeEncoding ...
Can't locate API module structure `mod_security' in file
E:/www/Apache2/modules/
mod_security.dll: No error
Press any key to continue . . .
gr.
Bart van der Ouderaa
As for canonizing paths a better approach would be to reject these
with HTTP 500. I actually do that in the apps in a more user
friendly way but if I don't have the source for something I'd rather
show my visitors a HTTP 500 page.