Recently, I've spent a lot of time tweaking my ModSecurity configuration to remove some false positives. This tutorial will:
- Explain the the various methods of altering ModSecurity rules starting with the crudest and working up to the more specific techniques
- Give some varied examples of custom rules written for exception handling, with a particular focus on the rules distributed by the OWASP Core Rule Set team.
I am calling the process of removing false positives "whitelisting", but technically I should be calling it "exception handling". However, I think more people looking for this information will find it by searching for "whitelisting".
Basic Whitelisting
My previous articles about ModSecurity have discussed some of the cruder methods of removing false positives. When I was new to ModSecurity, I even wrote a BASH script to create a simple whitelist based on ModSecurity messages in the apache error log. The script made use of the crudest (and easiest) whitelisting technique: removing rules entirely with SecRuleRemoveById
, for example this configuration disables rule 981143:
SecRuleRemoveById 981143
Something that wasn't immediately obvious to me is that these are Apache directives and can go in any Apache configuration file. You can put them inside the main Apache config file or a specific VirtualHost file. You can also put them in a separate file entirely, and Include
them, like this:
# Include personalised whitelist file for ModSecurity Include /etc/modsecurity/whitelists/samhobbs.co.uk.conf
One of the features the BASH script I wrote made use of was the ability to combine SecRuleRemoveById
with Location
and LocationMatch
statements to remove rules at specific locations, e.g.:
<LocationMatch "^/comment/[0-9]+/approve$"> SecRuleRemoveById 981143 </LocationMatch>
The difference between Location and LocationMatch is that the latter supports regular expressions, like in the example above. In addition to SecRuleRemoveById
, there are two other directives available, SecRuleRemoveByMsg
and SecRuleRemoveByTag
. These do the same thing, but match messages and tags instead of rule IDs, which can be especially handy when you want to do something like remove all SQLi rules in the Core Rule Set.
More Specific Whitelisting
The methods of whitelisting above work well enough for simple cases, but if you over-use them you'll soon end up with the whole of your CRS turned off, or broken! The next level of complexity involves some new directives, discussed below.
SecRuleUpdateActionById
The first of these directives is SecRuleUpdateActionById
, which allows us to update the action performed by a rule. The Core Rule Set is best used in anomaly scoring mode, where the complete chain of rules is evaluated during each phase of request processing, and an overall score is generated to decide whether to block the request or not. However, if you are running ModSecurity in traditional mode then the first rule that matches with the block
action will execute the default action, which is normally deny
. In this situation, if you wanted to prevent a rule from causing a request to be denied, but still wanted it to be logged, you could do so like this:
SecRuleUpdateActionById 981143 "pass"
Any of the parameters that are part of the action list in a SecRule
statement can be updated here. If you want to specify a list, you can do it inside a set of quotation marks. The old list is inherited and overwritten by any new parameters you specify, so you don't need to re-type the whole thing. The one exception I have found is chain
- if you are updating a rule that is starting or continuing a chain, you must specify chain
again or the subsequent rules will not be treated as part of the chain.
SecRuleUpdateTargetById, SecRuleUpdateTargetByMsg and SecRuleUpdateTargetByTag
Each ModSecurity rule specifies a list of variables to be tested against the operator (e.g. @rx
, @beginsWith
, @streq
etc.). If you want to add additional variables to the list to be inspected, or remove a particular one that is causing a problem, you can use one of these parameters. The following rule would remove the comment_body
argument from the list inspected by rule 981143:
SecRuleUpdateTargetById 981143 !ARGS:comment_body
Note that the arguments you add will be processed on top of the existing list, so if the original list contained ARGS
and it was updated by the modification above, the complete list would be ARGS !ARGS:comment_body
or in words "all arguments apart from comment body".
Chaining SecRule statements
If you want to use one of the directives above, but only apply it in specific situations, you can create chains of statements. This (made up example) chain removes request cookies from the target list of 981231 at yourdomain.com/admin :
SecRule REQUEST_URI "@beginsWith /admin" \ "chain, \ id:'000001', \ phase:1, \ t:none, \ nolog, \ pass" SecRuleUpdateTargetById 981231 !REQUEST_COOKIES
To create a logical "and", just increase the length of the chain - this extended example only updates the target list if the URL begins with /admin and the IP address of the client is 192.168.1.1
:
SecRule REQUEST_URI "@beginsWith /admin" \ "chain, \ id:'000001', \ phase:1, \ t:none, \ nolog, \ pass" SecRule REMOTE_ADDR "@ipMatch 192.168.1.1" chain SecRuleUpdateTargetById 981231 !ARGS:REQUEST_COOKIES
Whitelisting with "ctl" actions
Although it is possible to amend the rule set selectively by creating chains with SecRuleUpdateTargetById
and similar parameters, these chains can quickly become difficult to read. My preferred method of mitigating false positives is to use the newer ctl
versions of those actions:
- ctl:ruleRemoveById (or Msg or Tag)
- ctl:ruleRemoveTargetById (or Msg or Tag)
- ctl:ruleEngine On|Off|DetectionOnly
I find this method easier on the eye, since everything that starts with SecRule is a condition, and the actions (updating target lists, removing rules etc.) are placed in the action list. If you have two conditions (logical "and") then you have two SecRule statements in the chain, and the ctl action goes in the action list of the last one. The rule below is equivalent to the previous example:
SecRule REQUEST_URI "@beginsWith /admin" \ "chain, \ id:'000001', \ phase:1, \ t:none, \ nolog, \ pass" SecRule REMOTE_ADDR "@ipMatch 192.168.1.1" \ ctl:ruleRemoveTargetById=981173;ARGS:REQUEST_COOKIES
Note that you don't need the !
in front of the argument you want to remove when using ctl:ruleRemoveById
. Remember to put the ctl
actions in the last rule in a chain if you want them to be executed when the whole chain matches - the example code below would result in the target list being updated for all IP addresses, because the action is fired before the @ipMatch
condition is evaluated:
SecRule REQUEST_URI "@beginsWith /admin" \ "chain, \ id:'000001', \ phase:1, \ t:none, \ nolog, \ pass, \ ctl:ruleRemoveTargetById=981173;ARGS:REQUEST_COOKIES" SecRule REMOTE_ADDR "@ipMatch 192.168.1.1"
Since ctl:
actions are evaluated at runtime, these rules should generally go in modsecurity_crs_15_customrules.conf
so they are processed before the rule they are amending.
Examples
This section lists some real world examples of rules I wrote for my setup.
Use a custom error file
No matter how careful and thorough you are when creating your whitelist, it is inevitable that some users will be blocked when trying to do legitimate things. Luckily, it is possible to serve clients a custom error document when they are blocked, which is much less frustrating for them than seeing a standard "500: Internal Server Error" with no explanation of what happened. There are three rules in the CRS that do all the blocking when ModSecurity is deployed in anomaly scoring mode. First, update the actions of these rules in modsecurity_crs_60_customrules.conf
:
# update action for inbound blocking rules in modsecurity_crs_49_inbound_blocking.conf # so that they use the Security Error script SecRuleUpdateActionById 981175 "chain,deny,status:501" SecRuleUpdateActionById 981176 "chain,deny,status:501" # also update the outbound blocking rules SecRuleUpdateActionById 981200 "chain,deny,status:501"
Next, tell Apache to serve a custom error page for error 501:
ErrorDocument 501 /security-error.php
This directive could go in the same rule file, or if you want to serve different pages for different domains (e.g. with different contact details), you could place it in your VirtualHost file instead. The path is relative to the DocumentRoot
defined in the VirtualHost; my site files are stored in /var/www/samhobbs
so I created the custom error page at /var/www/samhobbs/security-error.php
. Here's my file:
<?php header("HTTP/1.0 403 Forbidden"); ?> <html> <head> <title>Security Error</title> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> </head> <body> <h1>Security Error</h1> <pre> Your request has been blocked for security reasons. Please try again in a few minutes. Should the problem persist, please email me (see the contact page for details) with the following unique ID, which will help to identify your request: <?php echo htmlspecialchars($_SERVER["REDIRECT_UNIQUE_ID"], ENT_QUOTES, 'UTF-8'); ?> Sorry for any inconvenience. <!-- This comment is here to increase the page size and prevent Internet Explorer from masking the message. More information is available at the following address: http://support.microsoft.com/default.aspx?scid=kb;en-us;Q294807 --> </body> </html>
You'll notice that the start of the script sets the status back to 403
- the 501
status is just for internal use/convenience (you only want to serve this page for security errors, not for actual internal server errors as well). This file has to be readable by Apache, but it does not have to be executable. Mine has ownership root:root
and permissions 644
. You will notice that the variables ModSecurity uses for processing rules are made available to the PHP script, prefixed with REDIRECT_
, so REDIRECT_UNIQUE_ID
is the unique ID that appears in the Apache logs. If someone sends you that ID, it's simple to look it up in the audit log and determine why the request was blocked. You can see what this page looks like to users by clicking this link: https://samhobbs.co.uk/?test=test-attack The rule causing the request to be blocked when you click that URL is this:
SecRule ARGS "test-attack"\ "id:'000001', \ phase:1, \ log, \ deny, \ status:501"
This is one of the many cool tips I discovered by reading the ModSecurity handbook, although I had to modify the example slightly as the HTTP response codes allowed by default in apache2 seem to have changed since the book was written (the book suggests using 509, but the configuration checking tool throws errors if you try and use that code). I also had to add php tags to the script.
Turn off denial of service counter for a specific URI or path
The denial of service rules in modsecurity_crs_11_dos_protection.conf
count the number of requests from each IP address and block new requests when the limit has been exceeded. The rule that does the blocking is 981045
:
# Block and track # of requests but don't log SecRule IP:DOS_BLOCK "@eq 1" "phase:1,id:'981045',t:none,drop,nolog,setvar:ip.dos_block_counter=+1"
This rule checks to see if the ip.dos_block
is set, but since it doesn't actually set it itself, removing the rule at a specific URL will prevent the client from being blocked for part of the site, but it will be blocked everywhere else. Not a good solution. Here is the rule that counts the number of requests:
# # DOS Counter # Count the number of requests to non-static resoures # SecRule REQUEST_BASENAME "!\.(jpe?g|png|gif|js|css|ico)$" "phase:5,id:'981047',t:none,nolog,pass,setvar:ip.dos_counter=+1"
Removing this rule on parts of the site where you expect lots of requests (like ownCloud) prevents the IP block from being created in the first place:
SecRule REQUEST_URI "@beginsWith /owncloud" \ "id:'000013', \ phase:5, \ t:none, \ nolog, \ pass, \ ctl:ruleRemoveById=981047"
This rule needs to be processed before the file modsecurity_crs_11_dos_protection.conf
, so place it in a new file named modsecurity_crs_10_customrules.conf
.
Enable extra HTTP request methods and content types for specific locations
The CRS sets the allowed HTTP request methods in modsecurity_crs_10_setup.conf
rule 900012
, and enforces them in file modsecurity_crs_30_http_policy.conf
, rule 960032
. Here is the relevant section from the setup file:
SecAction \ "id:'900012', \ phase:1, \ t:none, \ setvar:'tx.allowed_methods=GET HEAD POST OPTIONS', \ setvar:'tx.allowed_request_content_type=application/x-www-form-urlencoded|multipart/form-data|text/xml|application/xml|application/x-amf|application/json', \ setvar:'tx.allowed_http_versions=HTTP/0.9 HTTP/1.0 HTTP/1.1', \ setvar:'tx.restricted_extensions=.asa/ .asax/ .ascx/ .axd/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .resources/ .resx/ .sql/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/', \ setvar:'tx.restricted_headers=/Proxy-Connection/ /Lock-Token/ /Content-Range/ /Translate/ /via/ /if/', \ nolog, \ pass"
The methods GET, HEAD, POST, and OPTIONS are usually enough for something like a simple blog, and disabling the less well-known methods is probably quite a good idea, because legitimate users won't be using them (bots might, in an attempt to get your web app to react in a non-standard way and expose some vulnerability or sensitive information). However, if you run ownCloud or a similar service, you will find that calDAV, cardDAV and webDAV together use the methods PROPFIND, REPORT, PUT and MKCOL, which you need to enable to avoid these requests from being blocked by ModSecurity. The same is true for standard content types: most requests will be fine with the defaults, but I've noticed ModSecurity uses text/calendar
for calDAV and application/octet-stream
for webDAV. The following rule will enable these requests by overriding the allowed methods and content types for ownCloud transactions, without allowing them globally:
SecRule REQUEST_URI "@beginsWith /owncloud/remote.php" \ "id:'000002', \ phase:1, \ t:none, \ setvar:'tx.allowed_methods=GET HEAD POST OPTIONS PROPFIND REPORT PUT MKCOL', \ setvar:'tx.allowed_request_content_type=application/x-www-form-urlencoded|multipart/form-data|text/xml|application/xml|application/x-amf|application/json|application/octet-stream|text/calendar', \ nolog, \ pass"
I put this rule in modsecurity_crs_15_customrules.conf
.
Allow missing accept header for a specific location
Pretty much every browser will send an "accept" header to the server to communicate which content types it is happy to receive. For this reason, requests without accept headers are protocol anomalies (not strictly a violation, but a good sign it's not a genuine request) and are flagged by the rules in modsecurity_crs_21_protocol_anomalies.conf
. However, you may find that certain programs (particularly ones that send lots of requests like sync clients) omit the accept
header. This rule will remove the rule check for ownCloud:
SecRule REQUEST_URI "@beginsWith /owncloud" \ "id:'000005', \ phase:2, \ t:none, \ ctl:ruleRemoveById=960015, \ nolog, \ pass"
Fight comment spam with hidden fields
There are modules that achieve something similar to this for Drupal and probably WordPress: a field is added to comment forms and hidden with CSS. Humans don't see it but bots do, and the dumb ones fill it in causing the comment to be blocked. This rule will block those requests before they are even seen by the web app, which is preferable (some of these bots are carrying out SQLi probing attacks).
SecRule ARGS:feed_me "!@rx ^$" \ "id:'000023', \ phase:2, \ log, \ t:none, \ block, \ logdata:'SPAM comment detected - hidden filled in comment form', \ msg:'SPAM comment detected - hidden field filled in comment form', \ severity:'2', \ setvar:'tx.msg=%{rule.msg}', \ setvar:tx.anomaly_score=+20, \ setvar:tx.%{rule.id}-CUSTOM_RULE/COMMENT_SPAM"
The regex matches "not empty". This one goes in modsecurity_crs_15_customrules.conf
.
Turn on the XML processor for CalDAV/CardDAV/WebDAV requests
The CRS has some rules that enable the XML processor for standard XML content types, but many *DAV requests have custom content types and therefore don't trigger these rules. If ModSecurity inspects an XML request body without processing it with the XML processor, it will spew out a bunch of false positives due to all of the <
and >
characters (normally you get a "number of special characters exceeded" message), and there's a good chance the request will be blocked. Turning on the XML request body processor will ensure the XML is valid (this check is done in both the CRS and the ModSecurity setup file, so you don't need to add anything), and it will also strip the XML tags leaving just the data, which is run against the other rules.
# turn on the XML processor for webdav and caldav requests SecRule REQUEST_URI "@rx ^/owncloud/remote.php/(webdav|caldav|carddav)" \ "chain,id:'000090',phase:1,t:none,t:lowercase,pass,nolog" SecRule REQUEST_METHOD "@rx (PROPFIND|REPORT)" \ "ctl:requestBodyProcessor=XML"
That rule should be placed in modsecurity_crs_15_customrules.conf
.
Log all comments, not just ones with high anomaly scores
If there's a specific type of request that you always want to log regardless of whether the request matched any rules or not, you can force ModSecurity to log it. In my case, I wanted a record of all comments posted on the site:
# log all comments to auditlog SecRule REQUEST_METHOD "@streq post" \ "chain, \ id:'000081', \ phase:1, \ t:none, \ t:lowercase, \ t:normalisePath, \ msg:'All comments logged to auditlog'" SecRule REQUEST_URI "@rx ^(/comment/reply(/\d+)*|/comment/\d+/edit)$" \ "setvar:'tx.msg=%{rule.msg}', \ nolog,auditlog
A couple of things worth noting here: be careful when using transformation functions. At first when I wrote this rule, it started SecRule REQUEST_METHOD "@streq POST"
, but after transforming the data to lowercase the request method is no longer POST
, it's post
, so the rule didn't match. Now I have a log of all the legitimate comments on the site as well as the ones that tripped the CRS rules.
Missing HTTP only flag in cookies
You will find that you get lots of misconfiguration messages that fill up your audit log if you haven't configured your site to set the httponly
flag in your cookies. This isn't a modsecurity rule, but it's relevant so I decided to include it. You can solve this issue by changing a parameter in /etc/php5/apache2/php.ini
:
session.cookie_httponly = 1
And you should no longer see the messages.
Inspect specific requests with the debug log
If your rules aren't working in the way you expected them to, you might want to see detailed information about how modsecurity is processing the relevant requests in the debug log. However, I quickly learned that you don't want to enable debug logging globally because the amount of output produced is huge! Instead, you can turn it on for specific requests (e.g. comments):
# turn on debug logging for comments SecRule REQUEST_METHOD "@eq POST" id:'000025',phase:1,chain,t:none,nolog SecRule REQUEST_URI "@rx ^(/comment/reply(/\d+)*|/comment/\d+/edit)$" \ log,ctl:debugLogLevel=5
This rule goes in modsecurity_crs_15_customrules.conf
.
Remove parameter names from the target list for a specific rule
I wrote this rule for my brother's WordPress site, which is set up to make changes using FTP. The argument names for the login data was triggering the special character limit, so I made an exception for them:
SecRule REQUEST_URI "@beginsWith /authorize.php" \ "id:'000028', \ phase:2, \ t:none, \ ctl:ruleRemoveTargetById=981173;ARGS_NAMES:connection_settings[ftp][username], \ ctl:ruleRemoveTargetById=981173;ARGS_NAMES:connection_settings[ftp][password], \ ctl:ruleRemoveTargetById=981173;ARGS_NAMES:connection_settings[ftp][advanced][hostname], \ ctl:ruleRemoveTargetById=981173;ARGS_NAMES:connection_settings[ftp][advanced][port], \ nolog, \ pass"
Again, this rule goes in modsecurity_crs_15_customrules.conf
. I could also have used a regular expression to match all of the argument names.
Remove a group of rules for a specific field (e.g. free-form fields like comment boxes)
The place you're likely to get most false positives is free-form text fields, for example comment bodies. This rule makes use of the fact that the CRS tags rules by type (e.g. SQL injection), which allows us to update the target list for all SQLi and XSS rules so that it excludes the comment_body field:
SecRule REQUEST_URI "@rx ^(/comment/reply(/\d+)*|/comment/\d+/edit)$" \ "id:'000029', \ phase:2, \ t:none, \ ctl:ruleRemoveTargetByTag=OWASP_CRS/WEB_ATTACK/SQL_INJECTION;ARGS:comment_body[und][0][value], \ ctl:ruleRemoveTargetByTag=OWASP_CRS/WEB_ATTACK/XSS;ARGS:comment_body[und][0][value], \ nolog, \ pass"
This rule goes in modsecurity_crs_15_customrules.conf
.
Put the rule engine in DetectionOnly mode for new apps or a specific path
Initially, your whole ModSecurity installation is likely to be in DetectionOnly
mode while you remove false positives. However, you may want to add a new web app to Apache after you have done your initial whitelisting exercise and turned the engine On
. In this case it's much better to put the engine in DetectionOnly
mode for just the new web app, leaving the engine On
for the rest of your site while you do initial testing. You can then comment the rule when you're done.
SecRule REQUEST_URI "@beginsWith /webapp" \ "id:'000080', \ phase:1, \ t:none, \ ctl:ruleEngine=DetectionOnly, \ nolog, \ pass"
Likewise, you may want to turn the rule engine off for the admin backend for your site:
SecRule REQUEST_URI "@beginsWith /admin" \ "id:'000003', \ phase:1, \ t:none, \ ctl:RuleEngine=Off, \ nolog, \ pass"
These rules go in modsecurity_crs_15_customrules.conf
.
Allow multiple URL encoding in comments
One of the protocol violation rules for the CRS was being triggered for URLs submitted in the body of node edits for Drupal, especially when the text was different to the URL like this:
<a href="https://samhobbs.co.uk">my website</a>
...which is something I do quite a lot, so I removed it for the specific field:
SecRule REQUEST_URI "@beginsWith /node" \ "id:'000006', \ phase:2, \ t:none, \ ctl:ruleRemoveTargetById=950109;ARGS:body, \ nolog, \ pass"
I put that rule in modsecurity_crs_60_customrules.conf
.
Increase the anomaly score threshold for blocking specific requests
The default inbound_anomaly_score_level
(the value compared to the transaction score to decide whether to block a request) is set in modsecurity_crs_10_setup.conf
with a value of 5
. I wanted to raise the threshold for comments to reduce the chances of false positives blocking people from commenting:
SecRule REQUEST_URI "@rx ^(/comment/reply(/\d+)*|/comment/\d+/edit)$" \ "id:'000014', \ phase:1, \ t:none, \ nolog, \ pass, \ setvar:'tx.inbound_anomaly_score_level=10'"
This rule goes in modsecurity_crs_60_customrules.conf
.
Remove rules matching common shell commands
The file modsecurity_crs_40_generic_attacks.conf
contains rules that look for common shell commands embedded in requests. For most blogs these rules are useful, but since I'm expecting users to post commands in comments on this site, I removed the comment body from the target list. For example, rule 950907 matches wget
, curl
and cc
:
SecRuleUpdateTargetById 950907 !ARGS:/^comment_body/
The regex matches any argument beginning with comment_body
, e.g. comment_body[und][0][value]
. This rule goes in modsecurity_crs_15_customrules.conf
.
Allow multiple linefeeds in comments
Rule 960024 looks for four (4) non-word characters in a row. Two linefeeds (\r\n
) in a row looks like (\x0d\x0a\x0d\x0a
) and triggers the rule. For this reason you may want to remove it from comment fields:
SecRuleUpdateTargetById 960024 !ARGS:/^comment_body/
Argument names tripping special character limits
Rule 981173
checks for too many special characters and on Drupal sites may be triggered by the names of comment fields, e.g. ARGS_NAMES:comment_body[und][0][value]
and ARGS_NAMES:comment_body[und][0][format]
. This rule will remove the names of arguments starting with comment_body
from that specific rule:
SecRuleUpdateTargetById 981173 !ARGS_NAMES:/^comment_body/
This rule goes in modsecurity_crs_60_customrules.conf
.
Notes, References and Tools
If you want to review a large volume of data at once, you might find my commandline utility for reading a modsecurity audit log file into a sqlite database useful. I started out by running ModSecurity in DetectionOnly
mode for a few months, which produced a huge amount of data in the audit logs. Reading it all into a database made it vastly easier to sort and get a feel for which false positives to prioritise. The ModSecurity reference manual is hosted on the project's github page. If you don't have the ModSecurity Handbook, I would highly recommend it. It is available directly from the publisher, in paper and ebook formats often cheaper than Amazon. Quite a cool company really, if you buy a paper copy you get the ebook thrown in (including subsequent revisions if the book is updated). In the variables list, &VARIABLE
means the number of that variable, e.g. &REQUEST_HEADERS
is the number of request headers. When writing rules for ModSecurity that are intended to increase an anomaly score, be aware that rule blocking is part of a complicated chain. One of the tests requires there to be a transaction variable that starts with a rule ID number, so you need to add something similar to this snippet to your rules if you want them to match:
setvar:tx.%{rule.id}-CUSTOM_RULE/COMMENT_SPAM
Hopefully you will find these rules useful. If you have any questions or spot a mistake, please leave a comment. Hopefully you won't be blocked :p
Comments
How do I overwrite rule 960032?
Hi there,
I have recently started implementing mod_security and have run into the issue that a "PUT /api/" request throws up rule 960032:
PUT /api/lists/stuff/subscribers/id123 HTTP/1.1
Message: Access denied with code 403 (phase 1). Match of "within %{tx.allowed_methods}" against "REQUEST_METHOD" required. [file "/etc/httpd/crs-ruleset/owasp-modsecurity-crs/base_rules/modsecurity_crs_30_http_policy.conf"] [line "31"] [id "960032"] [rev "2"] [msg "Method is not allowed by policy"] [data "PUT"] [severity "CRITICAL"] [ver "OWASP_CRS/2.2.9"] [maturity "9"] [accuracy "9"] [tag "OWASP_CRS/POLICY/METHOD_NOT_ALLOWED"] [tag "WASCTC/WASC-15"] [tag "OWASP_TOP_10/A6"] [tag "OWASP_AppSensor/RE1"] [tag "PCI/12.1"]
Action: Intercepted (phase 1)
Stopwatch: 1449836127922164 1129 (- - -)
Stopwatch2: 1449836127922164 1129; combined=314, p1=273, p2=0, p3=0, p4=0, p5=41, sr=67, sw=0, l=0, gc=0
Producer: ModSecurity for Apache/2.7.3 (http://www.modsecurity.org/); OWASP_CRS/2.2.9.
Server: Apache
Engine-Mode: "ENABLED"
In my HTTP config I have the inclusion of the OWASP rules and then my custom overwrite-rules:
Include /etc/httpd/crs-ruleset/owasp-modsecurity-crs/modsecurity_crs_10_setup.conf
Include /etc/httpd/crs-ruleset/owasp-modsecurity-crs/base_rules/*.conf
...
# my rules
SecRuleRemoveById 960032 # works but I ideally want to remove it only for URI /api
# This does not work?
SecRule REQUEST_URI "@beginsWith /api" "id:'000005',phase:1,t:none,t:lowercase,ctl:ruleRemoveById=960032,nolog,pass"
Change the list of allowed methods for that URI instead
ctl:ruleRemoveById
is triggered at runtime so it should be specified before the rule it is disabling. In my setup, modsecurity loads files from the/etc/modsecurity
directory. The default rules are symlinked there, and the filenames look like this: Since the rule you are removing is inmodsecurity_crs_30_http_policy.conf
, the custom rule to remove it would have to be read before that file by apache (e.g. you could usemodsecurity_crs_15_customrules.conf
). Instead of removing the rule, I think you should add PUT to the list of allowed methods at that location. Have a look at the example "Enable extra HTTP request methods and content types for specific locations". You could do something like: SamThanks for the help - How do I overwrite rule 960032?
Thank you so much. My config is slightly different - i.e. as part of my Apache config, I include a separate config file which has our virtuals defined, in there I have a global section and I just needed to switch the rule before the base includes:
Include /etc/httpd/crs-ruleset/owasp-modsecurity-crs/modsecurity_crs_10_setup.conf
# Allow API access - see https://samhobbs.co.uk/2015/09/example-whitelisting-rules-apache-modsecurity-and-owasp-core-rule-set
# Needs to be before other rules
SecRule REQUEST_URI "@beginsWith /api" "id:'001000',phase:1,t:none,t:lowercase,ctl:ruleRemoveById=960032,nolog,pass"
Include /etc/httpd/crs-ruleset/owasp-modsecurity-crs/base_rules/*.conf
# Use OWASP CRS to detect & block malicious attacks
SecRuleEngine On
Cool, glad it's working now.
<Location>
blocks! I still think you should consider updating the allowed methods to include PUT instead of removing that rule altogether, at the moment you are allowing all kinds of other methods (PROPFIND, REPORT, PUT, MKCOL etc.) through to the web app if the URI matches. SamMy sits outside the
My sits outside the VirtualHost definition and the rules I customise are global to all virtuals. I sofar did not have the need to provide custom rules within a VirtualHost.
I do have a SecRule further down in my config where I limit the methods based on the request URI. Initially I hoped that I could just expand the allowed methods, but I also did not want to make changes to files within OWASP files.
You don't have to change the CRS file directly
Generic rules
Hi,
Have anyone tested these modsecurity rules ? https://malware.expert
Regards,
Jani
blocking URL
Hi Sam,
I tried blocking the uri using ARGS (https://samhobbs.co.uk/?test=test-attack) and its working fine. Thanks.
I wanted to know how to block absolute URL, please let me know.
i tried several ways like HTTP_REFERER, REQUEST_URI and REQUEST_RAW_URI but nothing worked.
Regards,
Muruli
This should work for the URI,
Hi Sam,
Hi Sam,
Thanks for the reply.
I tried it and it is blocking everything in my app path. i want it to block only specific uri like any path to html file or php file, etc.
Hi Sam,
Hi Sam,
i gave "/test/test.html" then it is blocking and working as expected.
but If i give http://ipaddress/test/test.html, then it is allowing.
Please help me in resolving this, i want it in both ways.
Try using @streq
Hi Sam,
Hi Sam,
Thanks for your kind reply.
I tried with @streq but no luck. Anyways without https:// it is working fine.
is it a valid request to mention https://IpAddress/path in modsecurity conf files to block ?
Check modsecurity is on in both virtualhosts
https://foo.com/bar
andhttp://1.2.3.4/bar
should both match"REQUEST_URI @streq /bar"
(see the documentation for REQUEST_URI). As the docs say, no transformations are done to prevent evasion by default, so you could use the transformations in their example to prevent people from evading the rule: How have you enabled modsecurity? If you've done it in your virtualhost configuration, make sure you have the same config for HTTP and HTTPS (is modsecurity enabled on one and disabled on the other?). SamHi Sam,
Hi Sam,
I have configured apache to ssl and enabled modsecurity for it.
As i mentioned earlier if i do not mention ipaddress then it is blocking perfectly.
Also as per documentation, if domain name is provided on the request line we have to use REEQUEST_URI_RAW. i tired this option also but still no luck.
let me know if you have used it anywhere.
Thanks
Don't use REQUEST_URI_RAW
making whitelist uri
Hi Sam,
I can list out the url in custom rules file to block url which essentially becomes like a black listing.
In order to make a white list which allows only mentioned url. What are the changes required in custom rules file ?
Hi Sam,
Hi Sam,
Great read.
I have a question - how to remove certain IP from blacklist/ban ?
For example with a request like www.mysite.com/remove_from_blacklist?ip=0.0.0.1. ?
Thanks
requests are all evaluated individually
Whitelisting Port #
Hi Sam,
Is it possible to have port # whitelist so that no one can access the webserver in other ports. This is the case if we are using proxy server so I tried "SecRule SERVER_PORT "^80$" "id:69, deny, status:403" but still i am able to access the original server with redirected port.
Please let me know if i can block the other ports using Modsecurity rule.
Do it in the virtualhost file
Thanks for these understandable examples
Hi Sam,
Thank you very much for all these wonderful examples and the understandable descriptions.
They helped me so much getting mod_security work as expected.
Guenter
No problem! It took me a
multiple urls
I was wondering if there is a way to use "SecRule REQUEST_URI "@beginsWith " with more then one Url/
I tried separating them with a coma like I could with IPMatch to white list a couple of IP's but that didn't work.
Trying to to it for something like /admin and /something/control.
Thanks
Use regular expression rx
Thanks for the reply. I'm a
Thanks for the reply. I'm a dork. I was trying to add multiple rules and breaking apache which is why I asked what I asked. I then realized my rules all has the same id: which is why it was breaking things. Once they had unique id's, everything worked like I thought they should.
Thanks again.
Add new comment