owasp-modsecurity/ModSecurity-nginx

Questions: Process to properly introduce modsecurity in an already running cluster

Closed this issue · 7 comments

Hi,

Hopefully this is the right repo (but could be https://github.com/owasp-modsecurity/ModSecurity/tree/v3/master)

I'm searching for a blog/documentation on the process to bring modsecurity to an already running system without causing any disruption.
Ingress-Nginx helm allows to almost transparently enable mod-security: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#modsecurity-snippet

But as the following recommended setting is showing: https://github.com/owasp-modsecurity/ModSecurity/blob/v3/master/modsecurity.conf-recommended#L7 start with DetectionOnly then turn it on.

Now I want to discuss the process between the 2 stages.

My initial thought process was:

  1. Enable detection only
  2. Review what would be blocked
  3. Introduce skip rules when they are false positive
  4. Continue reviewing what would be blocked, ignoring the ones skipped already
  5. Enable Enforce On

First issue, while in detection only we found that SecAuditLogParts ABIJDEFHZ was printing too much details including body token/password. So we restricted to SecAuditLogParts AHKZ but now the log doesn't show the actual domain but only the path. Is that expected? Is there an alternative?

In detection only, nothing should be blocked thus if a rule was to be blocked when turned on it would still be a 200 right? Thus if setting SecAuditLogRelevantStatus "^(?:5|4(?!04))" wouldn't that make only print the blocked queries from other means?

Step 4, "reviewing what would still be blocked but ignoring the ones skipped" seems tricky. In Detection Only everything returns 200 and skipped rules don't seem to apply. Am I wrong here? Is there a flag in detection only that could show the rule would be skipped in non detection only?

Happy to get your input or be sent to a full breakdown on how one brought this live

Hi @Fran-Rg,

Hopefully this is the right repo (but could be https://github.com/owasp-modsecurity/ModSecurity/tree/v3/master)

Yes, I think this question would be better there. But never mind.

First issue, while in detection only we found that SecAuditLogParts ABIJDEFHZ was printing too much details including body token/password. So we restricted to SecAuditLogParts AHKZ but now the log doesn't show the actual domain but only the path. Is that expected? Is there an alternative?

Unfortunately actually there isn't.

Unfortunately libmodsecurity3 does not support sanitisearg and similar actions, so you can't hide sensitive information in logs.

(Note, that v2 supports that).

In detection only, nothing should be blocked thus if a rule was to be blocked when turned on it would still be a 200 right? Thus if setting SecAuditLogRelevantStatus "^(?:5|4(?!04))" wouldn't that make only print the blocked queries from other means?

That's a really good question. Now I checked the code to find the correct answer, but it seems like it follows some completely different way as it documented.

I think here the intention was that the engine checks these conditions:

  • transactionAuditLogStatus == RelevantOnlyAuditLogStatus - check SecAuditEngine setting
  • this->isRelevant(transaction->m_httpCodeReturned) == false - that you asked above
  • saveAnyway == false - check if any rule message has noauditlog action; if any of that does not have, then this condition will be True

All three conditions must be true (which means the second and third variables must be false) to skip generating audit log.

Because this is not true, then no matter what's the status...

So thanks for bringing up this, I'm going to write this bug beside the others...

Step 4, "reviewing what would still be blocked but ignoring the ones skipped" seems tricky. In Detection Only everything returns 200 and skipped rules don't seem to apply. Am I wrong here? Is there a flag in detection only that could show the rule would be skipped in non detection only?

Section H contains the triggered rules. Based on those you can create your own exclusion.

Please be careful, if you use CRS then there are few important rules that you mustn't exclude! These are: 949nnn, 959nnn, 980nnn.

I found this: https://github.com/owasp-modsecurity/ModSecurity/wiki/ModSecurity-Frequently-Asked-Questions-%28FAQ%29#how-do-i-handle-false-positives-and-creating-custom-rules but the link doesn't work anymore

Thanks too. I'm going to fix that (but honestly I don't know what was the content and what did the author think in general, so I have no idea what's a good link to replace this).

Anyway, if you use CRS you can find here a pretty good documentation.

The CRS documentation on false positives is down to the point. My own tutorials at netnea.com about false positive handling (look at the apache version, the nginx one is outdated) actually bring a proven methodology to do this in an interative way.

Section H contains the triggered rules. Based on those you can create your own exclusion.

Once a rule is skipped, should it appear in the logs in detection only? I can double check but It didn't feel like it

Hi @Fran-Rg,

sorry, I re-read your first comment again, and

"reviewing what would still be blocked but ignoring the ones skipped" seems tricky. In Detection Only everything returns 200 and skipped rules don't seem to apply. Am I wrong here? Is there a flag in detection only that could show the rule would be skipped in non detection only?

There isn't any flag that could show you your engine is in DetectionOnly mode. What you see in logs in this mode that you have to analyze by eye/hand.

Once a rule is skipped, should it appear in the logs in detection only? I can double check but It didn't feel like it

Sorry, could you clarify this again? I mean if a rule is skipped, then it does not executed, therefore it mustn't appear in the log. Or I don't see something...

Sorry, could you clarify this again? I mean if a rule is skipped, then it does not executed, therefore it mustn't appear in the log. Or I don't see something...

We have the following config:

# skip rule 932235 if request uri has suite/framework and host appian.myhost.com
SecRule REQUEST_URI "@contains suite/framework"
  "id:1000013,phase:1,pass,t:none,nolog,ctl:ruleRemoveById=932235,ctl:ruleRemoveById=942190,chain"
SecRule REQUEST_HEADERS:Host "@contains appian.myhost.com" "t:none"

Yet in our logs we see the following:

{
    "transaction":{
       "client_ip":"130.x.x.x",
       "time_stamp":"Mon Mar 17 11:35:27 2025",
       "server_id":"6790d2559be555fa480e9a",
       "client_port":56420,
       "host_ip":"10.149.x.x",
       "host_port":8444,
       "unique_id":"17422113",
       "request":{
          "method":"POST",
          "http_version":2.0,
          "uri":"/suite/framework/backgroundAction.none?appian_environment=tempo"
       },
       "response":{
          "http_code":200
       },
       "producer":{
          "modsecurity":"ModSecurity v3.0.12 (Linux)",
          "connector":"ModSecurity-nginx v1.0.3",
          "secrules_engine":"DetectionOnly",
          "components":[
             "OWASP_CRS/4.4.0\""
          ]
       },
       "messages":[
          {
             "message":"Detects MSSQL code execution and information gathering attempts",
             "details":{
                "match":"Matched \"Operator `Rx' with parameter `(?i)[\\\"'`](?:[\\s\\x0b]*![\\s\\x0b]*[\\\"'0-9A-Z_-z]|;?[\\s\\x0b]*(?:having|select|union\\b[\\s\\x0b]*(?:all|(?:distin|sele)ct))\\b[\\s\\x0b]*[^\\s\\x0b])|\\b(?:(?:(?:c(?:onnection_id|urrent_user)|database|schema|user (311 characters omitted)' against variable `ARGS:$bgUrl' (Value: `/process/embeddedExpressionEditorForPm.do?$e=asiDialog&dialogId=asiExpressionEditor&initialExpressio (2437 characters omitted)' )",
                "reference":"o265,429v1192,2537t:urlDecodeUni,t:removeCommentsChar",
                "ruleId":"942190",
                "file":"/etc/nginx/owasp-modsecurity-crs/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf",
                "lineNumber":"205",
                "data":"Matched Data: select: {          data: {            process: { runtimeData: { smeInbox: { name: true() } } }          }        },        queryFilters: {          a!queryFilter(            field: \"taskId\",            operator: \"=\",            value: pv!taskId          )        },        pagingInfo: a!pagingInfo(startIndex: 1, batchSize: 1),        consumer: \"KPMG_ODD Respond SME Query\"      )    ),    1,    null  )) = tostring(loggedInUser( found within ARGS:$bgUrl: /process/embeddedExpressionEditorForPm.do?$e=asiDialog&dialogId=asiExpressionEditor&initialExpression== touniformstring(  index(    rule!PRO_TK_Index(      path: \"cases.data.process.runtimeData.smeInbox.name\",      dictionary: rule!KPMG_ODD_QueryTaskResult(        select: {          data: {            process: { runtimeData: { smeInbox: { name: true() } } }          }        },        queryFilters: {          a!queryFilter(            field: \"taskId\",            operator: \"=\",            value: pv!taskId          )        },        pagingInfo: a!pagingInfo(startIndex: 1, batchSize: 1),        consumer: \"KPMG_ODD Respond SME Query\"      )    ),    1,    null  )) = tostring(loggedInUser())&variableBindings={\"pv\":[{\"name\":\"buttonAction\",\"typeId\":3},{\"name\":\"caseId\",\"typeId\":3},{\"name\":\"noOfProcesses\",\"typeId\":1},{\"name\":\"readOnly\",\"typeId\":26},{\"name\":\"smeData\",\"typeId\":516},{\"name\":\"smeNme\",\"typeId\":3},{\"name\":\"taskId\",\"typeId\":3}],\"pp\":[{\"name\":\"id\",\"typeId\":1},{\"name\":\"name\",\"typeId\":3},{\"name\":\"priority\",\"typeId\":1},{\"name\":\"initiator\",\"typeId\":4},{\"name\":\"designer\",\"typeId\":4},{\"name\":\"starttime\",\"typeId\":9},{\"name\":\"deadline\",\"typeId\":9},{\"name\":\"timezone\",\"typeId\":3}],\"pm\":[{\"name\":\"id\",\"typeId\":1},{\"name\":\"name\",\"typeId\":3},{\"name\":\"description\",\"typeId\":3},{\"name\":\"version\",\"typeId\":3},{\"name\":\"creator\",\"typeId\":4},{\"name\":\"timezone\",\"typeId\":3}]}&locationContext={\"expressionContext\":\"PROCESS_MODEL\",\"objectId\":3047,\"includeVariablePane\":true}&isVariablePaneEnabled=true",
                "severity":"2",
                "ver":"OWASP_CRS/4.4.0",
                "rev":"",
                "tags":[
                   "application-multi",
                   "language-multi",
                   "platform-multi",
                   "attack-sqli",
                   "paranoia-level/1",
                   "OWASP_CRS",
                   "capec/1000/152/248/66",
                   "PCI/6.5.2"
                ],
                "maturity":"0",
                "accuracy":"0"
             }
          },
          {
             "message":"Inbound Anomaly Score Exceeded (Total Score: 5)",
             "details":{
                "match":"Matched \"Operator `Ge' with parameter `5' against variable `TX:BLOCKING_INBOUND_ANOMALY_SCORE' (Value: `5' )",
                "reference":"",
                "ruleId":"949110",
                "file":"/etc/nginx/owasp-modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf",
                "lineNumber":"222",
                "data":"",
                "severity":"0",
                "ver":"OWASP_CRS/4.4.0",
                "rev":"",
                "tags":[
                   "anomaly-evaluation",
                   "OWASP_CRS"
                ],
                "maturity":"0",
                "accuracy":"0"
             }
          }
       ]
    }
 }

ruleId 932235 and 942190 are skipped rules. How come they appear as above in the logs? Shouldn't they be omitted from the logs if they are skipped?