mod_security / ModSecurity / ModSec / whatever the kids are calling it today is a battle-tested Web Application Firewall that plugs into the Apache HTTP daemon's modular framework and has been the main mechanism for implementing Intrusion Prevention and DoS mitigation to the LAMP stack for even slightly longer than I've been doing this - way back in the Apache 1.3.x era 19 years ago. If you've never used mod_security but have implemented any sort of IPS/IDS or DoS mitigation technology before your mind's gears have already sputtered "gee I bet that takes a hella lotta resources," buddy - you better believe it. While I would never leave my house and venture into The Wild without smothering myself in a thick lather of mod_security it is prudent to apply it intelligently in situations where available resources, cluster nodes or funding in general is not unlimited. That's why on high-traffic installations you might be enabling and disabling it or at least running radically different configurations with content-appropriate rulesets on a per-vhost basis, separating things like static content away from interpreted scripts and other attack surfaces more susceptible to, say, getting tricked into running Bob's shellcode zero-du-jour.
These days mod_security comes with a lot of rules and that's great (other than the false positives). But as anyone who admins a Snort or Suricata instance knows the more rules the more resources are demanded. PCREs in particular are powerful tools for, well, pattern matching - and that's most of what we're doing in this type of situation. mod_security essentially acts like a proxy, sitting between the end user making a request and the server-side end point that will process it, keeping track of vital statistics so it can judge the intent behind and risk of each request and running pattern match query after query after query on the data going in and coming out. Unfortunately powerful tools are also power hogs, so to prevent mod_security from itself instigating a resource starvation Denial of Service condition default limits on the total number of PCRE operations that can be run on any given transaction are imposed. The default behavior when that limit is reached is to err on the side of caution, instead of processing the transaction mod_security will call the destination up yet provide no input, meanwhile throwing a 500 Internal Server Error to the client. Depending on your configuration it may be very difficult to detect when this particular condition is at fault for your seemingly aborted transaction; often you will have to resort to prying open your Apache error logs, either those configured globally or that which is configured for the vhost at hand.
ModSecurity: Rule 6fa404524850 [id "932105"][file "/var/apache2/template/etc/mod_sec3_CRS/REQUEST-932-APPLICATION-ATTACK-RCE.conf"][line "158"] - Execution error - PCRE limits exceeded (-8): (null). [hostname "ychan.net"] [uri "/post.php"], referer: https://ychan.net/r/
As you can see I recently ran into such a condition with the upload processing script for our imageboard, Ychan (NSFW). I'm already doing a lot of my own security testing inside that script (including several PCREs, in fact), for obvious reasons, so I should be free to do any number of things:
- I can change what mod_security does when it encounters a match by changing the SecRuleEngine Apache configuration directive's value to DetectionOnly:
This will take the teeth out of mod_security's response and only log the match instead of zapping the transaction. Obviously I would want to think long and hard about using this on a production system and then only apply it in a very limited perimeter - for example a specific <Directory>, <Files> or <Location>:
<VirtualHost *:80> ServerName ychan.net ServerAlias www.ychan.net <Location /post.php> SecRuleEngine DetectionOnly </Location> ... </VirtualHost>
- I can raise the limits themselves with the SecPcreMatchLimit and SecPcreMatchLimitRecursion directives:
SecPcreMatchLimit 150000 SecPcreMatchLimitRecursion 150000
- Going by Google, it seems I could add these lines to my php.ini file:
pcre.backtrack_limit = 10000000 pcre.recursion_limit = 10000000
or, where supported, to the relevant .htaccess file:
php_value pcre_backtrack_limit = 10000000 php_value pcre_recursion_limit = 10000000
But there's a problem... as with SecRuleEngine above, I'd like to be able to make these changes to SecPcreMatchLimit and SecPcreMatchLimitRecursion with some specificity, i.e.:
- In my given vhost's configuration block (i.e. /etc/apache2/vhosts.d/mysub_domain_com.conf)
- In the .htaccess file of the relevant directory, where AllowOverride All is in effect
However, according to the documentation, these directives can only be configured globally! That is:
- In (/etc/[apache2|httpd]/conf.d/)httpd.conf
- In (/etc/[apache2|httpd]/modules.d/)(20_)mod_rewrite.conf
This effectively renders about 90% of the articles and answers I have encountered in writing this essentially useless, or misleading at best. What's more, these directives don't even apply in versions 3 and over, but it's not likely that you found this article if that's your situation. Good thing we read the manual around here, right? :D
Back to the solution, it begs the question: what do these values do exactly?
These appear to be settings internal to the PCRE engine in order to limit the maximum amount of memory/time spent on trying to match some text to a pattern. The pcreapi manpage does little to explain it in layman's terms:
The match_limit field provides a means of preventing PCRE from using up a vast amount of resources when running patterns that are not going to match, but which have a very large number of possibilities in their search trees. The classic example is the use of nested unlimited repeats.
Internally, PCRE uses a function called match() which it calls repeatedly (sometimes recursively). The limit set by match_limit is imposed on the number of times this function is called during a match, which has the effect of limiting the amount of backtracking that can take place. For patterns that are not anchored, the count restarts from zero for each position in the subject string.
The default value for the limit can be set when PCRE is built; the default default is 10 million, which handles all but the most extreme cases. You can override the default by suppling pcre_exec() with a pcre_extra block in which match_limit is set, and PCRE_EXTRA_MATCH_LIMIT is set in the flags field. If the limit is exceeded, pcre_exec() returns PCRE_ERROR_MATCHLIMIT.
The match_limit_recursion field is similar to match_limit, but instead of limiting the total number of times that match() is called, it limits the depth of recursion. The recursion depth is a smaller number than the total number of calls, because not all calls to match() are recursive. This limit is of use only if it is set smaller than match_limit.
Since the PCRE library built-in default is 10000000, my guess is that the lower setting is suggested for mod_security in order to prevent requests from being held up for a long time.
In fact, according to the documentation the defaut value for mod_security is 1500 - very low indeed, until we consider that it is expected to process up to several thousands of transactions per second.