1

I'm setting up a (self-hosted) ghost blog, running behind nginx. I've recently added the ModSecurity v3 (aka libModSecurity) WAF with the OWASP CRS v3 ruleset, but I'm having trouble ironing out some false positives.

My problem is very similar to this question, but the suggested solution there doesn't work for me.

The rule that's getting triggered is:

SecRule REQBODY_ERROR "!@eq 0" \
"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2"

I think the cause of the problem is that ghost (rightly or wrongly) is specifying a Content-Type: application/json header for an ajax GET request, to download some config data as Json. The request is blocked because this Content-Type header triggers the JSON parser for the request body:

SecRule REQUEST_HEADERS:Content-Type "application/json" \
     "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"

But, this is a GET request without a request body, so the JSON parser complains that JSON parsing error: parse error: premature EOF... i.e. there's no Json to parse.

After attempting to read umpteen guides, the ModSecurity documentation and many helpful answers on SO from Barry Pollard (e.g. here, here and here), I have tried to write a 'whitelist' rule that's specific to my trigger scenario (rather than disabling the triggered rule globally). Thus, I tried using the following chained rule to exclude the request body from parsing: only for GET requests, to URIs starting with /ghost/ and when they have application/json specified for Content-Type...

SecRule REQUEST_URI "^/ghost/" "phase:1,id:1200,t:none,t:urlDecode,t:lowercase,t:normalizePath,pass,ctl:requestBodyAccess=Off,chain"
    SecRule REQUEST_METHOD "@streq get" "t:none,t:lowercase,chain"
        SecRule REQUEST_HEADERS:Content-Type "application/json" "t:none,t:lowercase"

To my disappointment, this doesn't work - my rule is in the audit log right before the rule 200002 is also logged. In other words, ctl:requestBodyAccess=Off doesn't seem to prevent the JSON parser from getting triggered. Here's the relevant bit of the audit log for a failed transaction:

ModSecurity: Warning. Matched "Operator `Rx' with parameter `application/json' against variable `REQUEST_HEADERS:content-type' (Value: `application/json; charset=UTF-8' ) [file "/etc/nginx/modsec/modsecurity.conf"] [line "8"] [id "1200"] [rev ""] [msg ""] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "xxx.xxx.xxx.xxx"] [uri "/ghost/api/v0.1/configuration/"] [unique_id "152042340176.931426"] [ref "o0,7v4,30t:urlDecode,t:lowercase,t:normalizePathv0,3t:lowercaseo0,16v416,31t:lowercase"]
ModSecurity: Access denied with code 400 (phase 2). Matched "Operator `Eq' with parameter `0' against variable `REQBODY_ERROR' (Value: `1' ) [file "/etc/nginx/modsec/modsecurity.conf"] [line "51"] [id "200002"] [rev ""] [msg "Failed to parse request body."] [data "JSON parsing error: parse error: premature EOF\x0a"] [severity "2"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "xxx.xxx.xxx.xxx"] [uri "/ghost/api/v0.1/configuration/"] [unique_id "152042340176.931426"] [ref "v561,1"]

FWIW, I also tried the simpler rule suggested by Barry for the other, similar, question:

SecRule REQUEST_BODY_LENGTH "@eq 0" "phase:1,id:'12345',ctl:requestBodyAccess=Off"

But that doesn't make a difference either. Being a GET, there's no Content-Length specified in the request headers, so maybe that's why it's failing?

2 Answers2

0

As a workaround, I've managed to squash this particular false positive using the following rule:

SecRule REQUEST_URI "^/ghost/" "phase:1,id:1200,t:none,t:urlDecode,t:lowercase,t:normalizePath,nolog,chain"
    SecRule REQUEST_METHOD "@streq get" "t:none,t:lowercase,chain"
        SecRule REQUEST_HEADERS:Content-Type "application/json" "t:none,t:lowercase, \
           ctl:ruleRemoveById=200002, \
           ctl:ruleRemoveById=920130"

(Rule 920130 is essentially the OWASP CRS equivalent to the SpiderLabs rule 200002.)

However, it seems silly that I need a rule to prevent Json body parser errors from causing rejection of these transactions, where it would surely be more efficient to prevent the body parser from being invoked at all (when I decide)?

If anyone can explain how to make ctl:requestBodyAccess=Off work, I'd be grateful!

0

You tried this:

SecRule REQUEST_URI "^/ghost/" "phase:1,id:1200,t:none,t:urlDecode,t:lowercase,t:normalizePath,pass,ctl:requestBodyAccess=Off,chain"
    SecRule REQUEST_METHOD "@streq get" "t:none,t:lowercase,chain"
        SecRule REQUEST_HEADERS:Content-Type "application/json" "t:none,t:lowercase"

Can you try this instead?

SecRule REQUEST_URI "^/ghost/" "phase:1,id:1200,t:none,t:urlDecode,t:lowercase,t:normalizePath,pass,chain"
    SecRule REQUEST_METHOD "@streq get" "t:none,t:lowercase,chain"
        SecRule REQUEST_HEADERS:Content-Type "application/json" "t:none,t:lowercase, ctl:requestBodyAccess=Off"

I think (though it’s been a while since I looked at this!) that non-disruptive actions like Ctrl should go at the end of the chain, whereas disruptive ones (e.g. pass) should go at the beginning.

But been a while since I looked at this so could be wrong...

Barry Pollard
  • 40,655
  • 7
  • 76
  • 92
  • Thanks Barry, I tried it but unfortunately it makes no difference: the 200002 rule still gets triggered, so the Json body parser gets invoked whether the `ctl:requestBodyAccess=Off` is in the first or last rule of the chain. The triggers for the rule must be OK, otherwise my workaround using `ctl:ruleRemoveById` wouldn't work either, I assume. – GainfulShrimp Mar 08 '18 at 08:50