Secure your Apache2 with mod-security

This article will show how-to install, configure and set up apache's mod-security module on a debian based system. This was done on Ubuntu Dapper and should fit any Debian based system.

Mod_security is an Apache 1.x/2.x module whose purpose is to tighten the Web application security by shielding the applications from attack. The idea is to filter request and web content before passing it to apache core.

Once installed, mod-security needs to be defined some rules matching patterns, filter request and HTTP stream and in the end do different actions like allowing, denying, log...

Effectively, it is an intrusion detection and/or prevention system for apache web server.

1. Installation:

In order to install mod-security with apache2, you need libapache2-mod-security:

 $sudo apt-get install libapache2-mod-security

and then enable mod-security and reload apache2

$sudo  a2enmod mod-security
$sudo  /etc/init.d/apache2 force-reload

Once this is done, you will be able to filter GET, POST urls .... and apply different rules depending on what the page/variables/url contain.

 2. Configuration:

A good habit is to keep functionalities configuration files as separate from the main configuration as possible. This avoid big messed up config files.

Apache running on debian offers such a place. In /etc/apache2/apache2.conf, you can find that directive:

 # Include generic snippets of statements
Include /etc/apache2/conf.d/[^.#]*

So this is the place we are going to put our mod-security statements. Create and edit /etc/apache2/conf.d/mod_security and add the following:

  <IfModule mod_security.c>
    # mod_security configuration directives
    # ...
    # Turn the filtering engine On or Off
    SecFilterEngine On

    # Some sane defaults
    #Check if URL characters where encoded
    SecFilterCheckURLEncoding On
    #Check UTF-8 encoding
    SecFilterCheckUnicodeEncoding Off

    #Allow 1 byte characters
    # Accept almost all byte values
    SecFilterForceByteRange 0 255

  
    # Server masking is optional
    # SecServerSignature "Microsoft-IIS/0.0"

    SecAuditEngine RelevantOnly
    # The name of the audit log file
    SecAuditLog /var/log/apache2/audit_log

    # You normally won't need debug logging
    # Debug level set to a minimum
    SecFilterDebugLog /var/log/apache2/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>

This is a default template we are going to use, what we 've done here is set the engine to On (SecFilterEngine On), we check that only good url encoding and characters are passed to the HTTP request, define log files, tell mod-security to scan POST request content and finally define the default filter action which will in this case log the error to /var/log/apache2/audit_log and send a 500 error to the client.

Here is a more detailled view of what those directives are used for:

Well, now that this is setted up,  it is about time to add some filters.


Secure your Apache2 with mod-security -- page 2


3. Adding Filtering Rules:

mod-security can take two kinds of filters:

Simple filter directives apply on any filters you turned on, so in our case, on GET and POST request.

the syntax of simple filter directive is:

SecFilter KEYWORD [ACTIONS]

 KEYWORD can be a string or a regular expression, ACTIONS is optionnal, if it is not defined, mod-security will use the SecFilterDefaultAction value (log and return 500 error page as we defined earlier, in mod-security skeleton file).

Advance filters do filter specific streams. Its syntax is:

SecFilterSelective LOCATION KEYWORD [ACTIONS]

Same here, ACTIONS is optionnal, LOCATION consist of a serie of location identifier separated by pipes (|). An advance filter looks like:

SecFilterSelective "REMOTE_ADDR|REMOTE_HOST" KEYWORD

you can get the full list of keywords from mod-security site . For actions, you might want to refer to mod-security documentation action page.

Among the most important actions, we could highlight:

Now that's said, it's time for some examples:


Secure your Apache2 with mod-security -- page 3

4. mod-security filter examples:

Suppose for instance you want to prevent attackers injecting shell command execution through your scripts. You could use this query in order to block anything containing /bin/:

SecFilter /bin/

As mod-security filter by default filters every fields activated, this will also though a 500 error and block access to some available to the public binaries you've made, such as http://example.com/my_project/bin/latest-release.tar.gz .

To counter this, We could use SecFilterSelective combine with a regular expression as a location and tell it to only look into GET and POST datas:

 SecFilterSelective "POST_PAYLOAD|QUERY_STRING" /bin/

 or even, by looking further down mod-security documentation, we could give a go to ARGS location:

SecFilterSelective ARGS /bin/

as well, if you simply want to filter arguments value, you could actually do it using ARGS_VALUES instead.

 If finally, you decide that only the parameter file should not contain a value with /bin/ in it, you could decide to restrict only that parameter with:

SecFilterSelective ARGS_file /bin/

Now, let's play with another example. Let say you want to prevent access to your web server content from outside your local network which is 192.168.1.0/24.

SecFilterSelective REMOTE_ADDR !^192.168.1.

but this will restrict also local access, playing with regular expression, you could use this one instead:

 SecFilterSelective "REMOTE_ADDR" !(^192.168.1.|^127.0.0.1$)

Finally, you setted up a virtual server www.my-virtual-server.com which should be available worldwide. chain is what you need. We are going to set up a rule which will only be applied if the hostname is not www.my-virtual-server.com:

  SecFilterSelective SERVER_NAME !www.my-virtual-server.com chain
  SecFilterSelective "REMOTE_ADDR" !(^192.168.1.|^127.0.0.1$)

or you could redirect the user to some other place:

 SecFilterSelective SERVER_NAME !www.my-virtual-server.com chain
 SecFilterSelective "REMOTE_ADDR" !(^192.168.1.|^127.0.0.1$) "log,redirect:http://www.foo.com/not-authorized.html"

while detecting intrusion/attacks, it could be nice to get notified when an intrusion occurs. Let's use the exec action:

SecFilterSelective SERVER_NAME !www.my-virtual-server.com chain
 SecFilterSelective "REMOTE_ADDR" !(^192.168.1.|^127.0.0.1$) "exec:/path/to/report-intrusion.pl"