Speaking of technology

Adventures setting up stuff

Installing Mod Security WAF in Apache Virtualhosts

modsecurity logo

Mod security is a plugin that will enable a so called WAF in Apache. In this case I'll be working on Debian, but this applies to other distributions as well. A Waf is an application level firewall for HTTP that checks the requests from clients and optionally the responses from the server. Mod security tests are based on regular expressions. This also means that it’s important to run Mod security in a log only mode before enabling it in production because it will break your web applications. For installing Mod security do:

root@debian:~#apt-get install libapache2-mod-security2
root@debian:~#apt-get install modsecurity-crs

The latter is the OWASP core rule set for Mod security. You can find more information on OWASP crs here. The first step in configuring Mod security is loading the configuration and pointing to the main config file in /etc/modsecurity.

root@debian:~#vi /etc/apache2/mods-enabled/mod-security.conf

And add the following line if not already present:

<IfModule security2_module>
    SecDataDir /var/cache/modsecurity
        Include "/etc/modsecurity/mod-security.conf"
        Include "/etc/modsecurity/activated_rules/*.conf"
        Include "/etc/modsecurity/modsecurity_crs_99_custom_rules.conf"
</IfModule>

The first Include is the standard config file, the second one is the include of all the symbolic links to the crs rules that you create. The third Include is your custom ruleset, where you can disable certain id's or customize anything you want. We'll dive in to these later on. Do not reload Apache at this point ! First edit:

root@debian:~#vi /etc/modsecurity/modsecurity.conf 

and make sure the following has been configured:

SecRuleEngine DetectionOnly
SecResponseBodyAccess Off
SecAuditEngine RelevantOnly 
SecAuditLog /var/log/modsecurity/modsec_audit.log 

The first line is to make sure that mod_security won't block, but will only detect and log what would be blocked. I don't want to have response body checks in this case, so I set this to off. The third line will make sure only Relevant stuff gets logged and in the fourth line I define where the central log file should be. (I hope I don't need to tell you to create the path etc etc..)

Next we create a symlink in /etc/modsecurity to /usr/share/modsecurity-crs/activated_rules, in this directory we can add symlinks to /usr/share/modsecurity-crs/base_rules to make sure these rules are loaded during apache start or reload. You can simply enable them one by one and check for each the false positives. In the base directory we find the following rulesets:

modsecurity_35_scanners.data
modsecurity_40_generic_attacks.data
modsecurity_41_sql_injection_attacks.data
modsecurity_50_outbound.data
modsecurity_50_outbound_malware.data
modsecurity_crs_20_protocol_violations.conf
modsecurity_crs_21_protocol_anomalies.conf
modsecurity_crs_23_request_limits.conf
modsecurity_crs_30_http_policy.conf
modsecurity_crs_35_bad_robots.conf
modsecurity_crs_40_generic_attacks.conf
modsecurity_crs_41_sql_injection_attacks.conf
modsecurity_crs_41_xss_attacks.conf
modsecurity_crs_42_tight_security.conf
modsecurity_crs_45_trojans.conf
modsecurity_crs_47_common_exceptions.conf
modsecurity_crs_48_local_exceptions.conf.example
modsecurity_crs_49_inbound_blocking.conf
modsecurity_crs_50_outbound.conf
modsecurity_crs_59_outbound_blocking.conf
modsecurity_crs_60_correlation.conf

It is tempting to enable all of them for tight security, but due to the crude behaviour of the regular expression tests and the very nature of each and every application with it’s own often proprietary implementation this is not advised. As an example just enabling all of these rules and just auditing gave me 26GB of logging over a week on a moderate busy Apache installation. Also be aware that for some rule files there is also a data file which you should link as well.

Until now we have the standard installation as most of the people will use it. But I also want to differentiate between Virtualhosts. Some Virtual hosts are applications that only deliver an API with arbitrary data posted by customers and some are web applications with an html frontend. On the API we get a lot of false positives, for instance the cookie checks will trigger a lot of times, and since we are sure cookies are not stored in a sql database, we can disable certain sql injection tests for cookie data. Also customers are using different libraries to connect to the api so the tests that contain protocol violations are not relevant either. Since we do not have forms on the API and authentication for the API is decoupled from the other web applications we do not need to add the xss checks as well.

So we essentially need per Virtualhost configuration. These per Virtualhost stanzas are needed:

SecRuleInheritance Off
SecAuditEngine RelevantOnly
SecAuditLog /var/log/modsecurity/admin.log
SecDebugLogLevel 1
SecRuleEngine DetectionOnly

First we turn off inheritance to make sure we start with a fresh ruleset for this container. Make sure we audit only the rules that will trigger, define where we want log for this vhost, set the debuglevel to 1 and make sure we (for now) only detect and do not block until we have removed all false positives.

Now we can include relevant rules for each Virtualhost and on top of that include a config file with exceptions of the so called individual rule id’s that we want to remove for a context (or Location if you will) within the Virtualhost.

Now if I want to disable specific id’s which are causing false positives we can easily check that in the specific logs of the Virtualhost, for instance an entry like the following:

Message: Access denied with code 403 (phase 2). String match "HTTP/1.1" at REQUEST_PROTOCOL.
 [file "/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_20_protocol_violations.conf"] [line "399"]
 [id "960020"] [rev "2"] [msg "Pragma Header requires Cache-Control Header for HTTP/1.1 requests."] [severity "NOTICE"] 
[ver "OWASP_CRS/2.2.9"] [maturity "6"] [accuracy "8"] [tag "OWASP_CRS/PROTOCOL_VIOLATION/INVALID_HREQ"]
Action: Intercepted (phase 2)
Stopwatch: 1487479951887309 3271 (- - -)
Stopwatch2: 1487479951887309 3271; combined=1862, p1=1341, p2=311, p3=0, p4=0, p5=209, sr=83, sw=1, l=0, gc=0
Producer: ModSecurity for Apache/2.7.3 (http://www.modsecurity.org/); OWASP_CRS/2.2.9.
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.1e-fips mod_wsgi/3.4 Python/2.7.5
Engine-Mode: "ENABLED"

If I know this is a false positive based on the request data (which is ommitted here for brevity). I can create an exception for this is in the Virtualhost stanza, or use a specific config file which I can Include after the crs rules in de Virtualhost, an example would be:

<LocationMatch /mycontext/>
    SecRuleRemoveByID 960020
</LocationMatch>

Obviously we can add multiple id’s in one locationmatch stanza. Or we could even completely disable the rule engine for a specific location within a Virtualhost if we know it's save by doing:

<LocationMatch /savearea>
        SecRuleEngine Off
</LocationMatch>

When we have everything in place we simply run:

root@debian:~#apache2ctl configtest

And if no errors occur we can reload Apache. Now for the first part of this tutorial you might remember that we already loaded all of the files in /etc/modsecurity/activated_rules. You can leave them there loaded for your default host or you can disable them in the DSO config file and load them in your default host vhost file.

What about editing the regular expressions yourself ? This is highly discouraged, when you update the crs package it will obviously break your configuration. If you're like me, I like regex occasionally, but I don't want to drown in it :-)

regex logo
Original can be found at xkcd.com.

Yes it takes a while to configure Mod Security and yes it will help a lot if you understand web application development or even better if you've done it yourself and certainly you need to understand what kind of attacks are around. There is no magic bullet for setting up a WAF. When I used to train for a large Loadbalancer company, my students kept on asking me about the upcoming WAF in the new OS version. Once it was there, most people didn't like it. It was definitely good, but yes it takes more effort than bringing up an interface or setting a route ;-)