> ModSecurity Handbook: Getting Started: Chapter 3. Configuration


Contents
Previous
Next

3 Configuration

Now that you have ModSecurity installed and ready to run, we can proceed to the configuration. This section, with its many subsections, goes through every part of ModSecurity configuration, explicitly configuring every little detail:

In accordance with its philosophy, ModSecurity won’t do anything implicitly. It won’t even run unless you tell it to. There are three reasons for that:

  1. By not doing anything implicitly, we ensure that ModSecurity does only what you tell it to. That not only keeps you in control but also makes you think about every feature before you add it to your configuration.

  2. It is impossible to design a default configuration that works in all circumstances. We can give you a framework within which you can work (as I’m doing in this section), but you still need to shape your configuration according to your needs.

  3. Security is not free. You pay for it by the increased consumption of RAM, CPU, or the possibility that you may block a legitimate request. Incorrect configuration may cause problems, so we need you to think carefully about what you’re doing.

The remainder of this section explains the proposed default configuration for ModSecurity. You can get a good overview of the default configuration simply by examining the configuration directives supported by ModSecurity, which are listed in Table 3.1 (with the exception of the logging directives, which are listed in several tables in Chapter 4, Logging).

Table 3.1. Main configuration directives

DirectiveDescription
SecDataDir Sets the folder for persistent storage
SecRequestBodyAccess Controls request body buffering
SecRequestBodyInMemoryLimit Sets the size of the per-request memory buffer
SecRequestBodyLimit Sets the maximum request body size ModSecurity will accept
SecRequestBodyLimitActionControls what happens once the request body limit is reached
SecRequestBodyNoFilesLimit Sets the maximum request body size, excluding uploaded files
SecResponseBodyAccess Controls response body buffering
SecResponseBodyLimit Specifies the response body buffering limit
SecResponseBodyLimitAction Controls what happens once the response body limit is reached
SecResponseBodyMimeType Specifies a list of response body MIME types to inspect
SecResponseBodyMimeTypesClear Clears the list of response body MIME types
SecRuleEngine Controls the operation of the rule engine
SecTmpDir Sets the folder for temporary files
SecUploadDir Sets the folder in which intercepted files will be stored
SecUploadFileLimit Set the maximum number of file uploads processed in a multipart POST
SecUploadKeepFiles Controls whether the uploaded files will be kept after the transaction is processed

Your first configuration task is to decide where on the filesystem to put the various bits and pieces that every ModSecurity installation consists of. Installation layout is often a matter of taste, so it’s difficult to give specific advice. Similarly, different choices may be appropriate in different circumstances. For example, if you’re adding ModSecurity to a web server and you intend to use it only occasionally, you may not want to use an elaborate folder structure, in which case you’ll probably put the ModSecurity folder underneath Apache’s. When you’re using ModSecurity as part of a dedicated reverse proxy installation, however, a well-thought-out structure is something that will save you a lot of time in the long run.

I always prefer to use an elaborate folder layout, because I like things to be neat and tidy and because the consistency helps me when I am managing multiple ModSecurity installations. I start by creating a dedicated folder for ModSecurity (/usr/local/modsecurity) with multiple subfolders underneath. The subfolders that are written to at runtime are all grouped (in /usr/local/modsecurity/var), which makes it easy to relocate them to a different filesystem using a symbolic link. I end up with the following structure:

Getting the permissions right may involve slightly more effort, depending on your circumstances. Most Apache installations bind to privileged ports (e.g., 80 and 443), which means that the web server must be started as root, and that also means root must be the principal owner of the installation. Because it’s not good practice to stay as root at runtime, Apache will switch to a low-privilege account (we’ll assume it’s apache) as soon as it initializes. You’ll find the proposed permissions in Table 3.2.

Table 3.2. Folder permissions

LocationOwnerGroupPermissions
/usr/local/modsecurity root apache rwxr-x---
/usr/local/modsecurity/bin root apache rwxr-x---
/usr/local/modsecurity/etc root root rwx------
/usr/local/modsecurity/var root apache rwxr-x---
/usr/local/modsecurity/var/audit apache root rwx------
/usr/local/modsecurity/var/data apache root rwx------
/usr/local/modsecurity/var/log root root rwx------
/usr/local/modsecurity/var/tmp apache apache rwxr-x---
/usr/local/modsecurity/var/upload apache root rwx------

I’ve arrived at the desired permission layout through the following requirements:

  1. As already discussed, root that owns everything by default, and we assign ownership to apache only when necessary.

  2. In two cases (/usr/local/modsecurity and /usr/local/modsecurity/var), we need to allow apache to access a folder so that it can get to a subfolder; we do this by creating a group, also called apache, of which user apache is the only member. We use the same group for the /usr/local/modsecurity/bin folder, where you might store some binaries Apache will need to execute at runtime.

  3. One folder, /usr/local/modsecurity/var/log, stands out; it’s the only folder underneath /usr/local/modsecurity/var to which apache is not allowed to write. That folder contains log files that are opened by Apache early on while it’s still running as root. On any Unix system, you must have only one account with write access to that folder, and it has to be the principal owner. In our case, that must be root. Anything else would create a security hole, whereby the apache user would be able to obtain partial root privileges using symlink trickery. (Essentially, in place of a log file, the apache user creates a symlink to some other root-owned file on the system. When Apache starts, it runs as root and opens for writing the system file that the apache user would otherwise be unable to touch. By submitting requests to Apache, one might be able to control exactly what’s written to the log files. That can lead to system compromise.)

  4. A careful observer will notice that I’ve allowed group folder access to /usr/local/modsecurity/var/tmp (which means that any member of the apache group is allowed to read the files in the folder) even though this folder is owned by apache, which already has full access. This is because you’ll sometimes want to allow ModSecurity to exchange information with a third user account—for example, if you want to scan uploaded files for viruses (usually via ClamAV). To allow the third user account to access the files created by ModSecurity, make it a member of the apache group and relax the file permissions using the SecUploadFileMode directive.

If you have anything but a trivial setup, spreading configuration across several files is necessary in order to make maintenance easier. The layout depends on what you want to do with ModSecurity. If you plan to run the OWASP ModSecurity Core Rule Set, for example, you’ll follow their setup proposal to a certain extent. Other rule layout conventions have more to do with taste than anything else, but in this section I’ll describe an approach that’s good enough to start with.

Whatever configuration design I use, there is usually one main entry point, typically named modsecurity.conf, which I use as a bridge between Apache and ModSecurity. In my bridge file, I refer to any other ModSecurity files I might have, such as those listed in Table 3.3.

Your main configuration file (modsecurity.conf) thus may contain only the following lines:

Include /usr/local/modsecurity/etc/main.conf
Include /usr/local/modsecurity/etc/rules-first.conf
Include /usr/local/modsecurity/etc/rules.conf
Include /usr/local/modsecurity/etc/rules-last.conf

As the first step, make Apache aware of ModSecurity, adding the needed components. Depending on how you’ve chosen to run ModSecurity, this may translate to adding one or more lines to your configuration file. This is what the lines may look like:

# Load Lua
LoadFile /usr/lib/x86_64-linux-gnu/liblua5.3.so
# Finally, load ModSecurity
LoadModule security2_module modules/mod_security2.so

Now you just need to tell Apache where to find the configuration:

<IfModule mod_security2.c>
    Include /usr/local/modsecurity/etc/modsecurity.conf
</IfModule>

The <IfModule> tag is there to ensure that the ModSecurity configuration files are used only if ModSecurity is active in the web server. This is common practice when configuring any nonessential Apache modules; it allows you to deactivate a module simply by commenting out the appropriate LoadModule line.

ModSecurity has a master switch—the SecRuleEngine directive—that allows you to quickly turn it on and off. This directive will always come first in every configuration. I generally recommend that you start in detection-only mode, because that way you can be sure nothing will be blocked:

# Enable ModSecurity, attaching it to every transaction.
SecRuleEngine DetectionOnly

You’ll normally want to keep this setting enabled, of course, but there will be cases in which you won’t be exactly sure whether ModSecurity is doing something it shouldn’t be. Whenever that happens, you’ll want to set it to Off, just for a moment or two, until you perform a request without it running.

The SecRuleEngine directive is context-sensitive (i.e., it works with Apache’s container tags <VirtualHost>, <Location>, and so on), which means that you can control exactly where ModSecurity runs. You can use this feature to enable ModSecurity only for some sites, parts of a web site, or even for a single script only. I discuss this feature in detail later.

Requests consist of two parts: the headers part, which is always present, and the body, which is optional and depends on the HTTP method employed. Use the SecRequestBodyAccess directive to tell ModSecurity to look at request bodies:

# Allow ModSecurity to access request bodies. If you don't,
# ModSecurity won't be able to see any POST parameters,
# and that's generally not what you want.
SecRequestBodyAccess On

Once this feature is enabled, ModSecurity not only will have access to the content transmitted in request bodies but also will completely buffer them. The buffering is essential for reliable attack prevention. With buffering in place, your rules have the opportunity to inspect requests in their entirety; only after you choose not to block will the requests be allowed through.

The downside of buffering is that, in most cases, it uses RAM for storage, which needs to be taken into account when ModSecurity is running embedded in a web server. There are three directives that control how buffering occurs. The first two, SecRequestBodyLimit and SecRequestBodyNoFilesLimit, establish request limits:

# Maximum request body size we will accept for buffering.
# If you support file uploads, then the value given on the
# first line has to be as large as the largest file you
# want to accept. The second value refers to the size of
# data, with files excluded. You want to keep that value
# as low as practical.
SecRequestBodyLimit 1310720
SecRequestBodyNoFilesLimit 131072

File uploads generally don’t use RAM (and thus don’t create an opportunity for a memory-based denial of service attack), which means that it’s safe to allow large requests as defined by SecRequestBodyLimit. With all the other requests, the RAM usage has to be considered, and a lower limit is imperative. SecRequestBodyNoFilesLimit is applied in such cases.

The third directive that addresses buffering, SecRequestBodyInMemoryLimit, controls how much of a request body will be stored in RAM, but it only works with file upload (multipart/form-data) requests:

# Store up to 128 KB of request body data in memory. When
# the multipart parser reaches this limit, it will start
# using your hard disk for storage. That is generally slow,
# but unavoidable.
SecRequestBodyInMemoryLimit 131072

The request bodies that fit within the limit configured with SecRequestBodyInMemoryLimit will be stored in RAM. The request bodies that are larger will be streamed to disk. This directive allows you to trade performance (storing request bodies in RAM is fast) for size (the storage capacity of your hard disk is much bigger than that of your RAM).

Similarly to requests, responses consist of headers and a body. Unlike requests, however, most responses have bodies. Use the SecResponseBodyAccess directive to tell ModSecurity to observe (and buffer) response bodies:

# Allow ModSecurity to access response bodies. We leave
# this disabled because most deployments want to focus on
# the incoming threats, and leaving this off reduces
# memory consumption.
SecResponseBodyAccess Off

I prefer to start with this setting disabled, because many deployments don’t care to look at what leaves their web servers. Keeping this feature disabled means ModSecurity will use less RAM and less CPU. If you care about output, however, just change the directive setting to On.

There is a complication with response bodies, because you generally only want to look at the bodies of some of the responses. Response bodies make up the bulk of the traffic on most web sites, most of which is just static files that don’t have any security relevance in most cases. The response MIME type is used to distinguish interesting responses from those that are not. The SecResponseBodyMimeType directive lists the response MIME types you’re interested in:

# Which response MIME types do you want to look at? You
# should adjust this configuration to catch documents
# but avoid static files (e.g., images and archives).
SecResponseBodyMimeType text/plain text/html

You can control the size of a response body buffer via the SecResponseBodyLimit directive:

# Buffer response bodies of up to 512 KB in length.
SecResponseBodyLimit 524288

The problem with limiting the size of a response body buffer is that it breaks sites for which pages are longer than the limit. In ModSecurity 2.5, we introduced the SecResponseBodyLimitAction directive, which allows ModSecurity users to choose what happens when the limit is reached:

# What happens when we encounter a response body larger
# than the configured limit? By default, we process what
# we have and let the rest through.
SecResponseBodyLimitAction ProcessPartial

If the setting is Reject, the response will be discarded and the transaction interrupted with a 500 (Internal Server Error) response code. If the setting is ProcessPartial, which I recommend, ModSecurity will process what it has in the buffer and allow the rest through.

At first glance, it may seem that allowing the processing of partial response bodies creates a security issue. For the attacker who controls output, it seems easy to create a response that’s long enough to bypass observation by ModSecurity—and this is true. However, if you have an attacker with full control of output, it’s impossible for any type of monitoring to work reliably. For example, such an attacker could encrypt output, in which case it will be opaque to ModSecurity. Response body monitoring works best to detect information leakage, configuration errors, traces of attacks (successful or not), and data leakage in cases in which an attacker does not have full control of output.

Other than that, response monitoring is most useful when it comes to preventing the data leakage that comes from low-level error messages (e.g., database problems). Because such messages typically appear near the beginning of a page, the ProcessPartial setting will work just as well to catch them.

We’ve made the decisions regarding filesystem locations already, so all we need to do now is translate them into configuration. The following two directives tell ModSecurity where to create temporary files (SecTmpDir) and where to store persistent data (SecDataDir):

# The location where ModSecurity will store temporary files
# (e.g., when it needs to handle a multipart request
# body that's larger than the configured limit). If you don't
# specify a location here, your system's default will be used.
# It's recommended that you specify a location that's private.
SecTmpDir /usr/local/modsecurity/var/tmp/

# The location where ModSecurity will keep its data. This,
# too, needs to be a path that other users can't access.
# IMPORTANT: The path defined by SecDataDir must reside on
# on the same partition as the path defined by SecTmpDir.
SecDataDir /usr/local/modsecurity/var/data/

Next, we’ll configure the handling of file uploads. We’ll configure the folder where ModSecurity will store intercepted files, but keep this functionality disabled for now. File upload interception slows down ModSecurity and can potentially consume a lot of disk space, so you’ll want to enable this functionality where you really need it.

# The location where ModSecurity will store intercepted
# uploaded files. This location must be private to ModSecurity.
SecUploadDir /usr/local/modsecurity/var/upload/

# By default, do not intercept (nor store) uploaded files.
SecUploadKeepFiles Off

For now, we also assume that you will not be using external scripts to inspect uploaded files. That allows us to keep the file permissions more secure, by allowing access only to the apache user:

# Uploaded files are by default created with permissions that
# don't allow any other user to access them. You may need to
# relax that if you want to interface ModSecurity with an
# external program (e.g., an anti-virus program).
SecUploadFileMode 0600

You should set the maximum number of files that ModSecurity will handle in a request:

# Limit the number of files we are willing
# to handle in any one request.
SecUploadFileLimit 32

There isn’t a limit by default, so setting one in the configuration is very important. The issue here first is that it’s easy for an attacker to include many embedded files (hundreds or even thousands) in a single multipart/form-data request, but also you don’t want ModSecurity to create that many files on the filesystem (which happens only if the storage or validation of uploaded files is required), because it would create a denial of service situation.

Debug logging is very useful for troubleshooting, but in production you want to keep it at minimum, because too much logging will affect performance. The debug log will duplicate what you’ll also see in Apache’s error log up to level 3. If the error log is growing fast and has to be rotated quickly, it can be useful to keep the ModSecurity-related messages longer in the debug log. However, if there are a lot of ModSecurity alerts, redundancy will be an issue, and you’ll need to make sure you also rotate the debug log regularly. It’s perfectly okay to run with a debug log level of 0 and to rely on the Apache error log. Any value above 3 is not recommended in production.

# Debug log
SecDebugLog /usr/local/modsecurity/var/log/debug.log
SecDebugLogLevel 3

In ModSecurity terminology, audit logging refers to the ability to record complete transaction data. For a typical transaction without a request body, this translates to roughly 1 KB. Multiply that by the number of requests you’re receiving daily and you’ll soon realize that you want to keep this type of logging to an absolute minimum.

Our default configuration will use audit logging only for the transactions that are relevant, which means those that have had an error or a warning reported against them. Other possible values for SecAuditEngine are On (log everything) and Off (log nothing).

# Log only what's really necessary.
SecAuditEngine RelevantOnly

In addition, we’ll also log the transactions with response status codes that indicate a server error (500–599). You should never see such transactions on an error-free server. The extra data logged by ModSecurity may help you uncover security issues or problems of some other type.

# Also log requests that cause a server error.
SecAuditLogRelevantStatus ^5

Alternatively, you can also log client errors in the range of 400–499. This can be useful because affected users often will contact you with support questions. You probably don’t want to log status code 404 (Not Found) in this case, so the complete regular expression to keep an audit log of all erroneous requests—with the exception of 404—is as follows:

# Also log requests that cause an error.
SecAuditLogRelevantStatus "^(?:5|4(?!04))"

The audit log separates its records into multiple parts. Each part is assigned a single letter. You enable the logging of the individual parts by listing them as parameters of the SecAuditLogParts directive. By default, we log all transaction data except response bodies. This assumes that you will seldom log (as it should be), because response bodies can take up a lot of space.

# Log everything we know about a transaction.
SecAuditLogParts ABDEFHIJKZ

Using the same assumption, we choose to use a single file to store all the recorded information. This is not adequate for installations that will log a lot and it prevents remote logging, but it’s good enough to start with:

# Use a single file for logging.
SecAuditLogType Serial
SecAuditLog /usr/local/modsecurity/var/log/audit.log

As the final step, we’ll configure the path that will be used in the more scalable audit logging scheme, called concurrent logging, even though you won’t need to use it just yet:

# Specify the path for concurrent audit logging.
SecAuditLogStorageDir /usr/local/modsecurity/var/audit/

Now that we’re nearing the end of the configuration, you need to decide what you want to happen when a rule matches. We recommend that you start without blocking, because that will allow you to monitor the operation of your installation over a period of time and ensure that legitimate traffic is not being marked as suspicious:

SecDefaultAction "phase:1,log,auditlog,pass"

This default policy will work for all rules that follow it in the same configuration context.

As you may recall from our earlier discussion, ModSecurity avoids making decisions for you. It will detect problems as they occur, but it will generally leave it to you to deal with them. In our default configuration, we’ll have a couple of rules to deal with the situations that ModSecurity can’t deal with on its own: processing errors.

There are currently three types of processing errors:

Normally, you don’t need to be too concerned about encountering buffer limits, because they often occur during normal operation. If you do want to take them into account when making decisions, you can use the INBOUND_DATA_ERROR and OUTBOUND_DATA_ERROR variables for request and response buffering, respectively.

ModSecurity parsers are designed to be as permissive as possible without compromising security. They will raise flags when they fail, but also when they encounter something suspicious. By checking the flags in your rules, you can detect the processing errors.

Currently, the only parsing errors that can happen are request body processor errors. We’ll use two rules to handle such errors. The first rule will examine the REQBODY_PROCESSOR_ERROR flag for errors. This flag will be raised whenever a request body parsing error occurs, regardless of which parser was used for parsing:

# Verify that we've correctly processed the request body.
# As a rule of thumb, when failing to process a request body
# you should reject the request (when deployed in blocking mode)
# or log a high-severity alert (when deployed in detection-only mode).
SecRule REQBODY_PROCESSOR_ERROR "!@eq 0" \
    "id:2000,phase:2,block,t:none,log,msg:'Failed to parse request body: ↩
%{REQBODY_PROCESSOR_ERROR_MSG}'"

The second rule is specific to the multipart/form-data parser, which is used to handle file uploads. If it detects a problem, it produces an error message detailing the flaws:

# By default, be strict with what you accept in the multipart/form-data
# request body. If the rule below proves to be too strict for your
# environment, consider changing it to detection-only. You are encouraged
# *not* to remove it altogether.
SecRule MULTIPART_STRICT_ERROR "!@eq 0" \
"id:2001,phase:2,block,t:none,log,msg:'Multipart request body \
failed strict validation: \
PE %{REQBODY_PROCESSOR_ERROR}, \
BQ %{MULTIPART_BOUNDARY_QUOTED}, \
BW %{MULTIPART_BOUNDARY_WHITESPACE}, \
DB %{MULTIPART_DATA_BEFORE}, \
DA %{MULTIPART_DATA_AFTER}, \
HF %{MULTIPART_HEADER_FOLDING}, \
LF %{MULTIPART_LF_LINE}, \
SM %{MULTIPART_MISSING_SEMICOLON}, \
IQ %{MULTIPART_INVALID_QUOTING}, \
IF %{MULTIPART_INVALID_HEADER_FOLDING}, \
FE %{MULTIPART_FILE_LIMIT_EXCEEDED}'"

Errors specific to multipart parsers should never occur unless an attacker genuinely tries to bypass ModSecurity by manipulating the request body payload. Some versions of ModSecurity did have false positives in this area, but the most recent version should be false-positive-free. If you do encounter such a problem, feel free to post it to the ModSecurity users’ mailing list, noting that you’ve encountered an interesting attacker or a ModSecurity bug.

PCRE limits are set to protect the server from denial of service attacks via excessive resource consumption in regular expression calculations. The default limits are very low. Therefore, users can control the setting of the limits via SecPcreMatchLimit and SecPcreMatchLimitRecursion. The debug log will identify rules that have exceeded the limits—for example:

[3] Rule 292d670 [id "941140"][file "/usr/local/modsecurity/etc/core-rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf"][line "514"] - Execution error - PCRE limits exceeded (-8): (null).

For now, leave the PCRE limits defaults as they are, but add a rule to warn us when they’re exceeded:

SecRule TX:MSC_PCRE_LIMITS_EXCEEDED "@eq 1" \
    "id:9000,phase:5,pass,t:none,log,msg:'PCRE limits exceeded'"

I’ve used phase 5 for the rule, but if you’re really paranoid and think that exceeding PCRE limits is grounds for blocking, switch to phase 2 (and change pass to something else).

After you’re done installing and configuring ModSecurity, we recommend undertaking a short exercise to ensure everything is in order:

  1. Add a simple blocking rule to detect something in a parameter. For example, the following rule will inspect all parameters for the string MY_UNIQUE_TEST_STRING, responding with a 503 (Service Unavailable) on a match:

    SecRule ARGS "@contains MY_UNIQUE_TEST_STRING" \
        "id:2000,phase:2,deny,status:503,log"
  2. Restart Apache, using the graceful restart option if your server is in production and you don’t want any downtime.

  3. Send a GET request, using your browser, to the ModSecurity-protected server, including the “attack payload” in a parameter (i.e., http://www.example.com/?test=MY_UNIQUE_TEST_STRING). ModSecurity should block the request.

  4. Verify that the message has appeared in both the error log and the debug log and that the audit log contains the complete transaction.

  5. Submit a POST request that triggers the test rule. With this request, you’re testing whether ModSecurity will see the request body and whether it will be able to pass the data in it to your backend after inspection. For this test in particular, it’s important that you’re testing with the actual application you want to protect. Only doing so will exercise the entire stack of components that make the application. This test is important because of the way Apache modules are written (very little documentation, so module authors generally employ any approach that “works” for them); you can never be 100% certain that a third-party module was implemented correctly. For example, it’s possible to write a module that will essentially hijack a request early on and bypass all other modules, including ModSecurity. We’re doing this test simply because we don’t want to leave anything to chance.

  6. If you want to be really pedantic (I have been, on many occasion; you can never be too sure), you may want to consider writing a special test script for your application, which will somehow record the fact that it has been invoked (mine usually writes to a file in /tmp). By sending a request that includes an attack—which will be intercepted by ModSecurity—and verifying that the script has not been invoked, you can be completely sure that blocking works as intended.

  7. Remove the test rule and restart Apache again.

  8. Finally, and just to be absolutely sure, examine the permissions on all Apache and ModSecurity locations and verify that they’re correct.

You’re done!

In this chapter, we looked at the core configuration options of ModSecurity. Strictly speaking, we could have left many of these options set to their defaults and spent about a tenth of this time on configuration, but I’ve always found it better to explicitly define every setting; with that approach, you end up with the configuration that’s tailored to your needs. In addition, you get to know ModSecurity better, which might prove crucial at some point in the future.

Quite a few more optional configuration directives exist. Most of them are highly advanced or only applicable in rare and special situations. They’re covered in Chapter 15, Directives.

We didn’t pay much attention to logging in this chapter, opting to configure both the debug log and the audit log conservatively. However, there’s a wealth of logging options in ModSecurity. In the next chapter, I’ll discuss logging in detail and conclude with the configuration topics.