ModSecurity Overview

ModSecurity is an Apache module which adds an extra layer of security by analyzing client requests before they are processed by Apache and, furthermore, by analyzing server responses after a request has been processed. This article intends to be a ModSecurity overview and to provide the reader with the basic knowledge about the most important directives. For detailed information refer to the ModSecurity Documentation.

How ModSecurity Works

A definition of ModSecurity, as it appears in the official website, would be:

ModSecurity(TM) is an open source intrusion detection and prevention engine for web applications. It can also be called a web application firewall. It operates embedded into the web server, acting as a powerful umbrella, shielding applications from attacks.

The analysis of the client request and the server response is performed in stages:

  1. In the first stage, the request’s format is analyzed by a series of built-in checks (implicit validations). These checks can be controlled using configuration directives.
  2. In the second stage, the request goes through a series of user-defined input-filters. Whenever there is a match, a list of user-defined actions is performed.
  3. The request is processed by Apache.
  4. If output filtering is enabled, then the output goes through a series of user-defined output-filters. If there is a match, then the specified actions are performed.

Installation

Install the mod_security Apache module as you install every other package. On Fedora, for example, you can use yum:

# yum install mod_security

Basic Configuration

Ususally, there is a separate configuration file for each Apache module, which is imported into the main Apache configuration. On Fedora, ModSecurity’s configuration file exists in /etc/httpd/conf.d/mod_security.conf.
The basic configuration would be:

<IfModule mod_security.c>

    SecFilterEngine On
    SecAuditEngine RelevantOnly
    SecAuditLog logs/audit_log

    # Default action list
    SecFilterDefaultAction "deny,log,status:406"

    # Implicit validations

    SecFilterCheckURLEncoding On
    SecFilterCheckUnicodeEncoding On
    SecFilterForceByteRange 1 255
    SecFilterNormalizeCookies On
    #SecFilterCookieFormat 1
    
    # POST Payload scanning
    SecFilterScanPOST On

    # Output Filtering
    #SecFilterScanOutput On
    #SecFilterOutputMimeTypes "(null) text/html text/plain"

</IfModule>

All the directives that are used in the above block of code are called “Configuration Directives” in ModSecurity terminology. Below, there is some brief explanation for each one of them:

  • General configuration directives: ModSecurity’s operation involves two engines that work independently, the filter engine and the audit engine. Both engines can be turned On or Off.
    SecFilterEngine 
    Turn the filtering engine On or Off.
    SecAuditEngine 
    Turn the audit engine On or Off. This can be done on a per-server (virtualhost context in the Apache configuration) or on a per-directory basis (directory or .htaccess context in the Apache configuration). If this directive is set explicitly to On, all requests will be logged. If it is set to RelevantOnly, only those requests that were matched by a filter are logged. This is the recommended setting.
    SecAuditLog 
    The path to the audit log file.
  • Everytime a filter matches a request, an action is performed. These actions can be defined on a per-rule basis, but also a general default action can be specified.
    SecFilterDefaultAction 
    This directive sets the default action to be performed when a filter catches a request. The general syntax of actions is used, but some actions cannot be set in this directive (see ModSecurity Documentation). For example, the default action above will configure the engine to log each rule match and reject the request with status code 406.
  • General request validations (URL encoding, Unicode encoding, cookie format, byte range checks) can be performed by some configuration directives. These are built-in checks and are called implicit validations in ModSecurity terminology. Note, that in case you want to run ModSecurity in detect-only mode without rejecting any requests, for example by setting all the per-filter primary actions to pass, it is also required to set the default action to one that will not reject the request (eg pass), so that, even if the request fails to pass through these validations, the request analysis can continue on to the checks defined by the filters. An alternative for detect-only operation would be to explicitly turn off these built-in validations. For normal operation though, it is highly recommended that these validations are turned on:
    SecFilterCheckURLEncoding 
    Make sure that URL encoding is valid (On|Off).
    SecFilterCheckUnicodeEncoding 
    Make sure that Unicode encoding is valid (On|Off).
    SecFilterForceByteRange 
    Performs byte range checks. Only bytes that are in the specified range will be allowed in the request. Usually, the 1-255 range is accepted, but if you need to make it even more strict, you can set it to allow only bytes in the 32-126 range, for example: SecFilterForceByteRange 32 126
    SecFilterNormalizeCookies 
    By default, ModSecurity will not try to normalize cookie names and values. Some web applications encode the cookie content, but that can be normalized by setting this directive to On. The directive SecFilterCheckCookieFormat is completely deprecated in v1.9.x and has no effect.
    SecFilterCookieFormat 
    By default, ModSecurity supports normalization on cookies that are in Netscape format. It can be configured though to support version 1 cookies (as defined in RFC-2965). To enable version 1 cookie support set this directive to 1.
  • By default, ModSecurity can scan only GET variables. Scanning POST variables (POST Payload) is disabled by default.
    SecFilterScanPOST 
    By setting this directive to On the post payload can also be scanned.
  • By default, ModSecurity does not perform output filtering.
    SecFilterScanOutput 
    Set this configuration directive to On in order to enable output filtering (only Apache 2.x). Note that, when output filtering is enabled, only responses that have no content type, or whose content type is text/plan or text/html will be scanned. This behaviour can be modified with the SecFilterOutputMimeTypes directive.
    SecFilterOutputMimeTypes 
    Set which content types will be scanned in output filtering.

Specifying Actions

Whenever a filter catches a request, then an action, or better, a list of actions is performed. The general syntax for action lists is (no spaces are allowed between actions):

"primary_action,secondary_actions,flow_action or parameter:value action"

For example:

SecFilterDefaultAction "deny,log,status:406"

Action lists can be defined in three places:

  1. SecFilterDefaultAction directive: Whenever a user-defined rule, which follows this directive, is matched, then these default actions are performed, unless they are overridden by a per-ruleset or per-rule action list (see below).
  2. SecFilterSignatureAction directive: This directive may appear several times in the configuration and is used in order to specify a per-ruleset action list. A ruleset consists of all the rules that immediately follow the SecFilterSignatureAction directive. Action lists that are defined in this directive are merged with the default action list. Note that this merging procedure may result in action overriding. It depends on the type of the actions used. See the documentation for more information on this. Also, note that this action list will not be inherited by child contexts.
  3. SecFilter and SecFilterSelective directives: These directives accept an optional action list to be performed whenever the rule is matched. These are per-rule actions. Actions defined in these directives are merged with the actions that are defined in the other two directives above. Note that this merging procedure may result in action overriding. It depends on the type of the actions used. See the documentation for more information on this.

Another action-related directive is SecFilterActionsRestricted. When it is set to On, all the per-rule actions, except for the metadata actions (id, msg, rev, severity), are ignored. This is particularly useful when importing 3rd party rules, which also contain per-rule action lists, but you want to define your own action-list to be performed when any of these rules is matched.

In short, the most commonly used actions and what they actually do on a filter match are outlined below:

pass
Do nothing and continue on with the next rule. Useful when running ModSecurity in detect-mode.
allow
Allow the request, but do not continue the request or server-response analysis.
deny
Deny the request and return the error document which corresponds to the defined status code (see below).
log
Log to the Apache error log and to the adit log if the audit engine is enabled.
nolog
Log nothing to the Apache error log or to the audit log.
status
(status:CODE): Specifies the HTTP error code that will be returned if the request is rejected.
redirect
(redirect:URL): Redirect to specified page. Always overrides the status and deny actions.
exec
(exec:/path/to/script): Execute the specified script. This action is always performed in addition to the primary action (if one is defined). The script must write its output to stdout.

Also, a flow action, which affects the order in which the rules are processed, may be defined:

skipnext
(skipnext:N): Skips next N rules on filter match.
chain
Combine two or more filters together. The last filter is the one that will affect the request, but in order to reach the last filter, all previous chained filters must be matched.

One of the most useful features of ModSecurity is that metadata (rule id, revision number, a text message, severity information) may be defined for each filter. This metadata is defined as parameter:value, where action can be one of id, rev, msg, severity. For example:

SecFilter ".*admin.*" id:3,severity:1

Finally, a filter or a chain of filters can be explicitly marked for inheritance in child contexts with the mandatory action. For example:

SecFilter ".*admin.*" mandatory

or

SecFilter ".*admin.*" mandatory,chain
SecFilter ".*login.*"

For more information refer to the ModSecurity documentation.

Specifying Filters

Modsecurity supports writing filters in the ways outlined below. For further information refer to the documentation.

  • Simple Input Filter Syntax:
SecFilter KEYWORD [ACTIONS]
SecFilter !KEYWORD [ACTIONS]
  • Advanced Input Filter Syntax:
SecFilterSelective LOCATION KEYWORD [ACTIONS]
SecFilterSelective LOCATION !KEYWORD [ACTIONS]
  • Advanced Output Filter Syntax:
SecFilterSelective OUTPUT KEYWORD [ACTIONS]
SecFilterSelective OUTPUT !KEYWORD [ACTIONS]
KEYWORD 
is a regular expression.
 ! 
means that the regular expression is inverted, like the NOT logical operator.
LOCATION 
is a location identifiers or a list of location identifiers separated with the pipe | symbol. The location identifiers might be for example, the remote party’s IP (REMOTE_ADDR) or hostname (REMOTE_HOST) etc. For a detailed list of all the possible location identifiers refer to the “Advanced Filtering” section of the ModSecurity documentation.
OUTPUT 
indicates that the request will first be processed by Apache and the output will be checked against this filter. Requires that the directive SecFilterScanOutput has been set to On.

A special usage of advanced filtering is when the ARGS and the ARGS_someformfield are used. In this case, ARGS_someformfield supports inverted usage, so that all form fields are checked, except for the field that is defined in the ARGS_someformfield statement. For example:

SecFilterSelective "ARGS|!ARG_firstname" "Jack"

This one checks if any of the form fields, except for field “firstname“, is set to “Jack”.

Filter Inheritance

The filter inheritance scheme in ModSecurity follows the rules outlined below.

  • By default, all filters, together with their per-rule action lists, are inherited by child contexts.
  • The default action list is also inherited by child contexts.
  • Action lists, defined in the SecFilterSignatureAction directive (per-ruleset actions), are never inherited by child contexts.

It is possible to customize this scheme by using one or more of the directives SecFilterInheritance, SecFilterInheritanceMandatory, SecFilterImport, SecFilterRemove or by setting the per-rule action “mandatory” on a specific filter or filter chain.

Here are some notes about these directives:

SecFilterInheritance
Controls the inheritance of rules from the parent context. By default, its value is On. By setting it to Off in a context, eg virtualhost, none of the filters defined in parent contexts is inherited. This directive needs to be explicitly set in every context in which you do not want to inherit any rules. For example:

<Directory /path/to/some/dir>
    SecFilterInheritance Off
</Directory>

SecFilterImport
This directive works in conjuction with SecFilterInheritance and has a meaning only if the latter has been set to Off in a particular context. It accepts a space-delimited list of rule IDs and can be used to explicitly import filters from parent contexts. For example:

<Directory /path/to/some/dir>
    SecFilterInheritance Off
    SecFilterImport 1001 1002 1003
</Directory>

SecFilterRemove
This directive is the exact opposite of SecFilterImport. It has a meaning only if SecFilterInheritance has not been disabled in a particular context and works only for filters that have not been marked for mandatory inheritance (see below). It accepts a space-delimited list of rule IDs and can be used to explicitly disable inherited filters. For example:

<Directory /path/to/some/dir>
    SecFilterRemove 10 11 12
</Directory>

SecFilterInheritanceMandatory
Controls the inheritance of rules for the child contexts. By default, its value is Off. By setting it to On in a context, eg virtualhost, all of the filters defined in this particular context will be inherited by force by child contexts, despite the fact that filter inheritance might be disabled in those child contexts. This directive needs to be explicitly set in every context whose filters need to be always in-effect in subcontexts. For example:

<Virtualhost 192.168.0.1:80>
	SecFilterInheritanceMandatory On
	SecFilter ".*admin.*" "id:10"
	<Directory /path/to/some/dir/in/this/vhost>
		SecFilterRemove 10
	</Directory>
</Virtualhost>

In the above example, the filter with ID 10 will still be in effect in the directory context, because all rules have been marked for mandatory inheritance in its parent context.

Some times, it is needed to mark only specific rules for mandatory inheritance and not all of the current context’s rules. This can be achieved by using the mandatory action in a per-rule action list. For example:

<Virtualhost 192.168.0.1:80>
	SecFilter ".*admin.*" "id:10,mandatory"
</Virtualhost>

The rule with ID 10 will always be inherited by child contexts.

It is recommended that some critical filters are marked for mandatory inheritance, especially in environments where there is no trust between the users.

Per-Virtualhost ModSecurity Logging

It is possible to use Apache’s custom logging feature in order to log requests, which matched a ModSecurity filter, on a per-virtualhost basis. The key for this to work is the fact that ModSecurity defines the environment variable mod_security-relevant whenever a rule is matched.

So, by adding the following statement in the virtualhost context, Apache will record information about ModSecurity’s activity for the specific virtualhost. This statement is taken from the official documentation:

<IfModule mod_security.c>
    CustomLog /path/to/logs/modsec_custom_log \
        "%h %l %u %t \"%r\" %>s %b %{mod_security-message}i" \
        env=mod_security-relevant
</IfModule>

Recommended Filters

Accepted Encoding Types
As it is stated in the docs, ModSecurity supports two encoding types for the request body:

  1. application/x-www-form-urlencoded – used to transfer form data
  2. multipart/form-data – used for file transfers
In order to be sure that the web server will only accept requests with these two encoding types, a selective filter can be added. Note that GET requests are excluded from this rule because some (automated) clients supply “text/html” as Content-Type. Also, keep in mind that some web applications make use of the XMLRPC libraries in order to perform inter-application communication (communication between different web sites), send pingbacks/trackbacks for example. In this case, these features will not work, unless the text/xml encoding is accepted by ModSecurity.
# Accepted encoding types for request
SecFilterSelective REQUEST_METHOD "!^GET$" chain
SecFilterSelective HTTP_Content-Type "!(^$|^application/x-www-form-urlencoded|^multipart/form-data|^text/xml)"

Chunked Transfer Encoding
Reject requests whose body is delivered in chunks. This will not affect the server’s ability to send responses using the chunked transfer encoding (Rule taken from the ModSecurity Documentation):

# Reject Requests With Chunked Transfer Encoding
SecFilterSelective HTTP_Transfer-Encoding "!^$"

Missing User-Agent or Host Headers
Reject requests on which the User-Agent or Host headers are empty. This will only reject poorly made bots that do not define a user-agent string. Note that bots can easily use a fake user-agent string so to pretend that are common internet browsers. Nothing can be done to prevent this ability.

# Require HTTP_USER_AGENT and HTTP_HOST headers
SecFilterSelective "HTTP_USER_AGENT|HTTP_HOST" "^$"

Missing Content-Length Header
Reject POST requests that do not provide the Content-Length header (Rule taken from the default mod_security.conf supplied on Fedora):

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

Path Traversal Attacks
Reject requests which attempt to confuse the web server and make it traverse system paths and possibly execute shell commands. Note that this general filter might break the normal operation of web applications which use shell commands:

# Reject Path Traversal attacks
SecFilter "\.\./"

Cross-Site-Scripting Attacks (weak rule)
This filter rejects Cross-Site-Scripting Attacks (XSS) by preventing the usage of javascript statements (Javascript injection). This rule is safe to use, unless your application uses javascript in GET/POST request variables, which is very unlikely. (Rule taken from the default mod_security.conf supplied on Fedora):

# Weak rule to reject Cross-Site-Scripting Attacks (XSS) using javascript
SecFilter "<( |\n)*script"

Cross-Site-Scripting Attacks (stronger rule)
This filter rejects XSS Attacks by preventing HTML and Javascript injection attempts. This filter is a lot more general and stronger than the above and its usage is not recommended for most content management and web-forum systems, as there is a high possibility that it wll break their normal operation. (Rule taken from the default mod_security.conf supplied on Fedora):

# Stronger rule to reject XSS Attacks (HTML/Javascript)
SecFilter "<(.|
)+>"

ModSecurity Resources

There are many web sites that offer ready ModSecurity rulesets for various web applications. In the following links you can find some very useful general rulesets, but keep in mind that they need a bit of customization in order to work with your web application.

Some rule resources:

Useful information about the attacks:

Further Reading

The official documentation was used as a base for this overview and, in my opinion, is the best resource for help when coniguring ModSecurity. Everything is written in great detail in there:

  1. The ModSecurity documentation

Also, you might want to have a look at a web interface that aims to provide great help when writing filters for ModSecurity. This is available here.

Notes

I only recently started using mod_security. The first things that need to be done in order to be able to create effective filters that actually protect a web application is to study the application itself. Setting non-fatal actions for certain filters and checking the audit logs regularly is a good start. The goal is to protect the web application from being mis-used, but without breaking its normal operation.

This document is the second revision of the article, but it is still a draft. It needs to be enhanced with more examples. I also intend to write two extra articles related to this document, which will contain custom filter sets for WordPress and MediaWiki, which are the web applications I use. Links to those articles will appear in the “Further Reading” section of this document.

ModSecurity Overview by George Notaras is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Copyright © 2006 - Some Rights Reserved

4 responses on “ModSecurity Overview

  1. George Notaras Post authorPermalink →

    kanenas: I wish good luck :-) The “problem” is that there are no general filters that will work with every web application. Even the recommended filters I mention above need customization in order not to break the application’s normal operation.

    For example, in my case, it is needed to add text/xml to the acceptable encodings in a POST request, so that the wordpress pingbacks/trackbacks work. But, pingbacks still won’t work if the filter, that matches requests without a User-Agent header, denies further processing of the request. This happens because wordpress sends a GET request without specifying a user-agent string prior to actually executing the pings. Not to mention filters that catch other types of attacks (sql injection etc). It takes time…

  2. George Notaras Post authorPermalink →

    I finally found some time to update this article. This is the second revision of this document. Updates include:

    • Several typos and grammatical mistakes have been corrected as the first version had been written in a very fast pace.
    • The section about the filter inheritance has been rewritten from scratch in order to present the relevant directives in a more logical order and also to include some explanation and example about the SecFilterInheritanceMandatory directive, which was completely missing in the first revision.
    • The explanations of some of the directives have been rewritten.
    • The section with the recommended filters was also updated with some more info about the presented filters and also a horrible mistake – weak and strong XSS attack rules were the same – has been corrected.
  3. kanenas.net Permalink →

    I have tried mod_security and I have to say one thing…
    It does a great job !!!
    ;-)