ModSecurity

Special Features

File upload support

ModSecurity is capable of intercepting files uploaded through POST requests and multipart/form-data encoding or (as of 1.9) through PUT requests.

Choosing where to upload files

ModSecurity will always upload files to a temporary directory. You can choose the directory using the SecUploadDir directive:

SecUploadDir /tmp

It is better to choose a private directory for file storage, somewhere only the web server user is allowed access. Otherwise, other server users may be able to access the files uploaded through the web server.

Verifying files

You can choose to execute an external script to verify a file before it is allowed to go through the web server to the application. The SecUploadApproveScript directive enables this function. Like in the following example:

SecUploadApproveScript /full/path/to/the/script.sh

The script will be given one parameter on the command line - the full path to the file being uploaded. It may do with the file whatever it likes. After processing it, it should write the response on the standard output. If the first character of the response is "1" the file will be accepted. Anything else, and the whole request will be rejected. Your script may use the rest of the line to write a more descriptive error message. This message will be stored to the debug log.

Storing uploaded files

You can choose to keep files uploaded through the web server. Simply add the following line to your configuration:

SecUploadKeepFiles On

Files will be stored at a path defined using the SecUploadDir directive. If you want to keep files selectively you can use

SecUploadKeepFiles RelevantOnly

This will keep only those files that belong to requests that are deemed relevant.

Interacting with other daemons

To allow for interaction with other daemons (for example ClamAV, as described later), as of 1.9dev1 files are created with relaxed permissions allowing group read. To do this assuming Apache runs as httpd and daemon as clamav:

# mkdir /tmp/webfiles
# chown httpd:clamav /tmp/webfiles
# chmod 2750 /tmp/webfiles

With this configuration in place, the user clamav will have access to the folder. The same goes for files, which will be created with group ownership clamav. Don't forget to use the SecUploadDir directive to store files in /tmp/webfiles.

Note

If you are keeping files around it might not be safe to leave them there owned by the web server user. For example, if you have PHP running as a module and untrusted users on the server they may be able to access the files. Consider implementing a cron script to make the files only readable by root, and possibly move them to a separate location altogether.

Integration with ClamAV

ModSecurity includes a utility script that allows the file approval mechanism to integrate with the ClamAV virus scanner. This is especially handy to prevent viruses and exploits from entering the web server through file upload.

#!/usr/bin/perl
#
# modsec-clamscan.pl
# mod_security, http://www.modsecurity.org
# Copyright (c) 2002-2004 Ivan Ristic <ivanr@webkreator.com>
#
# $Id: modsecurity-manual.xml,v 1.8.2.6 2006/01/16 12:31:39 ivanr Exp $
#
# This script is an interface between mod_security and its
# ability to intercept files being uploaded through the
# web server, and ClamAV

# by default use the command-line version of ClamAV,
# which is slower but more likely to work out of the
# box
$CLAMSCAN = "/usr/bin/clamscan";

# using ClamAV in daemon mode is faster since the
# anti-virus engine is already running, but you also
# need to configure file permissions to allow ClamAV,
# usually running as a user other than the one Apache
# is running as, to access the files
# $CLAMSCAN = "/usr/bin/clamdscan";

if (@ARGV != 1) {
    print "Usage: modsec-clamscan.pl <filename>\n";
    exit;
}

my ($FILE) = @ARGV;

$cmd = "$CLAMSCAN --stdout --disable-summary $FILE";
$input = `$cmd`;
$input =~ m/^(.+)/;
$error_message = $1;

$output = "0 Unable to parse clamscan output [$1]";

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";

Upload memory limit

Apache 1.x does not offer a proper infrastructure for request interception. It is only possible to intercept requests storing them completely in the operating memory. With Apache 1.x there is a choice to analyse multipart/form-data (file upload) requests in memory or not analyse them at all (selectively turn POST processing off).

With Apache 2.x, however, you can define the amount of memory you want to spend parsing multipart/form-data requests in memory. When a request is larger than the memory you have allowed a temporary file will be used. The default value is 60 KB but the limit can be changed using the SecUploadInMemoryLimit directive:

SecUploadInMemoryLimit 125000

Server identity masking

One technique that often helps slow down and confuse attackers is the web server identity change. Web servers typically send their identity with every HTTP response in the Server header. Apache is particularly helpful here, not only sending its name and full version by default, but it also allows server modules to append their versions too.

To change the identity of the Apache web server you would have to go into the source code, find where the name "Apache" is hard-coded, change it, and recompile the server. The same effect can be achieved using the SecServerSignature directive:

SecServerSignature "Microsoft-IIS/5.0"

It should be noted that although this works quite well, skilled attackers (and tools) may use other techniques to "fingerprint" the web server. For example, default files, error message, ordering of the outgoing headers, the way the server responds to certain requests and similar - can all give away the true identity. I will look into further enhancing the support for identity masking in the future releases of mod_security.

If you change Apache signature but you are annoyed by the strange message in the error log (some modules are still visible - this only affects the error log, from the outside it still works as expected):

[Fri Jun 11 04:02:28 2004] [notice] Microsoft-IIS/5.0 mod_ssl/2.8.12 OpenSSL/0.9.6b \
configured -- resuming normal operations

Then you should re-arrange the modules loading order to allow mod_security to run last, exactly as explained for chrooting.

Note

In order for this directive to work you must leave/set ServerTokens to Full.

When the SecServerSignature directive is used to change the public server signature, ModSecurity will start writing the real signature to the error log, to allow you to identify the web server and the modules used.

[Fri Jun 11 04:02:28 2004] [notice] mod_security/1.9dev1 configured - Apache/2.0.52 \
(Unix) PHP/4.3.10 proxy_html/2.4

Chroot support

Standard approach

ModSecurity includes support for Apache filesystem isolation, or chrooting. Chrooting is a process of confining an application into a special part of the file system, sometimes called a "jail". Once the chroot (short for “change root”) operation is performed, the application can no longer access what lies outside the jail. Only the root user can escape the jail. A vital part of the chrooting process is not allowing anything root related (root processes or root suid binaries) inside the jail. The idea is that if an attacker manages to break in through the web server he won't have much to do because he, too, will be in jail, with no means to escape.

Applications do not have to support chrooting. Any application can be chrooted using the chroot binary. The following line:

chroot /chroot/apache /usr/local/web/bin/apachectl start

will start Apache but only after replacing the file system with what lies beneath /chroot/apache.

Unfortunately, things are not as simple as this. The problem is that applications typically require shared libraries, and various other files and binaries to function properly. So, to make them function you must make copies of required files and make them available inside the jail. This is not an easy task (take a look at http://penguin.epfl.ch/chroot.html for detailed instructions on how to chroot an Apache web server).

The mod_security way

While I was chrooting an Apache the other day I realised that I was bored with the process and I started looking for ways to simplify it. As a result, I built the chrooting functionality into the mod_security module itself, making the whole process less complicated. With ModSecurity under your belt, you only need to add one line to the configuration file:

SecChrootDir /chroot/apache

and your web server will be chrooted successfully.

Apart from simplicity, ModSecurity chrooting brings another advantage. Unlike external chrooting (mentioned previously) ModSecurity chrooting requires no additional files to exist in jail. The chroot call is made after web server initialisation but before forking. Because of this, all shared libraries are already loaded, all web server modules are initialised, and log files are opened. You only need your data in jail.

There are some cases, however, when you will need additional files in jail, and that is if you intend to execute CGI scripts or system binaries. They may have their own file requirements. If you fall within this category then you need to proceed with the external chroot procedure as you normally would.

Note

With Apache 2.x, the default value for the AcceptMutex directive is pthread. Sometimes this setting prevents Apache from working when the chroot functionality is used. Set AcceptMutex to any other setting to overcome this problem (e.g. posixsem).

If you configure chroot to leave log files outside the jail, Apache will have file descriptors pointing to files outside the jail. The chroot mechanism was not initially designed for security and some people fill uneasy about this. Make your own decision. Treat this feature as somewhat experimental.

Note

If your Apache installation uses mod_ssl you will find that it is not possible to leave the logs directory outside the jail when a file-based SSL mutex is used. This is because mod_ssl creates a lock file in the logs directory immediately upon startup, but fails when it cannot find it later. This problem can be avoided by using some other mutex type, for example SSLMutex sem, or by telling mod_ssl to place its file-based mutex in a directory that is inside the jail (using SSLMutex file://path/to/file).

Note

If you are trying to use the chroot feature with a multithreaded Apache installation you may get the folllowing message "libgcc_s.so.1 must be installed for pthread_cancel to work". Add LoadFile /lib/libgcc_s.so.1 to your Apache configuration to fix this problem.

The files used by Apache for authentication must be inside the jail since these files are opened on every request.

Required module ordering for chroot support (Apache 1.x)

Note

This step should not be needed if you intend to leave the log files inside the jail.

As mentioned above, the chroot call must be performed at a specific moment in Apache initialisation, only after all other modules are initialised. This means that ModSecurity must be the first on the list of modules. To ensure that, you will probably need to make some changes to module ordering, using the following configuration directives:

ClearModuleList
AddModule mod_security.c
AddModule ...
AddModule ...
AddModule ...

The first directive clears the list. You must put ModSecurity next, followed by all other modules you intend to use (except http_core.c, which is always automatically added and you do not have to worry about it). You can find out the list of built-in modules by executing the httpd binary with the -l switch:

./httpd -l

Note

If you choose to put the Apache binary and the supporting files outside of jail, you won't be able to use the apachectl graceful and apachectl restart commands anymore. That would require Apache reaching out of the jail, which is not possible. With Apache 2, even the apachectl stop command may not work. For future releases I may create a replacement script as a workaround.

Required module ordering for chroot support (Apache 2.x)

Note

This step should not be needed if you intend to leave the log files inside the jail.

With Apache 2.x you shouldn't need to manually configure module ordering since Apache 2.x already includes support for module ordering internally. ModSecurity uses this feature to tell Apache 2.x when exactly to call it and chroot works (if you're having problems let me know).

There was a change in how the process is started in Apache2. The httpd binary itself now creates the pid file with the process number. Because of this you will need to put Apache in jail at the same folder as outside the jail. Assuming your Apache outside jail is in /usr/local/web/apache and you want jail to be at /chroot you must create a folder /chroot/usr/local/web/apache/logs.

When started, the Apache will create its pid file there (assuming you haven't changed the position of the pid file in the httpd.conf in which case you probably know what you're doing).

A step-by-step chroot guide

If you follow this step-by-step guide you won't even have to bother with the module ordering. First install Apache as you normally would. Here I will assume Apache was installed into /usr/local/apache. I will also assume the jail will be placed at /chroot/apache. It is always a good idea the installation was successful by starting the server and checking it works properly.

# mkdir -p /chroot/apache/usr/local
# cd /usr/local
# mv apache /chroot/apache/usr/local
# ln -s /chroot/apache/usr/local/apache

Now instruct ModSecurity to perform chroot upon startup:

SecChrootDir /chroot/apache

And start Apache:

/usr/local/apache/bin/apachectl startssl

Note

This procedure describes an approach where the Apache files are left inside the jail after chroot takes place. This is the recommended approach because it works every time, and because it is very easy to switch from a non-chrooted Apache to a chrooted one (simply by commenting the SecChrootDir line in the configuration file). It is perfectly possible, however, to create a jail where most of the files are outside. But this is also an option that is more difficult to get right. A good understanding of the chroot mechanism is needed to get it right.

Note

Since version 1.8, if ModSecurity fails to perform chroot for any reason it will prevent the server from starting. If it fails to detect chroot failure during the configuration phase and then detects it at runtime, it will write a message about that in the error log and exit the child. This may not be pretty but it is better than running without a protection of a chroot jail when you think such protection exists.

Performance measurement

In 1.9dev1 I introduced experimental support for performance measurement to the Apache 2 version of ModSecurity. Measuring script performance is sometimes difficult if the clients are on a slow link. Because the response is generated and sent to the client at the same time it is not possible to separate the two. The only way to measure performance is to withhold from sending the response in parts, and only send it when it is generated completely. This is exactly what ModSecurity does anyway (for security purposes) so it makes sense to use it for performance measurement. Three time measurements are performed and data stored the results in Apache notes. All times are given in microseconds relative to the start of request processing:

  • mod_security-time1 - ModSecurity initialisation completed. If the request contains a body the body will have been read by now (provided POST scanning is enabled).

  • mod_security-time2 - ModSecurity completes rule processing. Since we try to execute last, just before request is processed by a handler, this time is roughly the time just before processing begins.

  • mod_security-time3 - response has been generated and is about to be sent to the client.

To use these values in a custom log do this (again, this only works with Apache 2):

CustomLog logs/timer_log "%h %l %u %t \"%r\" %>s %b - \
%<{mod_security-time1}n %<{mod_security-time2}n \
%<{mod_security-time2}n %D"

Each entry in the log will look something like this:

82.70.94.182 - - [19/Nov/2004:11:33:52 +0000] "GET /cgi-bin/modsec-test.pl HTTP/1.1" \
200 1418 - 532 1490 13115 14120

In the example above it took 532 microseconds for processing to reach ModSecurity. ModSecurity used 958 microseconds (1490 - 532) to execute the defined rules, the CGI script generated output in 11625 microseconds (13155 - 1490), and Apache took 965 microseconds to send the response to the client.