HowtoForge

Secure Your Apache With mod_security - Page 2

2 Configuration

Now let's start with a basic mod_security configuration that allows us to insert rules quickly. We put all mod_security rules in the global Apache configuration (it is possible to use most of the directives in a virtual host context, too, but not all).

On Debian and Ubuntu, we edit /etc/apache2/apache2.conf and put this at the end of it:

Debian/Ubuntu:

vi /etc/apache2/apache2.conf
<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 /var/log/apache2/audit_log
    # 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>

On Fedora, we add pretty much the same to /etc/httpd/conf.d/mod_security.conf, but change the paths to the log files as Fedora's Apache uses /var/log/httpd as its log directory instead of /var/log/apache2:

Fedora:

vi /etc/httpd/conf.d/mod_security.conf
<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 /var/log/httpd/audit_log
    # Debug level set to a minimum
    SecFilterDebugLog /var/log/httpd/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>

Afterwards we restart Apache:

Debian/Ubuntu:

/etc/init.d/apache2 restart

Fedora:

/etc/init.d/httpd restart   

The directives are pretty self-explanatory.


Actions

These are the most important actions mod_security can apply to an event that is catched by the filtering ruleset:


Until now not much has happened. I will now present a few filter rules that should give you an idea what you can do with mod_security.

Let's assume you have an application that is vulnerable to SQL injection attacks. An attacker could try to delete all records from a MySQL table like this:

http://www.example.com/login.php?user=tom';DELETE%20FROM%20users-- 

You can prevent this with this rule:

SecFilter "delete[[:space:]]+from" 

Whenever a request is caught by your filter, something like this is logged to your audit_log:

========================================
Request: 192.168.0.207 - - [04/Jul/2006:23:43:00 +1200] "GET /login.php?user=tom';DELETE%20FROM%20users-- HTTP/1.1" 500 1215
Handler: (null)
----------------------------------------
GET /login.php?user=tom';DELETE%20FROM%20users-- HTTP/1.1
Host: 192.168.0.100
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.4) Gecko/20060508 Firefox/1.5.0.4
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
mod_security-message: Access denied with code 500. Pattern match "delete[[:space:]]+from" at THE_REQUEST
mod_security-action: 500

HTTP/1.1 500 Internal Server Error
Last-Modified: Fri, 21 Oct 2005 14:30:18 GMT
ETag: "8238-4bf-833a5280"
Accept-Ranges: bytes
Content-Length: 1215
Connection: close
Content-Type: text/html

and SecFilterDefaultAction is applied (i.e., the request is denied, logged, and the user gets a 500 error). If you want a different action to take place, you can specify this individually for each filter rule, like this:

SecFilter "delete[[:space:]]+from" log,redirect:http://example.com/invalid_request.html

This would redirect the request to a HTML page that could say something about that the request was invalid.

To prevent more SQL injection attacks, we can add a few other rules:

SecFilter "insert[[:space:]]+into"
SecFilter "select.+from"
SecFilter "drop[[:space:]]table"

The following directives help to prevent cross-site scripting attacks:

SecFilter "<script"
SecFilter "<.+>"

This one is for preventing path traversal attacks:

SecFilter "../"

Please note: sometimes you find

SecFilter "\.\./"

instead of

SecFilter "../"

As of mod_security 1.8, there is no need to escape dots anymore. This is managed automatically by mod_security which means you it doesn't mastter if you escape dots or not!


This one blocks all requests that do not contain the string php in it:

SecFilter !php

This directive blocks requests that try to execute /bin/sh on your server:

SecFilter /bin/sh

This one blocks all requests that contain the string viagra:

SecFilter "viagra"

You can also use regular expressions like here:

SecFilter "(viagra|mortgage|herbal)"

The problem with the SecFilter directive is that it scans the whole request instead of particular fields. If the referrer is ihateviagra.mydomain.com, it would be blocked by the last two directives. But if you want to prevent comment spam, and your form to submit comments uses the POST method, then it would be better to just scan the POST variables for the string viagra. We can do this with the SecFilterSelective directive:

SecFilterSelective "POST_PAYLOAD" "viagra" 

You can also scan other fields of the request:

SecFilterSelective "HTTP_REFERER" "(viagra|mortgage|texasholdem)"

would block all requests that contain either viagra, mortgage, or texasholdem in the HTTP_REFERER field.

This rule requires HTTP_USER_AGENT and HTTP_HOST headers in every request:

SecFilterSelective "HTTP_USER_AGENT|HTTP_HOST" "^$"

You can also block IP addresses:

SecFilterSelective "REMOTE_ADDR" "^1.2.3.4$" 

If you have an input field url in your comment form, and you want to scan the value of url for the string viagra, you do it like this:

SecFilterSelective "ARG_url" "viagra" 

The following rule would redirect the Googlebot to the Google start page:

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

You can find a list of all fields you can scan in the ModSecurity documentation: http://www.modsecurity.org/documentation/modsecurity-apache/1.9.3/html-multipage/04-rules.html#N103D0

You should also check out these pages: http://www.onlamp.com/pub/a/apache/2003/11/26/mod_security.html and http://atomicplayboy.net/blog/2005/01/30/an-introduction-to-mod-security/, they contain a lot of useful examples and a more detailed explanation about what mod_security can do.

mod_security also allows your Apache to pretend it's another web server, e.g. like this:

SecServerSignature "Microsoft-IIS/5.0"

If Apache shouldn't show a signature at all, use this:

SecServerSignature " " 

mod_security also allows you to filter outgoing content. For example, if you use PHP scripts, and there's a possibility that your PHP scripts result in a fatal error, and you don't want to show the real error message to your users (because it can contain some important details that only you should see), you can do it like this:

SecFilterScanOutput On
SecFilterSelective OUTPUT "Fatal error:" deny,status:500
ErrorDocument 500 /php-fatal-error.html

If a fatal error occurs, the user will be redirected to the file php-fatal-error.html (which you must create before, of course).

This should give you a basic idea what you can do with mod_security. For more examples and details, you should definitely visit these URLs:

There's also an online rule creator for mod_security here: http://leavesrustle.com/tools/modsecurity which helps you to create your own rules.

You should now be able to add your own rules to the basic configuration from above. If you're unsure, you can start with this configuration:

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

    # Change Server: string
    SecServerSignature " "

    # Make sure that URL encoding is valid
    SecFilterCheckURLEncoding On

    # This setting should be set to On only if the Web site is
    # using the Unicode encoding. Otherwise it may interfere with
    # the normal Web site operation.
    SecFilterCheckUnicodeEncoding Off

    # Only allow bytes from this range
    SecFilterForceByteRange 1 255

    # The audit engine works independently and
    # can be turned On of Off on the per-server or
    # on the per-directory basis. "On" will log everything,
    # "DynamicOrRelevant" will log dynamic requests or violations,
    # and "RelevantOnly" will only log policy violations
    SecAuditEngine RelevantOnly

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

    # Should mod_security inspect POST payloads
    SecFilterScanPOST On

    # Action to take by default
    SecFilterDefaultAction "deny,log,status:500"

    # Require HTTP_USER_AGENT and HTTP_HOST in all requests
    SecFilterSelective "HTTP_USER_AGENT|HTTP_HOST" "^$"

    # Prevent path traversal (..) attacks
    SecFilter "../"

    # Weaker XSS protection but allows common HTML tags
    SecFilter "<[[:space:]]*script"

    # Prevent XSS atacks (HTML/Javascript injection)
    SecFilter "<(.|n)+>"

    # Very crude filters to prevent SQL injection attacks
    SecFilter "delete[[:space:]]+from"
    SecFilter "insert[[:space:]]+into"
    SecFilter "select.+from"
    SecFilter "drop[[:space:]]table"

    # Protecting from XSS attacks through the PHP session cookie
    SecFilterSelective ARG_PHPSESSID "!^[0-9a-z]*$"
    SecFilterSelective COOKIE_PHPSESSID "!^[0-9a-z]*$"
</IfModule>

Another good starting point is the configuration proposed by the mod_security documentation (http://www.modsecurity.org/documentation/modsecurity-apache/1.9.3/html-multipage/aa-recommended_configuration.html):

<IfModule mod_security.c>
    # Turn ModSecurity On
    SecFilterEngine On

    # Reject requests with status 403
    SecFilterDefaultAction "deny,log,status:403"

    # Some sane defaults
    SecFilterScanPOST On
    SecFilterCheckURLEncoding On
    SecFilterCheckUnicodeEncoding Off

    # Accept almost all byte values
    SecFilterForceByteRange 1 255

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

    SecUploadDir /tmp
    SecUploadKeepFiles Off

    # Only record the interesting stuff
    SecAuditEngine RelevantOnly
    SecAuditLog /var/log/apache2/audit_log

    # You normally won't need debug logging
    SecFilterDebugLevel 0
    SecFilterDebugLog /var/log/apache2/modsec_debug_log

    # Only accept request encodings we know how to handle
    # we exclude GET requests from this because some (automated)
    # clients supply "text/html" as Content-Type
    SecFilterSelective REQUEST_METHOD "!^(GET|HEAD)$" chain
    SecFilterSelective HTTP_Content-Type \
    "!(^application/x-www-form-urlencoded$|^multipart/form-data;)"

    # Do not accept GET or HEAD requests with bodies
    SecFilterSelective REQUEST_METHOD "^(GET|HEAD)$" chain
    SecFilterSelective HTTP_Content-Length "!^$"

    # Require Content-Length to be provided with
    # every POST request
    SecFilterSelective REQUEST_METHOD "^POST$" chain
    SecFilterSelective HTTP_Content-Length "^$"

    # Don't accept transfer encodings we know we don't handle
    SecFilterSelective HTTP_Transfer-Encoding "!^$"
</IfModule>

Or you take the configuration that comes with Fedora's mod_security package. Always make sure you adjust the paths to the log files!

 

Secure Your Apache With mod_security - Page 2