ModSecurity is capable of intercepting files uploaded through
POST
requests and
multipart/form-data
encoding or (as of 1.9) through
PUT
requests.
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.
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.
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.
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.
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.
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.13 2006/04/10 12:35:37 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";
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
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.
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
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 (in most cases, there are some circumstances when even non-root users can escape too, but only on an improperly configured 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. (I covered the process in detail in my book, Apache Security. The chapter that covers chroot is available for free at http://www.apachesecurity.net).
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.
The internal chroot functionality provided by ModSecurity works great for simple setups. One example of a simple setup is Apache serving static files only, or running scripts using modules. For more complex setups you should consider building a jail the old-fashioned way.
The internal chroot feature should be treated as somewhat experimental. Due to the large number of default and third-party modules available for the Apache web server, it is not possible to verify the internal chroot works reliably with all of them. You are advised to think about your option and make your own decision. In particular, if you are using any of the modules that fork in the module initialisation phase (e.g. mod_fastcgi, mod_fcgid, mod_cgid), you are advised to examine each Apache process and observe its current working directory, process root, and the list of open files.
What follows is a list of facts about the internal chroot functionality for you to consider before making the decision:
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 files in the jail.
To create new processes from within jail your either need to use statically-compiled binaries or place shared libraries in the jail too.
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.
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
).
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.
Certain modules (e.g. mod_fastcgi
,
mod_fcgi
, mod_cgid
) fork in
the module initialisation phase. If they fork before chroot takes
place they create a process that lives outside jail. In this case
ModSecurity must be configured to initialise after most modules
but before the modules that fork. This is a manual process with
Apache 1.3.x. It is an automated process with Apache 2.x since
ModSecurity 1.9.3.
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
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.
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).
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
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.
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.
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 - %{UNIQUE_ID}e \ %<{mod_security-time1}n %<{mod_security-time2}n \ %<{mod_security-time3}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.