We are unsing the ModSecurity-Annotation in our ingress-nginx-controller. We are using the OWAP-Core-Ruleset included with the modsecurity-snipped annotation:
enable-modsecurity: "true"
modsecurity-snippet: |
Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf
We’ve customized several rules and it works well. For some reason our last rule (allowing text/plain) is not working. I guess it’s overwritten at some point. But in don’t know why. It’s the last rule in our snipped-annotation:
SecAction "id:900220, phase:1, pass, t:none, nolog,
setvar:'tx.allowed_request_content_type=|application/x-www-form-urlencoded|multipart/form-data|multipart/related|text/xml|application/xml|application/soap+xml|application/json|application/cloudevents+json|application/cloudevents-batch+json|text/plain|'"
We’ve also tried
SecAction "id:900220, phase:1, pass, t:none, nolog,
setvar:'tx.allowed_request_content_type=|application/x-www-form-urlencoded| |multipart/form-data| |multipart/related| |text/xml| |application/xml| |application/soap+xml| |application/json| |application/cloudevents+json| |application/cloudevents-batch+json| |text/plain|'"
UPDATE
Removing spaces and change to phase 2 doesn’t work either:
SecAction "id:900220,phase:1,pass,t:none,nolog,setvar:'tx.allowed_request_content_type=|application/x-www-form-urlencoded|multipart/form-data|multipart/related|text/xml|application/xml|application/soap+xml|application/json|application/cloudevents+json|application/cloudevents-batch+json|text/plain|'"
2
Answers
Put your rule into phase 2. Variable
tx.allowed_request_content_type
is initialized in phase 1 so your copy of that variable is overwritten by CRS.Also, your rule has wrong syntax:
'
insetvar
actionOWASP CRS Dev on Duty reporting. Firstly, the updated rule you shared is still in phase 1. Was that a copy-paste mistake? If not, can you please change it to
phase:2
and retest, to test what Azurit suggested?Secondly, I think it’s important to clearly state that ModSecurity does not have a default way to meaningfully parse or inspect
text/plain
request data. That is why it’s not allowed by default in CRS any more (we took it out a while ago.) If you allow through request bodies of that type then be aware that you’re opening a door for clients to simply walk around your WAF implementation and avoid inspection.For now, let’s assume you’re happy with all of that. Let’s also assume that you re-test what Azurit suggested and moving the rule to
phase:2
does not resolve your issue.The likely cause of the problem would be the order of operations. The order in which config files are loaded with ModSecurity and CRS is important. If the CRS rule files are loaded first then they’ll notice that the configurable variables are empty and will set sensible default values for them. Probably that’s the underlying issue in some form, that your config is not being loaded in the necessary or expected order.
If in doubt: enable debug logging and then you can see precisely what is actually happening under the hood. Specifically: is your rule 900220 executing before or after rule 901162 in
REQUEST-901-INITIALIZATION.conf
which sets the default value forallowed_request_content_type
? Your rule must come first and then, with a value forallowed_request_content_type
already set, your custom variable will be left alone and used in rule execution.If re-working the order of execution is not possible for some reason then you could also try disabling CRS rule 901162 outright to force the default value to never be set. That’s a last resort option, though, as you don’t really want to make it possible to run without a sensible default value having been set… (Imagine if you or someone else changes the config in the future without accounting for the fact that rule 901162 has been disabled outright…)
Also, if it turns out that the problem is simply that the nginx/Kubernetes config file doesn’t like something about your rule syntax or escaping certain characters then that will become obvious, too. The debug log will state a.) whether your rule 900220 is executing, and b.) what value it is actually setting for
allowed_request_content_type
. Any strangeness should become apparent quite quickly.