> ModSecurity Handbook: Getting Started: Chapter 4. Logging


Contents
Previous
Next

4 Logging

This chapter covers the logging capabilities of ModSecurity in detail. Logging is a big part of what ModSecurity does, so it’s not surprising that there are extensive logging facilities available for your use.

The debug log is going to be your primary troubleshooting tool, especially initially, while you’re learning how ModSecurity works. You’re likely to spend a lot of time with the debug log cranked up to level 9, observing why certain things work the way they do. There are two debug log directives, as you can see in Table 4.1.

In theory, there are 10 debug log levels, but not all are used. You’ll find the ones that are in Table 4.2. Messages with levels 1–3 are copied to Apache’s error log. The higher-level messages are there mostly for troubleshooting and debugging.

You will want to keep the debug log level in production low (either at 3 if you want a copy of all messages in the debug log or at 0 if you’re happy having the messages only in the error log). You can expect in excess of 50 debug log messages (each message is an I/O operation) and at least 7 KB of data for an average transaction; logging all that for every transaction consumes a lot of resources.

This is what a single debug log line looks like:

[24/Jul/2016:17:38:25 +0200] [192.168.3.111/sid#15e21f8][rid#7f683c002970]↩
[/index.html][4] Initialising transaction (txid V5Tg8X8AAQEAABXZdiwAAAAA).

The line starts with metadata that is often longer than the message itself: the time, client’s IP address, internal server ID, internal request ID, request URI, and, finally, the debug log level. The rest of the line is occupied by the message, which is essentially free-form. You will find many examples of debug log messages throughout this guide.

There’s another reason to avoid extensive debug logging in production, and that’s simply that it’s very difficult. There’s usually so much data that it sometimes takes ages to find the messages that pertain to the transaction you want to investigate. In spite of the difficulties, you may occasionally need to debug in production because you can’t reproduce a problem elsewhere.

One way to make debugging easier is to keep debug logging disabled by default and enable it only for the part of the site that you want to debug. You can do this by overriding the default configuration, using the <Location> context directive, as shown ahead. While you’re doing that, it may be a good idea to specify a different debug log file altogether. That way, you’ll keep the main debug log file free of your tests.

<Location /myapp/>
    SecDebugLogLevel 9
    SecDebugLog /usr/local/modsecurity/var/log/troubleshooting.log
</Location>

This approach, although handy, still doesn’t guarantee that the volume of information in the debug log will be manageable. What you really want is to enable debug logging for the requests a specific client sends. ModSecurity provides a solution for this by allowing a debug log level to be changed at runtime, on a per-request basis. This is done by using the special ctl action that allows some of the configuration to be updated at runtime.

All you need to do is somehow uniquely identify yourself. In some circumstances, observing the IP address will be sufficient:

SecRule REMOTE_ADDR "@ipMatch 192.168.1.1" \
    id:1000,phase:1,pass,nolog,ctl:debugLogLevel=9

Using your IP address won’t work in cases in which you’re hidden by a network address translation of some sort and share an IP address with a bunch of other users. One straightforward approach is to modify your browser settings to put a unique identifier in your User-Agent request header. (How exactly that’s done depends on the browser you’re using.)

SecRule REQUEST_HEADERS:User-Agent "@contains YOUR_UNIQUE_ID" \
    id:1000,phase:1,pass,nolog,ctl:debugLogLevel=9

This approach, although easy, has a drawback: all your requests will cause an increase in debug logging. You may think of an application in terms of dynamic pages, but extensive debug logging will be enabled for every single embedded object, too. Also, if you’re dealing with an application that you’re using frequently, you may want to avoid excessive logging.

The most accurate way to dynamically enable detailed debug logging is to manually indicate to ModSecurity the exact requests on which you want it to increase logging. You can do this by modifying your User-Agent string on a request-by-request basis, using one of the tools that support request interception and modification. You can do so via a browser extension or can use an interception proxy. Armed with such a tool, you submit your requests in your browser, modify them in the tool, and then allow them through, modified. It’s a bit involved, but a time-saver overall. While you’re at it, it’s a good idea to make your identifiers similar enough for your rule to always detect them, but different enough to allow you to use a search function to quickly find the exact request in a file with thousands of lines.

It’s a little-known fact that the earliest versions of ModSecurity essentially had one feature: the ability to record complete HTTP transaction data. Being able to see exactly what’s exchanged between browsers and servers is very important for developers, yet few web servers make it possible. The audit log, which does just that, was one of the first features implemented.

ModSecurity is currently able to log most, but not all, transactions. Transactions involving errors (e.g., 400 and 404 transactions) and some third-party Apache modules like mod_auth_cas use a different execution path, which ModSecurity doesn’t support. This means that they shortcut the ModSecurity rule phases 1 to 4, effectively preventing the module from extracting the necessary data out of the request. Audit log directives are shown in Table 4.3.

A typical audit log entry (short, GET request, brief User-Agent, without a body, and no logging of the response body) consumes around 1.5 KB. Requests with bodies will increase the amount of data that needs to be logged, as well as the logging of response bodies.

Logically, each audit log entry is a single file. When serial audit logging is used, all entries will be placed within one file, but with concurrent audit logging, one file per entry is used. There is a native format for the individual entries or an alternative JSON format. Looking at a single audit log entry in the default native format, you’ll find that it consists of multiple independent segments (parts):

--6b253045-A--
...
--6b253045-B--
...
--6b253045-C--
...
--6b253045-F--
...
--6b253045-E--
...
--6b253045-H--
...
--6b253045-Z--

A segment begins with a boundary and ends when the next segment begins. The only exception is the terminating segment (Z), which consists only of the boundary. The idea behind the use of multiple segments is to allow each audit log entry to contain potentially different information. Only parts A and Z are mandatory; the use of the other parts is controlled with the SecAuditLogParts directive. Table 4.4 contains the list of all audit log parts, along with a description of their purpose.

Every audit log entry begins with part A, which contains the basic information about the transaction: time, unique ID, source IP address, source port, destination IP address, and destination port:

--be58b513-A--
[27/Jul/2016:05:46:16 +0200] V5guiH8AAQEAADTeJ2wAAAAK 192.168.3.1 50084 192.168.3.111 80

Part B contains the request headers and nothing else:

POST /index.html?a=test HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://example.com/index.html
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 6

Part C contains the raw request body, typically that of a POST request:

--be58b513-C--
b=test

Part F contains the response headers:

--be58b513-F--
HTTP/1.1 200 OK
Last-Modified: Sun, 24 Jul 2016 15:24:49 GMT
ETag: "2d-5386344b7871a"
Accept-Ranges: bytes
Content-Length: 159
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

Part E contains the response body:

--be58b513-E--
<html><body><h1>It works!</h1></body></html>

<form action="index.html?a=test" method="POST">
<textarea name="b">test</textarea>
<input type=submit>
</form>

The final part, H, contains additional transaction information:

--be58b513-H--
Stopwatch: 1470025005945403 1715 (- - -)
Stopwatch2: 1470025005945403 1715; combined=26, p1=0, p2=0, p3=0, p4=0, p5=26, ↩
sr=0, sw=0, l=0, gc=0
Response-Body-Transformed: Dechunked
Producer: ModSecurity for Apache/2.9.1 (http://www.modsecurity.org/).
Server: Apache
Engine-Mode: "ENABLED"

Part K contains a list of rules that matched in a transaction. It isn’t unusual for this part to be empty, but if you have a complex ruleset, it may show quite a few rules. Audit logs that record transactions for which there were warnings or those that were blocked will contain at least one rule here. In this example, you’ll find a rule that emits a warning on every request:

--be58b513-K--
SecAction "phase:2,auditlog,log,pass,msg:'Matching test'"

Every audit log file ends with the terminating boundary, which is part Z:

--be58b513-Z--

Initially, ModSecurity supported only the serial audit logging type. Concurrent logging was introduced to address two issues:

Concurrent audit logging changes the operation of ModSecurity in two aspects. To observe the changes, switch to concurrent logging without activating mlogc by changing SecAuditLogType to Concurrent (don’t forget to restart Apache).

First, as expected, each audit log entry will be stored in a separate file. The files will be created not directly in the folder specified by SecAuditLogStorageDir but in an elaborate structure of subfolders, the names of which will be constructed from the current date and time:

20160727
20160727/20160727-0546
20160727/20160727-0546/20160727-054616-V5guiH8AAQEAADTeJ2wAAAAK
20160727/20160727-0546/20160727-054616-V5guBH8AAQEAADTeJ2cAAAAA

The purpose of the scheme is to prevent too many files from being created within one directory; many filesystems have limits that can be relatively quickly reached on a busy web server. The first two parts in each filename are based on time (YYYYMMDD and HHMMSS). The third parameter is the unique transaction ID.

In addition to each entry getting its own file, the type of the main audit log file will change when concurrent logging is activated. The file that previously stored the entries themselves will now be used as a record of all generated audit log files:

192.168.3.130 192.168.3.1 - - [27/Jul/2016:05:46:16 +0200] "GET / HTTP/1.1" 200 218 "-" "-" V5guiH8AAQEAADTeJ2wAAAAK "-" /20160727/20160727-0546/20160727-054616-V5guiH8AAQEAADTeJ2wAAAAK 0 1592 md5:48e90bf2a5ab9e7368d8b579f14bf82b

The index file is similar in principle to a web server access log. Each line describes one transaction, duplicating some of the information already available in audit log entries. The purpose of the index file is twofold:

When real-time audit log centralization is used, this information isn’t written to a file. Instead, it’s written to a pipe, which means that it’s sent directly to another process, which deals with the information immediately. You’ll see how that works in the next section.

ModSecurity comes with a tool called mlogc (short for ModSecurity Log Collector), which can be used to transport audit logs in real time to a remote logging server. This tool has the following characteristics:

If you’ve followed my installation instructions, you’ll have mlogc compiled and sitting in your bin/ folder. To proceed, you’ll need to configure it, then add it to the ModSecurity configuration.

The mlogc configuration file is similar to that of Apache, only simpler. We usually place it in /usr/local/modsecurity/etc/mlogc.conf. First, we need to tell mlogc where its “home” is, which is where it will create its log files. The mlogc log files are very important, because—as it’s Apache that starts mlogc and ModSecurity that talks to it—we never interact with mlogc directly. We’ll need to look in the log files for clues in case of problems:

# Specify the folder where the logs will be created
CollectorRoot  /usr/local/modsecurity/var/log

# Define what the log files will be called. You probably
# won't ever change the names, but mlogc requires you
# to define it.
ErrorLog       mlogc-error.log

# The error log level is a number between 0 and 5, with
# level 3 recommended for production (5 for troubleshooting).
ErrorLogLevel  3

# Specify the names of the data files. Similar comment as
# above: you won't want to change these, but they are required.
TransactionLog mlogc-transaction.log
QueuePath      mlogc-queue.log
LockFile       mlogc.lck

Then, we tell mlogc where to find audit log entries. The value given to LogStorageDir should be the same as the one you provided to ModSecurity’s SecAuditLogStorageDir:

# Where are the audit log entries created by ModSecurity?
LogStorageDir /usr/local/modsecurity/var/audit

Next, we need to tell mlogc where to submit audit log entries. We identify a remote server with a URL and credentials:

# Remote logging server details.
ConsoleURI "https://REMOTE_ADDRESS:8888/rpc/auditLogReceiver"
SensorUsername "USERNAME"
SensorPassword "PASSWORD"

The remaining configuration directives aren’t required, but it’s usually a good idea to explicitly configure your programs, rather than let them use their defaults:

# How many parallel connections to use to talk to the server,
# and how much to wait (in milliseconds) between submissions.
# These two directives are used to control the rate at which
# audit log entries are submitted.
MaxConnections      10
TransactionDelay    50

# How many entries is a single thread allowed to process
# before it must shut down.
MaxWorkerRequests   1000

# How long to wait at startup before really starting.
StartupDelay        5000

# Checkpoints are periods when the entries from the transaction
# log (which is written to sequentially) are consolidated with
# the entries in the main queue.
CheckpointInterval  15

# If network communication fails, suspend log
# submission to give the server time to recover.
ServerErrorTimeout  60

You’ll need to make two changes to your default configuration. First, you need to switch to concurrent audit logging, because that’s the only way mlogc can work:

SecAuditLogType Concurrent

Next, you need to activate mlogc, which is done using the piped logging feature of Apache:

SecAuditLog "|/usr/local/modsecurity/bin/mlogc ↩
/usr/local/modsecurity/etc/mlogc.conf"

The pipe character at the beginning of the line tells Apache to treat what follows as a command line. As a result, whenever you start Apache from now on, it will start a copy of mlogc in turn and keep it running in parallel, leaving a one-way communication channel that will be used by ModSecurity to inform mlogc of every new audit log entry it creates.

Please note that you still need to configure SecAuditLogStorageDir because ModSecurity will refuse to work properly without it. Your complete configuration should look like this now:

SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus ^5
SecAuditLogParts ABDEFHIJKZ
SecAuditLogType Concurrent
SecAuditLog "|/usr/local/modsecurity/bin/mlogc ↩
/usr/local/modsecurity/etc/mlogc.conf"
SecAuditLogStorageDir /usr/local/modsecurity/var/audit/

If you restart Apache now, you should see mlogc running:

USER       PID  COMMAND
root     11845  /usr/sbin/apache2 -k start
root     11846  /usr/local/modsecurity/bin/mlogc ↩
/usr/local/modsecurity/etc/mlogc.conf
apache   11847  /usr/sbin/apache2 -k start
apache   11848  /usr/sbin/apache2 -k start
apache   11849  /usr/sbin/apache2 -k start
apache   11850  /usr/sbin/apache2 -k start
apache   11851  /usr/sbin/apache2 -k start

If you go to the log/ folder, you should see two new log files:

-rw-r-----  1 root     root  83K Jul 29 15:12 audit.log
-rw-r-----  1 root     root  68M Jul 29 15:12 debug.log
-rw-r--r--  1 root     root  769 Jul 30 05:31 mlogc-error.log
-rw-r--r--  1 root     root    0 Jul 30 05:31 mlogc-transaction.log

If you look at the mlogc-error.log file, there will be signs of minimal activity (the timestamps from the beginning of every line were removed for clarity):

[3] [5877/0] Configuring ModSecurity Audit Log Collector 2.9.1.
[3] [5877/0] Delaying execution for 5000ms.
[3] [5897/0] Configuring ModSecurity Audit Log Collector 2.9.1.
[3] [5897/0] Delaying execution for 5000ms.
[3] [5877/0] Queue file not found. New one will be created.
[3] [5877/0] Caught SIGTERM, shutting down.
[3] [5877/0] ModSecurity Audit Log Collector 2.9.1 terminating normally.
[3] [5897/0] Queue file not found. New one will be created.

It’s normal for two copies of mlogc to have run, because that’s how Apache treats all piped logging programs. It starts two (one while it’s checking configuration), but leaves only one running. The second token on every line in the example is the combination of process ID and thread ID. Thus, you can see how there are two processes running at the same time (PID 5877 and PID 5897). Because only one program can handle the data files, mlogc is designed to wait for a while before it does anything. Basically, if it still lives after the delay, that means it’s the copy that’s meant to do something.

What happens if you make an error in the configuration file, preventing mlogc from working properly? As previously discussed, mlogc can’t just respond to you on the command line, so it will do the only thing it can—that is, it will report the problem and shut down. (Don’t be surprised if Apache continues to attempt to start it. That’s what Apache does with piped logging programs.)

If you make a mistake in defining the error log, you may get an error message in response to the attempt to start Apache. The following is the error message you’d get if you left ErrorLog undefined:

dev:/usr/local/modsecurity/etc# apache2ctl start
[1] [15997/0] Failed to open the error log (null): Bad address
[3] [15997/0] ModSecurity Audit Log Collector 2.9.1 terminating with error 1

If mlogc manages to open its error log, it will do what’s expected and write all error messages there. For example:

[1] [16402/0] QueuePath not defined in the configuration file.
[3] [16402/0] ModSecurity Audit Log Collector 2.9.1 terminating with error 1

At this point, it’s a good idea to delete the serial audit log file audit.log, or store it elsewhere. Having switched to concurrent logging, that file won’t be updated anymore, and it will only confuse you.

Assuming the default logging configuration (level 3), a single audit log entry handled by mlogc will produce one line in the log file:

[3] [1748/7f65840009c0] Entry completed (0.109 seconds, 1415 bytes): ↩
V5w0Rn8AAQEAAAbptJUAAABC

That’s basically all you need to know—that an entry has been safely transmitted to the intended destination. You’ll get more information when something goes wrong, of course. For example, you’ll see the following message whenever your logging server goes down:

[2] [23743/7ff2840009c0] Flagging server as errored after failure to submit entry V5wthX8AAQEAAE1D3OYAAAAE (cURL code 7): Failed to connect ↩
to loghost port 8888: Connection refused

The message will appear on the first failed delivery, and then once every minute until the server becomes operational. This is because mlogc will shut down its operation for a short period whenever something unusual happens with the server. Only one thread of operation will continue to work to probe the server, with processing returning to full speed once the server recovers. You’ll see the following information in the log:

[3] [1748/7f65840009c0] Clearing the server error flag after successful entry ↩
submission: V5w1WX8AAQEAAAerQowAAADA
[3] [1748/7f65840009c0] Entry completed (0.887 seconds, 1415 bytes): ↩
V5w1WX8AAQEAAAerQowAAADA

Going back to the error message, the first part tells you that there’s a problem with the server; the second part tells you what the problem is. In the previous case, the problem was couldn’t connect to host, which means the server is down. See Table 4.5 for the description of the most common problems.

If you still can’t resolve the problem, I suggest that you increase the mlogc error log level from 3 (NOTICE) to 5 (DEBUG2), restart Apache (graceful will do), and try to uncover more information that would point to a solution. Actually, I advise you to perform this exercise even before you encounter a problem, because an analysis of the detailed log output will give you a better understanding of how mlogc works.

File upload interception is a special form of logging, in which the files being uploaded to your server are intercepted, inspected, and stored—and all that before they are seen by an application. The directives related to this feature are in Table 4.6, but you’ve already seen them all in the section called “File Uploads” in Chapter 3, Configuration.

For most people, a more reasonable SecUploadKeepFiles setting is RelevantOnly, which enables the storage of only the files that have failed inspection in some way. For this setting to make sense, you need to have at least one external inspection script, along with a rule that invokes it.

There are four separate variables involved with file uploads, and you need to pick the right one for the inspection:

The best way to inspect a file is to pass the temporary filename to an inspection script, so we settle on the FILES_TMPNAMES variable. Once this is clear, a file inspection rule is rather simple:

SecRule FILES_TMPNAMES "@inspectFile /usr/local/modsecurity/bin/file-inspect.pl" \
    id:2000,phase:2,block,t:none,log

This example rule will invoke the /usr/local/modsecurity/bin/file-inspect.pl script for every uploaded file. The script will be given the location of the temporary file as its first and only parameter. It can do whatever it wants with the contents of the file, but it’s expected to return a single line of output that consists of a verdict (1 if everything is in order and 0 for a fault), followed by an error message—for example:

1 OK

Or:

0 Error

The alert message in the audit log will only display the verdict, but the debug log will display the full error message. The following debug log lines are produced by the inspection file:

[4] Recipe: Invoking rule 2735e58; [file "/usr/local/apache/conf/httpd.conf_pod_2016-07-28_10:10"] [line "238"] [id "2000"].
[5] Rule 2735e58: SecRule "FILES_TMPNAMES" "@inspectFile /usr/local/apache/bin/inspect-file.pl" "phase:2,id:2000,block,t:none,log"
[4] Transformation completed in 1 usec.
[4] Executing operator "inspectFile" with param "/usr/local/modsecurity/bin/file-inspect.pl" against FILES_TMPNAMES:f.
[9] Target value: "/tmp//20160728-103559-V5nD738AAQEAAD5bgsAAAAAA-file-yfp59Q"
[4] Executing /usr/local/apache/bin/file-inspect.pl to inspect /usr/local/modsecurity/var/tmp/↩
20160728-103559-V5nD738AAQEAAD5bgsAAAAAA-file-yfp59Q.
[9] Exec: /usr/local/apache/bin/file-inspect.pl
[4] Exec: First line from script output: "1 OK"
[4] Operator completed in 2722317 usec.
[4] Rule returned 0.

If an error occurs—for example, if you make a mistake in the name of the script—you’ll get an error message that looks similar to this one:

[9] Exec: /usr/local/modsecurity/bin/file_inspect.pl
[1] Exec: Execution failed while reading output: /usr/local/modsecurity/bin/file_inspect.pl (End of file found)

ClamAV is a popular open source anti-virus program. If you have it installed, the following script will allow you to utilize it to scan files from ModSecurity:

#!/usr/bin/perl
   
$CLAMSCAN = "/usr/bin/clamscan";
   
if (@ARGV != 1) {
    print "Usage: modsec-clamscan.pl FILENAME\n";
    exit;
}
   
my ($FILE) = @ARGV;
   
$cmd = "$CLAMSCAN --stdout $FILE";
$input = `$cmd`;
$input =~ m/^(.+)/;
$error_message = $1;
   
$output = "0 Unable to parse clamscan output";
   
if ($error_message =~ m/: Empty file\.$/) {
    $output = "1 empty file";
}
elsif ($error_message =~ m/: (.+) ERROR$/) {
    $output = "0 clamscan: $1";
}
elsif ($error_message =~ m/: (.+) FOUND$/) {
    $output = "0 clamscan: $1";
}
elsif ($error_message =~ m/: OK$/) {
    $output = "1 clamscan: OK";
}
   
print "$output\n";

Note

If you need a file to test with, you can download one from EICAR’s web site.19 The files at this location contain a test signature that will be picked up by ClamAV.

The error message from the integration script will return either the result of the inspection of the file or an error message if the inspection process failed. The following example shows a successful detection of a “virus”:

[9] Exec: /usr/local/modsecurity/bin/modsec-clamscan.pl
[4] Exec: First line from script output: "0 clamscan: Eicar-Test-Signature"
[4] Operator completed in 2628132 usec.
[2] Warning. File 
"/usr/local/modsecurity/var/tmp/20160728-110518-V5nKzn8AAQEAAD5bgsEAAAAC-file-szWj6L" rejected by the approver script "/usr/local/modsecurity/bin/modsec-clamscan.pl": 0 clamscan: Eicar-Test-Signature [file "/usr/local/modsecurity/etc/rules.conf"] [line "238"] [id "2000"]

If you look carefully at the example output, you’ll see that the inspection took more than two seconds. This isn’t unusual (even for my slow virtual server), because we’re creating a new instance of the ClamAV engine for every inspection. The scanning alone is fast, but the initialization takes considerable time. A more efficient method would be to use the ClamAV daemon (e.g., the clamav-daemon package on Debian) for inspection. In this case, the daemon is running all the time, and the script is only informing it that it needs to inspect a file.

Assuming you’ve followed the recommendation for the file permissions settings given in the section called “Folder Locations” in Chapter 3, Configuration, this is what you need to do:

An examination of the logs after the change in the configuration will tell you that there’s been a significant improvement—from seconds to milliseconds:

[9] Exec: /usr/local/modsecurity/bin/modsec-clamscan.pl
[4] Exec: First line from script output: "0 clamdscan: Eicar-Test-Signature"
[4] Operator completed in 20581 usec.
[2] Warning. File "/usr/local/modsecurity/var/tmp/20160728-111500-V5nNFH8AAQEAAGO1b38AAAAA-file-vSR2dT" rejected by the approver script "/usr/local/modsecurity/bin/modsec-clamscan.pl": 0 clamscan: Eicar-Test-Signature [file "/usr/local/modsecurity/etc/rules.conf"] [line "239"] [id "2000"]

By now, you’ve seen how you have many facilities you can use to configure logging to work exactly as you need it. The facilities can be grouped into four categories:

Most web application programmers are taught always to use POST methods for transactions that contain sensitive data. After all, it’s well known that request bodies are never logged, meaning that sensitive data will never be logged, either. ModSecurity changes this situation, because it allows for full transaction logging. To deal with sensitive data that may find its way into the logs, ModSecurity uses the sanitiseArg, sanitiseRequestHeader, sanitiseResponseHeader, sanitiseMatched, and sanitiseMatchedBytes sanitization actions. You basically just need to tell ModSecurity which elements of a transaction you want removed, and it will remove them for you, replacing their values in the log with asterisks. The first three actions all require parameters that you will typically know at configuration time, which means that you will invoke them unconditionally with SecAction. Sanitization works when invoked from any phase, but you should always use phase 5, which is designed for this type of activity.

Use sanitiseArg to prevent the logging of the parameters for which you know the names. For example, let’s assume that you have an application that uses the password, oldPassword, and newPassword parameters to transmit, well, passwords. This is what you’ll do:

SecAction "id:9000,phase:5,pass,nolog,\
    sanitiseArg:password,\
    sanitiseArg:oldPassword,\
    sanitiseArg:newPassword"

Similarly, use sanitiseRequestHeader and sanitiseResponseHeader to remove the contents of the headers for which you know the names. For example, if you have an application that uses HTTP basic authentication, you’ll need the following rule to prevent passwords from being logged:

SecAction "id:9000,phase:5,pass,nolog,\
    sanitiseRequestHeader:Authorization"

The last action, sanitiseMatched, is used when you need to sanitize a parameter for which you don’t know the name in advance. My first example will sanitize the contents of every parameter that has the word password in the name:

SecRule ARGS_NAMES "@rx password" \
    "id:9000,phase:5,pass,nolog,sanitiseMatched"

In the following example, we look for anything that resembles a credit card number and then sanitize it:

SecRule ARGS "@verifyCC \d{13,16}" \
    "id:9000,phase:5,pass,nolog,sanitiseMatched"

Finally, the sanitiseMatchedBytes action can remove the parts of input that contain sensitive information only, rather than entire parameters. This action works only in conjunction with operators that are based on regular expressions (e.g., @rx, @verifyCC, etc.) and further requires the capture action to be specified:

SecRule ARGS "@verifyCC \d{13,16}" \
    "id:9000,phase:5,pass,capture,nolog,sanitiseMatchedBytes"

When further parameters are specified, this new operator can even leave parts of the sensitive data in the log. The following example leaves the first four and the last four digits of a credit card number in the log:

SecRule ARGS "@verifyCC \d{13,16}" \
    "id:9000,phase:5,pass,capture,nolog,sanitiseMatchedBytes:4/4"

Although full HTTP transaction logging sounds good in theory, in practice it’s very difficult to use, because it slows down your server and requires large amounts of storage space. There are ways to get some of the same benefits for a fraction of the cost by using partial logging on demand.

The trick is to tie logging into the tracking of IP addresses, users, or sessions. By default, you’ll log only what’s relevant, but when you spot something suspicious coming from (for example) an IP address, you can change your logging configuration to turn on full logging for the offending IP address only.

To use this functionality, first you need to set up IP address tracking. You do this only once for all your rules, so it should usually be part of your main configuration:

SecAction id:1000,phase:1,pass,nolog,initcol:ip=%{REMOTE_ADDR}

Now, you need to add a rule that will trigger logging when one of the rules in the ruleset matches. We assume that all the rules in the ruleset assign a severity to their alerts. The default value for HIGHEST_SEVERITY is 255. Any value below 255 thus indicates that an alert has occurred; in practice, you might choose one of the real severity values, matching only on serious events. The following rule will start logging everything coming from an IP address after such a single rule match; to achieve that, we set the flag ip.logflag for up to one hour (3,600 seconds):

SecRule HIGHEST_SEVERITY "@lt 4" \
    id:9000,phase:5,pass,nolog,setvar:ip.logflag=1,expirevar:ip.logflag=3600

Finally, we add a rule that detects the flag and forces logging for all the requests from the flagged IP address:

SecRule IP:logflag "@gt 0" \
    id:9001,phase:5,pass,nolog,ctl:auditEngine=On

This chapter, along with the two before it, covered the configuration of ModSecurity. You learned how to install ModSecurity and how to configure it, with special attention given to the logging facilities. Logging deserved its own chapter, because configuring a tool to perform certain actions is often only half of the entire story, with the other half consisting of tracking exactly what happened and why. Further, remote logging is a gateway to other systems, which may assist you in managing ModSecurity.

The next three chapters discuss a new topic: rule writing. You’ll first read an overview of the entire rule language, followed by a tutorial in rule writing, and then a higher-level discussion of how to place ModSecurity configuration within Apache’s own directives. Finally, the interesting bits are here!

[19] EICAR antimalware test file (Retrieved 15 January 2017)