Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Has the format of the modsecurity error.log message changed? #215

Open
ne20002 opened this issue Mar 7, 2024 · 51 comments
Open

Has the format of the modsecurity error.log message changed? #215

ne20002 opened this issue Mar 7, 2024 · 51 comments

Comments

@ne20002
Copy link

ne20002 commented Mar 7, 2024

I just updated the first of my containers with the new version 4.

After some adjustments the only issue left is that my CrowdSec instance no longer detects the ips of the rejected clients. Crowdsec is parsing the nginx error.log and until the update of the modsecurity container it worked well.

Has the format of the message written to the error.log somehow changed?

@theseion
Copy link
Contributor

theseion commented Mar 8, 2024

Not that I'm aware of. Could you post examples of pre v4 / v4 logs so I can see the difference?

@ne20002
Copy link
Author

ne20002 commented Mar 8, 2024

Hi @theseion

I've taken the same request on both my systems, one with the new CRS 4.0, one with the old 3.5.5.

New log entry:
2024/03/08 18:26:50 [error] 89#89: *546 [client 2a03:1b20:b:f011::a01d] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator Ge' with parameter 5' against variable TX:BLOCKING_INBOUND_ANOMALY_SCORE' (Value: 5' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "176"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "0"] [ver "OWASP_CRS/4.0.0"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "anomaly-evaluation"] [hostname "10.0.2.100"] [uri "/"] [unique_id "170991881036.291448"] [ref ""], client: 2a03:1b20:b:f011::a01d, server: , request: "GET /?exec=/bin/bash HTTP/1.1", host: "fedi.0x42.ch"

Old log entry:
2024/03/08 18:27:55 [error] 100#100: *14928 [client 2a03:1b20:b:f011::a01d] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator Ge' with parameter 5' against variable TX:ANOMALY_SCORE' (Value: 5' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "81"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "2"] [ver "OWASP_CRS/3.3.5"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "10.0.2.100"] [uri "/"] [unique_id "170991887581.167881"] [ref ""], client: 2a03:1b20:b:f011::a01d, server: , request: "GET /?exec=bin/bash HTTP/1.1", host: "nc1.0x42.ch"

The difference is the severity which is now 0 in the new version. The Crowdsec scenario blocks on CRITICAL aka value 2.

So why is severity now 0?

This is the part H in the modsec audit log:

---6D9Yq7X2---H--
ModSecurity: Warning. Matched "Operator PmFromFile' with parameter unix-shell.data' against variable ARGS:exec' (Value: /bin/bash' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf"] [line "556"] [id "932160"] [rev ""] [msg "Remote Command Execution: Unix Shell Code Found"] [data "Matched Data: bin/bash found within ARGS:exec: /bin/bash"] [severity "2"] [ver "OWASP_CRS/4.0.0"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "application-multi"] [tag "language-shell"] [tag "platform-unix"] [tag "attack-rce"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/152/248/88"] [tag "PCI/6.5.2"] [hostname "10.0.2.100"] [uri "/"] [unique_id "170991881036.291448"] [ref "o1,8v11,9t:cmdLine,t:normalizePath"]
ModSecurity: Access denied with code 403 (phase 2). Matched "Operator Ge' with parameter 5' against variable TX:BLOCKING_INBOUND_ANOMALY_SCORE' (Value: 5' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "176"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "0"] [ver "OWASP_CRS/4.0.0"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "anomaly-evaluation"] [hostname "10.0.2.100"] [uri "/"] [unique_id "170991881036.291448"] [ref ""]

This is the blocking rule in 3.3.5:

SecRule TX:ANOMALY_SCORE "@ge %{tx.inbound_anomaly_score_threshold}" \
    "id:949110,\
    phase:2,\
    deny,\
    t:none,\
    log,\
    msg:'Inbound Anomaly Score Exceeded (Total Score: %{TX.ANOMALY_SCORE})',\
    tag:'application-multi',\
    tag:'language-multi',\
    tag:'platform-multi',\
    tag:'attack-generic',\
    ver:'OWASP_CRS/3.3.5',\
    severity:'CRITICAL',\
    setvar:'tx.inbound_anomaly_score=%{tx.anomaly_score}'"

And this in 4.0:

SecRule TX:BLOCKING_INBOUND_ANOMALY_SCORE "@ge %{tx.inbound_anomaly_score_threshold}" \
    "id:949110,\
    phase:2,\
    deny,\
    t:none,\
    msg:'Inbound Anomaly Score Exceeded (Total Score: %{TX.BLOCKING_INBOUND_ANOMALY_SCORE})',\
    tag:'anomaly-evaluation',\
    tag:'OWASP_CRS',\
    ver:'OWASP_CRS/4.0.1-dev'"

As we see: the severity is missing. This is causing the problem with Modsec no longer detecting the log entry as relevant for blocking.

This is the filter line in Crowdsec:
filter: evt.Meta.log_type == 'modsecurity' && (evt.Parsed.ruleseverity == 'CRITICAL' || evt.Parsed.ruleseverity == '2')

Maybe the issue should go to the rules repository?

@theseion
Copy link
Contributor

theseion commented Mar 9, 2024

Technically, rule 949110 only performs the blocking action, it's not itself a rule for an attack that needs to have a severity rating. That being said, I'm not entirely sure that dropping the severity was a conscious decision. In the related PR at least that point wasn't discussed. I'm sure, @dune73 could shed some more light on this.

Reading your Crowdsec rule, it looks to me like it should block on 932160 anyway, right? What's more, you could modify the rule to block whenever you see 949110, since that means the blocking action was executed.

@dune73
Copy link
Member

dune73 commented Mar 9, 2024

Very interesting case. Very good you bring this up @ne20002. Sorry for the inconvenience. It is something somebody should have caught pre-release but if we fix in the next few days we can fix it for 4.1.0 due in the next few days.

So there are detection rules and administrative rules in CRS. 932160 is a detection rule, it identifies the request as an attacking request. Then there are the administrative rules that do all sorts of things. In that group we also have the blocking rules in 949 and 959.

The refactoring of the scoring variables also removed the severity item from the administrative rules. You will notice there is an initialization rule carrying the severity tag, but that's a special case that alarms on broken configurations.

Now the severity item is meant to be in sync with the anomaly score of a detection rule. Critical severity adds 5 points to the anomaly score.

With all this being said, I think or settings are more consistent with the intentions of the rule set with CRS4 than they were with CRS3.

But of course we want to me this work with CrowdSec. Exploring CrowdSec has been on my 2do list for too long. But I am still not familiar with the exact mechanism.

Here are a few questions:

  • So what does CrowdSec do with the logs exactly?
  • Why is it interested in the blocking rule?
  • What functionality is broken now?

I am going to open an issue against CRS, but let's continue to discuss this in this thread for your convenience.

@ne20002
Copy link
Author

ne20002 commented Mar 9, 2024

Hi
Thank you all very much.

Crowdsec works with parsers and scenarios to identify and block bad actors. Kind a fail2ban does.
The Crowsec parser for Modsecurity works on the Nginx error.log. After parsing info Crowdsec decides with the matching scenario.

Within parsing a number of values are extracted from the entry in the logfile. In the scenario then a decision is made based on those values.
In the current version of Crowdsec, the Crowdsec scenario acts on entries starting with 'modsecuriy' and having the string CRITICAL or a severity of 2. I believe they just want to act on critical which for me makes sense.

With the new CRS 4.0 neither the string CRITICAL nor a severity 2 is present in the log entry written to Nginx error.log. In fact, severity is now always 0.
This is, why Crowdsec no longer sees the log entries. Blocking by Modsecurity is still working, but using Crowdsec and blocking identified bad actors by their ip right away on the router (or where the Crowdsec bouncer is running) makes a lot of sense (as well as sharing information of detected bad actors with the Crowdsec community) to me.

If I understand then looking on the other rules as described above would need to parse the Modsecurity audit log instead of the Nginx error log. I believe this would put on some extra resource usage and is more difficult to implement.

I think, that a value describing criticality and severity in the log entry in Nginx error.log is not a bad idea. In fact, those values are still written, but are emtpy or 0.

I also see a possiblity to fix this on Crowdsec side by looking on different info in the log entry like 'Access denied' and 'Anomaly Score Exceeded'. Not sure what the best solution would be. But I'd go for writing the severity to the error.log (maybe based on the highest severity of underlying rules, not sure if this is possible).

@dune73
Copy link
Member

dune73 commented Mar 9, 2024

Very, good. I see what is going on now.

I dare say CrowdSec is not doing this in a very smart way.

Making CrowdSec making good use of ModSec and CRS probably means talking to the developers of the integration. Can you get me in touch with the relevant people?

Your description says that it will act on ModSec + critical/severity 2. That means it acts on 932160 and you do not have to do anything. Given 90+% of the CRS detection rules are in the critical category, we only have a problem if 2 or more rules below the critical threshold team up to trigger 949110 leading to a blockade, but lacking the severity in all the rules triggered CrowdSec ignores it. I think this relatively rare in production and if it happens it is often a false positive (e.g. Loadbalancer doing a health check with IP address as Host header and without an accept header)

There is a question where you want the CrowdSec / Fail2Ban logic to happen. Do you want (1) CrowdSec / Fail2Ban try and make sense of the various CRS alerts, make up their mind and decide on a blocking decision. Or do you want to (2) just follow the blocking decision of CRS that has more insight into the rules and the alerts than CrowdSec / Fail2Ban.

What CrowdSec attempts to do is (1) from my point and view and I think it should do (2).

I have a fail2ban setup and it's as simple as block IPs that hit a 403 in the Apache access log 3 times.

If I was to do that with CRS, I would either look for alerts of the blocking rules (949110, 949111, 959100, 959101) or I would
make sure the reporting level is at 1 or above (default is 4) and then look for rule 980170.

@ne20002
Copy link
Author

ne20002 commented Mar 9, 2024

Actually looking for e.g 932160 is not that easy, as Crowdsec is parsing the Nginx error.log. The parser is looking for a line starting with 'modsecurity' and either critical or severity 2. which means, it acted on all entries written to the error.log because of a blocking decision in Modsecurity/CRS 3.5. Afaik, only blocking rules are logged to Nginx's error.log. The relevant change causing the issue is the lack of the severity 2 in the error.log which is now 0.

I'm not a Crowdsec developer but I'll file an issue at crowdsec project and on discourse.crowdsec.net.

I'm not running an online banking, I just run my very own small Friendica and Nextcloud server. Thus, I consider all triggering of CRS rules an illegal connection attempt and want to block them right away, if possible on my router by ip blocking to reduce resource usage on my small systems. ;)

Also, this problem does not only affects me but all users of Modsecurity/CRS with Crowdsec. I have no idea how much users have this setup. It might also affect users of other tools scanning the Nginx error.log in a similar way. But I'm not aware of any tools like this.
Changing the parser may be the right way to go but would need all users of those two tools together to update them both.

I agree that as Crowdsec is relying on information in the log lines made by the CRS, it's Crowdsec's responsibility to change their parser.
On the other hand I wonder if the logging information provided by the CRS shall include a few more information on the blocking decission so that other tools can work with that.

@ne20002
Copy link
Author

ne20002 commented Mar 9, 2024

@theseion, @dune73

There is a question where you want the CrowdSec / Fail2Ban logic to happen. Do you want (1) CrowdSec / Fail2Ban try and make sense of the various CRS alerts, make up their mind and decide on a blocking decision. Or do you want to (2) just follow the blocking decision of CRS that has more insight into the rules and the alerts than CrowdSec / Fail2Ban.

Maybe we should widen the view on the issue. I see CRS as responsible for detecting attacks, etc and it is doing well. If there is no need for the severity in the log from CRS' point of view, that is ok.
As Crowdsec is relying on the log entries from CRS, it should work on what is available.

On the other hand, Crowdsec is much more than just blocking. It collects information about attacks, it combines information from different sources and user / systems. It also provides a larger picture on an actual threat situation.
For this it requires a bit more information than just 'CRS decided to block a request'. As this wider view will probably become more and more important, it would be good when CRS would provide a bit more insights.

Having said that, I still see no need in changing anything in CRS. If Crowdsec wants to go with (2) a simple parser acting on the error.log of Nginx/Apache would be sufficiant.
If more information is of interesst, as of (1), an enhanced parser working on the CRS audit.log would be a second option. It would be up to the Crowdsec user to decide.

The only thing to do at the moment is to provide a workaround on Crowdsec side to make the parser / scenario work again with the CRS version 4 so that at least (2) is still working.
An enhanced parser / scenario would be a seperate task to take.

@ne20002
Copy link
Author

ne20002 commented Mar 9, 2024

For me I will try to manipulate my local scenario file and see if I get it working again for (2). ;)

@EsadCetiner
Copy link
Member

@ne20002 If you don't mind log spam, you can set the error logging level in Nginx to info. It will display all the triggered rules and not just the anomoly scoring rule that blocked the request, most rules have an severity level of 2 which is enough to trigger the scenario. If you modify the ModSecurity scenario then it'll become tainted and you won't receive automatic updates for the ModSecurity collection. Or you could just copy the scenario, change it's name(to avoid conflicts) and make the modifications you need to avoid tainting the ModSecurity collection.

@ne20002
Copy link
Author

ne20002 commented Mar 10, 2024

Yes, copying is the way I go. It should fix the current problem until an official fix is available.

@dune73
Copy link
Member

dune73 commented Mar 11, 2024

@ne20002 Thank you for the followup.

I do not get this, though.

You quoted the following logline above:

ModSecurity: Warning. Matched "Operator PmFromFile' with parameter unix-shell.data' against variable ARGS:exec' (Value: /bin/bash' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf"] [line "556"] [id "932160"] [rev ""] [msg "Remote Command Execution: Unix Shell Code Found"] [data "Matched Data: bin/bash found within ARGS:exec: /bin/bash"] [severity "2"] [ver "OWASP_CRS/4.0.0"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "application-multi"] [tag "language-shell"] [tag "platform-unix"] [tag "attack-rce"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/152/248/88"] [tag "PCI/6.5.2"] [hostname "10.0.2.100"] [uri "/"] [unique_id "170991881036.291448"] [ref "o1,8v11,9t:cmdLine,t:normalizePath"]

This matches the criteria you pointed out above: It starts with "ModSecurity" and it carries a severity of "2".

And I would be very surprised if NGINX would only log the blocking decision and not the detection rule. The idea is clearly to log both: The detection rules with their alerts and the blocking decision (if any).

You asked for CRS to log more information about a request. This is that the detection rules are doing. If you want a better summary of the blocking decision with more information, then I suggest you look into the CRS4 reporting level used in the RESPONSE-980 rule group.

But first, let's get this logging situation sorted out: If CrowdSec works the way you say it does, then a 933160 alert should be logged by Nginx and it should trigger CrowdSec.

@LaurenceJJones
Copy link

@ne20002 Thank you for the followup.

I do not get this, though.

You quoted the following logline above:

ModSecurity: Warning. Matched "Operator PmFromFile' with parameter unix-shell.data' against variable ARGS:exec' (Value: /bin/bash' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf"] [line "556"] [id "932160"] [rev ""] [msg "Remote Command Execution: Unix Shell Code Found"] [data "Matched Data: bin/bash found within ARGS:exec: /bin/bash"] [severity "2"] [ver "OWASP_CRS/4.0.0"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "application-multi"] [tag "language-shell"] [tag "platform-unix"] [tag "attack-rce"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/152/248/88"] [tag "PCI/6.5.2"] [hostname "10.0.2.100"] [uri "/"] [unique_id "170991881036.291448"] [ref "o1,8v11,9t:cmdLine,t:normalizePath"]

This matches the criteria you pointed out above: It starts with "ModSecurity" and it carries a severity of "2".

And I would be very surprised if NGINX would only log the blocking decision and not the detection rule. The idea is clearly to log both: The detection rules with their alerts and the blocking decision (if any).

You asked for CRS to log more information about a request. This is that the detection rules are doing. If you want a better summary of the blocking decision with more information, then I suggest you look into the CRS4 reporting level used in the RESPONSE-980 rule group.

But first, let's get this logging situation sorted out: If CrowdSec works the way you say it does, then a 933160 alert should be logged by Nginx and it should trigger CrowdSec.

Just so I can attempt to help, Laurence from CrowdSec Support 👋🏻

The example line shown doesnt have the nginx error prefix example:

2024/02/08 00:56:35 [error] 2120350#2120350: *527836 [client 1.2.3.4]

I guess it was omitted by thought I make sure 👍🏻

and we expect currently after the "ref" section this information

, client: %{IPORHOST:remote_addr}, server: %{DATA:target_fqdn}, request: \"%{WORD:verb} %{DATA:request} HTTP/%{NUMBER:http_version}\"(, host: \"%{IPORHOST}(:%{INT})?\")?

If this information is now not needed or is logged on a conditional basis we can make this optional for the line to parse example

image

@ne20002
Copy link
Author

ne20002 commented Mar 11, 2024

Actually I'm using Crowdsec and CRS/Nginx container. It logs detection rule and blocking rule to Modesc audit.log. But to error.log only the blocking rule is logged.
This in version 3.3.5 and 4.0.0.
The Crowdsec parser looks only on the error.log.

@dune73
Copy link
Member

dune73 commented Mar 11, 2024

Hey @LaurenceJJones, thank you for joining the discussion. I'm sure we can sort this out to make things run smoothly for CrowdSec users.

Trying to recreate the logging situation locally now.

@dune73
Copy link
Member

dune73 commented Mar 11, 2024

OK. So this is not what I expected to find. The logging on the official CRS Nginx container (nginx-alpine) is as described as above.

Request: curl localhost:8080 --data "exec:/bin/bash" -H "Host: localhost" -v

Access Log

127.0.0.1 - - [11/Mar/2024:15:29:29 +0000] "POST / HTTP/1.1" 403 146 "-" "curl/8.5.0" "-"

Error Log

2024/03/11 15:29:29 [error] 538#538: *24 [client 127.0.0.1] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:BLOCKING_INBOUND_ANOMALY_SCORE' (Value: `5' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "176"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "0"] [ver "OWASP_CRS/4.0.0"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "anomaly-evaluation"] [hostname "127.0.0.1"] [uri "/"] [unique_id "171017096930.654276"] [ref ""], client: 127.0.0.1, server: localhost, request: "POST / HTTP/1.1", host: "localhost"

Notice how 933160 is not being logged in the error.log.

AuditLog

{"transaction":{"client_ip":"127.0.0.1","time_stamp":"Mon Mar 11 15:29:29 2024","server_id":"0464d6f362807f483b1335360064b441bc6da3a9","client_port":50446,"host_ip":"127.0.0.1","host_port":8080,"unique_id":"171017096930.654276","request":{"method":"POST","http_version":1.1,"uri":"/","headers":{"Host":"localhost","User-Agent":"curl/8.5.0","Accept":"*/*","Content-Length":"14","Content-Type":"application/x-www-form-urlencoded"}},"response":{"body":"<html>\r\n<head><title>403 Forbidden</title></head>\r\n<body>\r\n<center><h1>403 Forbidden</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n","http_code":403,"headers":{"Server":"nginx","Date":"Mon, 11 Mar 2024 15:25:42 GMT","Content-Length":"146","Content-Type":"text/html","Connection":"keep-alive"}},"producer":{"modsecurity":"ModSecurity v3.0.12 (Linux)","connector":"ModSecurity-nginx v1.0.3","secrules_engine":"Enabled","components":["OWASP_CRS/4.0.0\""]},"messages":[{"message":"Remote Command Execution: Unix Shell Code Found","details":{"match":"Matched \"Operator `PmFromFile' with parameter `unix-shell.data' against variable `ARGS_NAMES:exec:/bin/bash' (Value: `exec:/bin/bash' )","reference":"o6,8v135,14t:cmdLine,t:normalizePath","ruleId":"932160","file":"/etc/modsecurity.d/owasp-crs/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf","lineNumber":"556","data":"Matched Data: bin/bash found within ARGS_NAMES:exec:/bin/bash: exec:/bin/bash","severity":"2","ver":"OWASP_CRS/4.0.0","rev":"","tags":["modsecurity","application-multi","language-shell","platform-unix","attack-rce","paranoia-level/1","OWASP_CRS","capec/1000/152/248/88","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/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf","lineNumber":"176","data":"","severity":"0","ver":"OWASP_CRS/4.0.0","rev":"","tags":["modsecurity","anomaly-evaluation"],"maturity":"0","accuracy":"0"}}]}}

Pretty-Printed:

{
  "transaction": {
    "client_ip": "127.0.0.1",
    "time_stamp": "Mon Mar 11 15:29:29 2024",
    "server_id": "0464d6f362807f483b1335360064b441bc6da3a9",
    "client_port": 50446,
    "host_ip": "127.0.0.1",
    "host_port": 8080,
    "unique_id": "171017096930.654276",
    "request": {
      "method": "POST",
      "http_version": 1.1,
      "uri": "/",
      "headers": {
        "Host": "localhost",
        "User-Agent": "curl/8.5.0",
        "Accept": "*/*",
        "Content-Length": "14",
        "Content-Type": "application/x-www-form-urlencoded"
      }
    },
    "response": {
      "body": "<html>\r\n<head><title>403 Forbidden</title></head>\r\n<body>\r\n<center><h1>403 Forbidden</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n",
      "http_code": 403,
      "headers": {
        "Server": "nginx",
        "Date": "Mon, 11 Mar 2024 15:25:42 GMT",
        "Content-Length": "146",
        "Content-Type": "text/html",
        "Connection": "keep-alive"
      }
    },
    "producer": {
      "modsecurity": "ModSecurity v3.0.12 (Linux)",
      "connector": "ModSecurity-nginx v1.0.3",
      "secrules_engine": "Enabled",
      "components": [
        "OWASP_CRS/4.0.0\""
      ]
    },
    "messages": [
      {
        "message": "Remote Command Execution: Unix Shell Code Found",
        "details": {
          "match": "Matched \"Operator `PmFromFile' with parameter `unix-shell.data' against variable `ARGS_NAMES:exec:/bin/bash' (Value: `exec:/bin/bash' )",
          "reference": "o6,8v135,14t:cmdLine,t:normalizePath",
          "ruleId": "932160",
          "file": "/etc/modsecurity.d/owasp-crs/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf",
          "lineNumber": "556",
          "data": "Matched Data: bin/bash found within ARGS_NAMES:exec:/bin/bash: exec:/bin/bash",
          "severity": "2",
          "ver": "OWASP_CRS/4.0.0",
          "rev": "",
          "tags": [
            "modsecurity",
            "application-multi",
            "language-shell",
            "platform-unix",
            "attack-rce",
            "paranoia-level/1",
            "OWASP_CRS",
            "capec/1000/152/248/88",
            "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/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf",
          "lineNumber": "176",
          "data": "",
          "severity": "0",
          "ver": "OWASP_CRS/4.0.0",
          "rev": "",
          "tags": [
            "modsecurity",
            "anomaly-evaluation"
          ],
          "maturity": "0",
          "accuracy": "0"
        }
      }
    ]
  }
}

@theseion, @fzipi, @franbuehler : Is this logging behavior desired? If yes, how did you pull this off? (seriously, I am not sure how you can do this with ModSec. Based on the rule configuration, I would say 932160 and 949110 would both be logged in the error.log. Or none of the two.)

Or is it a funny side-effect of the SecDefaultAction used here in combination with ModSecurity v3?

I'm a bit at a loss, since this brings CrowdSec in a troubling situation.

@LaurenceJJones : I think looking for the severity in a ModSec alert message is not the best approach. Custom rules written by a user or some other 3rd party are unlikely to carry a severity value most of the time.

A dead-beat indication that ModSecurity has taken action is to look for ModSecurity: Access denied in the error.log. See above. This is consistent across all ModSecurity versions and I have never seen it lie. Triggering CrowdSec on this pattern would be basing on the fact that the WAF was instructed to block a request for whatever reason and that's very close to desiring CrowdSec to block the same client for subsequent requests.

But before you jump into action, let's wait what the CRS docker people have to say.

@ne20002
Copy link
Author

ne20002 commented Mar 11, 2024

Notice how 933160 is not being logged in the error.log.*

This is how it worked since I use the modesurity-crs container.

I believe implementing/restoring the logging of the triggered detection rule to the error.log would be best in any case.

Changing the Crowdsec parser to look on ModSecurity: Access denied would also be a good idea.
But I like the idea of Crowdsec being able, besides blocking subsequent requests, to collect more information on the reason for blocking, not only severity but e.g. rule number, from maybe many attacked instances to send it to central api and use this for community decisions.

@theseion
Copy link
Contributor

theseion commented Mar 11, 2024

@dune73 I can't recreate what you see using the compose setup for CRS (I would have been very surprised too, because many of our tests would fail against nginx otherwise).

Error log entries for curl localhost:8080 --data "exec:/bin/bash" -H "Host: localhost" -v:

2024/03/11 18:17:22 [info] 105#105: *1 ModSecurity: Warning. Matched "Operator `Rx' with parameter `^(?:(?:\*|[^!\"\(\),/:-\?\[-\]\{\}]+)/(?:\*|[^!\"\(\),/:-\?\[-\]\{\}]+)|\*)(?:[\s\v]*;[\s\v]*(?:charset[\s\v]*=[\s\v]*\"?(?:iso-8859-15?|utf-8|windows-1252)\b\"?|(?:[^\s\v-\"\(\),/:-\?\[-\]c\{\}]|c(?: (728 characters omitted)' against variable `REQUEST_HEADERS:Accept' (Value: `*/*' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1241"] [id "920600"] [rev ""] [msg "Illegal Accept header: charset parameter"] [data "*/*"] [severity "2"] [ver "OWASP_CRS/4.0.1-dev"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [hostname "172.18.0.3"] [uri "/"] [unique_id "171018104222.881454"] [ref "v63,3t:lowercase"], client: 192.168.65.1, server: localhost, request: "POST / HTTP/1.1", host: "localhost"
2024/03/11 18:17:22 [info] 105#105: *1 ModSecurity: Warning. Matched "Operator `ValidateByteRange' with parameter `38,44-46,48-58,61,65-90,95,97-122' against variable `REQUEST_BODY' (Value: `exec:/bin/bash' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1799"] [id "920273"] [rev ""] [msg "Invalid character in request (outside of very strict set)"] [data "REQUEST_BODY=exec:/bin/bash"] [severity "2"] [ver "OWASP_CRS/4.0.1-dev"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "paranoia-level/4"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [hostname "172.18.0.3"] [uri "/"] [unique_id "171018104222.881454"] [ref "o5,1o9,1v135,14t:urlDecodeUnio5,1o9,1v135,14t:urlDecodeUni"], client: 192.168.65.1, server: localhost, request: "POST / HTTP/1.1", host: "localhost"
2024/03/11 18:17:22 [info] 105#105: *1 ModSecurity: Warning. Matched "Operator `PmFromFile' with parameter `unix-shell.data' against variable `ARGS_NAMES:exec:/bin/bash' (Value: `exec:/bin/bash' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf"] [line "556"] [id "932160"] [rev ""] [msg "Remote Command Execution: Unix Shell Code Found"] [data "Matched Data: bin/bash found within ARGS_NAMES:exec:/bin/bash: exec:/bin/bash"] [severity "2"] [ver "OWASP_CRS/4.0.1-dev"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "application-multi"] [tag "language-shell"] [tag "platform-unix"] [tag "attack-rce"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/152/248/88"] [tag "PCI/6.5.2"] [hostname "172.18.0.3"] [uri "/"] [unique_id "171018104222.881454"] [ref "o6,8v135,14t:cmdLine,t:normalizePath"], client: 192.168.65.1, server: localhost, request: "POST / HTTP/1.1", host: "localhost"
2024/03/11 18:17:22 [info] 105#105: *1 ModSecurity: Warning. Matched "Operator `Ge' with parameter `5' against variable `TX:BLOCKING_INBOUND_ANOMALY_SCORE' (Value: `20' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "222"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 20)"] [data ""] [severity "0"] [ver "OWASP_CRS/4.0.1-dev"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "anomaly-evaluation"] [tag "OWASP_CRS"] [hostname "172.18.0.3"] [uri "/"] [unique_id "171018104222.881454"] [ref ""], client: 192.168.65.1, server: localhost, request: "POST / HTTP/1.1", host: "localhost"

@ne20002
Copy link
Author

ne20002 commented Mar 11, 2024

I took a look at the compose file and set the values accordingly in my setup. I still have the issue. I'm not using compose but start the container from shell script with docker run.

In error.log only the blocking rule is logged, in audit.log both rules are logged.

In my error.log the entry starts with 2024/03/11 20:32:21 [error] whereas the entries in the log of @theseion starting with 2024/03/11 18:17:22 [info] . Is this a problem of log level?

@dune73
Copy link
Member

dune73 commented Mar 11, 2024

Here what I did:

host $ docker buildx bake -f docker-bake.hcl --set "*.platform=linux/amd64" --load nginx-alpine
...
host $ docker run -ti --rm --name crs owasp/modsecurity-crs:nginx-alpine &
...
host $ docker exec -u 0 -it crs /bin/sh
...
EDIT FILES TO GET SEPARATE access.log, error.log and audit.log
...
crs $ kill -HUP 1
...
crs $ curl localhost:8080 --data "exec:/bin/bash" -H "Host: localhost" -v

With the logs all set to write to STDOUT / STDERR, it's difficult to keep them apart and to understand where the messages really originate from. Hence the separation.

@ne20002
Copy link
Author

ne20002 commented Mar 11, 2024

It's different depending on how the container is started?

@theseion
Copy link
Contributor

All rule logs are written as info messages, except for the final blocking message, which is logged as error. However, for the blocking message to be logged, the engine has to be set to On, not DetectionOnly.

@dune73
Copy link
Member

dune73 commented Mar 12, 2024

@theseion :

In @LaurenceJJones 's screenshot above, there is a nonblocking 932160 alert on loglevel error.

In your dump, though, there is 2024/03/11 18:17:22 [info] 105#105: for the blocking rule 949110. Or was that done via DetectionOnly?

This just does not add up for me.

@dune73
Copy link
Member

dune73 commented Mar 12, 2024

That's my gut feeling as well. And I would be pleased if somebody with more docker FU than me (I'm like the least docker-experienced person you can think of) would play this through and dig at the bottom of this.

@ne20002
Copy link
Author

ne20002 commented Mar 12, 2024

If I may sum up what we expect and see:

  1. In CRS 3.3.5 the blocking rule has been logged to Nginx error.log with loglevel error and with severity = 2.

  2. The Crowdsec parser was able to identify the log entry as it looks on the log line prefix and on severity = 2.

  3. In my setup the detection rule has not been logged in the error.log.

  4. With CRS 4 severity is now logged with 0 for blocking rules. As the detection rule is not logged to error.log, the Crowdsec parser now fails.

  5. The expectation is, that the detection rule should also have been logged to error.log with log level error. If this would be the case, the Crowdsec parser would detect it. @theseion, is this expectation correct?

  6. At least @dune73 can reproduce the behavior as described by me. We both start the modsecurity-crs:nginx container with docker run command (podman run in my case). Whereas @theseion started it with docker compose, the automatic tests also use docker compose.

7: I suspect that the behavior is caused by either the different way of starting the container or by the setting of the engine set to On versus DetectionOnly (suppressing the logging of the detection rule if set to On).

@ne20002
Copy link
Author

ne20002 commented Mar 12, 2024

I have changed my Nginx error_log setting to level info. Now I get the detection rule logged in the error.log:

2024/03/12 11:00:08 [info] 91#91: *59 ModSecurity: Warning. Matched "Operator `PmFromFile' with parameter `unix-shell.data' against variable `ARGS:exec' (Value: `/bin/bash' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf"] [line "556"] [id "932160"] [rev ""] [msg "Remote Command Execution: Unix Shell Code Found"] [data "Matched Data: bin/bash found within ARGS:exec: /bin/bash"] [severity "2"] [ver "OWASP_CRS/4.0.0"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "application-multi"] [tag "language-shell"] [tag "platform-unix"] [tag "attack-rce"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/152/248/88"] [tag "PCI/6.5.2"] [hostname "10.0.2.100"] [uri "/"] [unique_id "171023760855.494742"] [ref "o1,8v11,9t:cmdLine,t:normalizePath"], client: 2a03:1b20:b:f011::a01d, server: , request: "GET /?exec=/bin/bash HTTP/1.1", host: "fedi.xxxx.ch"
2024/03/12 11:00:08 [error] 91#91: *59 [client 2a03:1b20:b:f011::a01d] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:BLOCKING_INBOUND_ANOMALY_SCORE' (Value: `5' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "176"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "0"] [ver "OWASP_CRS/4.0.0"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "anomaly-evaluation"] [hostname "10.0.2.100"] [uri "/"] [unique_id "171023760855.494742"] [ref ""], client: 2a03:1b20:b:f011::a01d, server: , request: "GET /?exec=/bin/bash HTTP/1.1", host: "fedi.xxxx.ch"

But I also get a lot of log spam. :(

If this is the expected behaviour (@dune73 and I expected otherwise) than this needs to be documented, especially for the Crowdsec parser (@LaurenceJJones).

If this is the expected behaviour maybe a setting defining the log level for the detection rule in the error.log would be an option?

@dune73
Copy link
Member

dune73 commented Mar 12, 2024

OK, we getting near the bottom now.

This very close to the desired behavior now. I am not using ModSec3 myself and on ModSec2 / Apache everything is on level error. No difference between "ModSecurity: Warning" (= pass) and "ModSecurity: Access denied" (= deny) there. But here we apparently have it.

Still not sure why @LaurenceJJones got an "error" on his screenshot in combination with "ModSecurity: warning".

But this piece of the puzzle left aside, it looks like the docker container is running at level warn by default, but the engine logs decisive information at level info.

Also ModSecurity logging the word "Warning" on loglevel "Info" is obviously wrong.

@airween : What is your take?

The signal to noise ratio now looks worse for you @ne20002, but that's because you are probably not used to seeing these alerts. But seeing them is part of the operation and helps with the decision if it's a real alert or a false positive.

Now I would advise CrowdSec against blocking on this 930160 alert message that qualifies the defined criteris (ModSecurity + severity 2). I think it's better to trigger on "ModSecurity: Access denied". Optionally, if you want to limit to CRS, then "ModSecurity: Access denied" + "tag: OWASP_CRS".

But the docker container has to be fixed or better still, ModSecurity3 should start to log at a reasonable loglevel.

@ne20002
Copy link
Author

ne20002 commented Mar 12, 2024

The log spam is more other info messages printed by Nginx, not Modsecurity related. From Modsecurity I only see the detection rule with level info.

I agree that if the detection rule would be logged with level warn (as it is a warning), everything would be fine and it would suit the default log level of the container.

I have a bit different position regarding Crowdsec as I believe Crowdsec should be able to do its own decission. Modsecurity with CRS is excellent in detecting and blocking single requests. Crowdsec on the other hand is a level ontop and e.g has options to block an ip after a predefined number of attempts or based on extra information it gets from Modsecurity like severity, ruleId and also and more important to share this with the community and may benefit from a bit more information for this decission.

@dune73
Copy link
Member

dune73 commented Mar 12, 2024

The log spam is more other info messages printed by Nginx, not Modsecurity related. From Modsecurity I only see the detection rule with level info.

On Apache, you can set different loglevels for different modules. Do you know if Nginx allows for the same granularity?

As for CrowdSec: All agreed. This is up to CrowdSec. It's just that we need to provide CrowdSec with the necessary information. And CrowdSec using a tangential piece like "severity" is a bad choice though. I think CrowdSec needs to use patterns that allow it to understand what ModSec / CRS is doing exactly and then it can take that decision on a sound base.

@theseion
Copy link
Contributor

I haven't spent much time yet looking into the issue but I have no clue how @LaurenceJJones managed to log that rule at error level. I went back and looked at older images (back to 3.3.2) and they all log at info level. Seems we're missing something.

@LaurenceJJones
Copy link

LaurenceJJones commented Mar 12, 2024

I haven't spent much time yet looking into the issue but I have no clue how @LaurenceJJones managed to log that rule at error level. I went back and looked at older images (back to 3.3.2) and they all log at info level. Seems we're missing something.

Note we have test files that i took the line from https://github.com/crowdsecurity/hub/blob/master/.tests/modsecurity-logs-nginx/modsecurity-logs-nginx.log so I didnt generate it live sorry maybe i should of made that clear

@dune73
Copy link
Member

dune73 commented Mar 12, 2024

So you mean this could have been doctored a bit? :)

@ne20002
Copy link
Author

ne20002 commented Mar 12, 2024

I believe we can let this to Crowdsec. They may need to update their test log files (3 years old).

The only thing I don't get is that we have logging of detection rules with info level in Nginx and with error level with the Apache image (if I did get @dune73 right).

Changing the logging of detection rules to level warn would be consistent with the wording, with the container default log level (without having log spam) and will work with Crowdsec parser.

So is this the way to go?

@dune73
Copy link
Member

dune73 commented Mar 12, 2024

ModSecurity v2 (-> Apache) and ModSecurity v3 (-> Nginx) have many undocumented behavior differences. No surprise there.

Let's wait for @airween, who knows the v3 code base very well. He's a bit busy at the moment, but I am sure we'll hear from him.

@airween
Copy link

airween commented Mar 12, 2024

So why is severity now 0?

If I understand correctly, this is the "original" problem, right?

I don't know it is by design or an accident, but libmodsecurity3 puts each fields into the log, even the key does not set at all. See the source

    29      msg.append(" [file \"" + std::string(*rm->m_ruleFile.get()) + "\"]");
    30      msg.append(" [line \"" + std::to_string(rm->m_ruleLine) + "\"]");
    31      msg.append(" [id \"" + std::to_string(rm->m_ruleId) + "\"]");
    32      msg.append(" [rev \"" + utils::string::toHexIfNeeded(rm->m_rev, true) + "\"]");
    33      msg.append(" [msg \"" + rm->m_message + "\"]");
    34      msg.append(" [data \"" + utils::string::toHexIfNeeded(utils::string::limitTo(200, rm->m_data), true) + "\"]");
    35      msg.append(" [severity \"" +
    36          std::to_string(rm->m_severity) + "\"]");
    37      msg.append(" [ver \"" + utils::string::toHexIfNeeded(rm->m_ver, true) + "\"]");
    38      msg.append(" [maturity \"" + std::to_string(rm->m_maturity) + "\"]");
    39      msg.append(" [accuracy \"" + std::to_string(rm->m_accuracy) + "\"]");

The other fields (ver, maturity, accuracy) are also present in the log with value of 0, no matter are there at the rule or not.

@theseion
Copy link
Contributor

No, the question is, whether v3 really always logs at info level, except for the blocking action, which is logged at error level, but only when the engine is set to On. v2 for httpd always logs at error.

@airween
Copy link

airween commented Mar 12, 2024

No, the question is, whether v3 really always logs at info level, except for the blocking action, which is logged at error level, but only when the engine is set to On. v2 for httpd always logs at error.

Ah, thanks.

Then I need to investigate this behavior, it's really strange.

but only when the engine is set to On.

And what if the mode is DetectionOnly?

@theseion
Copy link
Contributor

DetectionOnly, all logged at info:

2024/03/11 18:17:22 [info] 105#105: *1 ModSecurity: Warning. Matched "Operator `Rx' with parameter `^(?:(?:\*|[^!\"\(\),/:-\?\[-\]\{\}]+)/(?:\*|[^!\"\(\),/:-\?\[-\]\{\}]+)|\*)(?:[\s\v]*;[\s\v]*(?:charset[\s\v]*=[\s\v]*\"?(?:iso-8859-15?|utf-8|windows-1252)\b\"?|(?:[^\s\v-\"\(\),/:-\?\[-\]c\{\}]|c(?: (728 characters omitted)' against variable `REQUEST_HEADERS:Accept' (Value: `*/*' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1241"] [id "920600"] [rev ""] [msg "Illegal Accept header: charset parameter"] [data "*/*"] [severity "2"] [ver "OWASP_CRS/4.0.1-dev"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [hostname "172.18.0.3"] [uri "/"] [unique_id "171018104222.881454"] [ref "v63,3t:lowercase"], client: 192.168.65.1, server: localhost, request: "POST / HTTP/1.1", host: "localhost"
2024/03/11 18:17:22 [info] 105#105: *1 ModSecurity: Warning. Matched "Operator `ValidateByteRange' with parameter `38,44-46,48-58,61,65-90,95,97-122' against variable `REQUEST_BODY' (Value: `exec:/bin/bash' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1799"] [id "920273"] [rev ""] [msg "Invalid character in request (outside of very strict set)"] [data "REQUEST_BODY=exec:/bin/bash"] [severity "2"] [ver "OWASP_CRS/4.0.1-dev"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "paranoia-level/4"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [hostname "172.18.0.3"] [uri "/"] [unique_id "171018104222.881454"] [ref "o5,1o9,1v135,14t:urlDecodeUnio5,1o9,1v135,14t:urlDecodeUni"], client: 192.168.65.1, server: localhost, request: "POST / HTTP/1.1", host: "localhost"
2024/03/11 18:17:22 [info] 105#105: *1 ModSecurity: Warning. Matched "Operator `PmFromFile' with parameter `unix-shell.data' against variable `ARGS_NAMES:exec:/bin/bash' (Value: `exec:/bin/bash' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf"] [line "556"] [id "932160"] [rev ""] [msg "Remote Command Execution: Unix Shell Code Found"] [data "Matched Data: bin/bash found within ARGS_NAMES:exec:/bin/bash: exec:/bin/bash"] [severity "2"] [ver "OWASP_CRS/4.0.1-dev"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "application-multi"] [tag "language-shell"] [tag "platform-unix"] [tag "attack-rce"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/152/248/88"] [tag "PCI/6.5.2"] [hostname "172.18.0.3"] [uri "/"] [unique_id "171018104222.881454"] [ref "o6,8v135,14t:cmdLine,t:normalizePath"], client: 192.168.65.1, server: localhost, request: "POST / HTTP/1.1", host: "localhost"
2024/03/11 18:17:22 [info] 105#105: *1 ModSecurity: Warning. Matched "Operator `Ge' with parameter `5' against variable `TX:BLOCKING_INBOUND_ANOMALY_SCORE' (Value: `20' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "222"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 20)"] [data ""] [severity "0"] [ver "OWASP_CRS/4.0.1-dev"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "anomaly-evaluation"] [tag "OWASP_CRS"] [hostname "172.18.0.3"] [uri "/"] [unique_id "171018104222.881454"] [ref ""], client: 192.168.65.1, server: localhost, request: "POST / HTTP/1.1", host: "localhost"

On, all logged at info, except for the blocking rule:

2024/03/12 20:20:48 [info] 105#105: *1 ModSecurity: Warning. Matched "Operator `Rx' with parameter `^(?:(?:\*|[^!\"\(\),/:-\?\[-\]\{\}]+)/(?:\*|[^!\"\(\),/:-\?\[-\]\{\}]+)|\*)(?:[\s\v]*;[\s\v]*(?:charset[\s\v]*=[\s\v]*\"?(?:iso-8859-15?|utf-8|windows-1252)\b\"?|(?:[^\s\v-\"\(\),/:-\?\[-\]c\{\}]|c(?: (728 characters omitted)' against variable `REQUEST_HEADERS:Accept' (Value: `*/*' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1241"] [id "920600"] [rev ""] [msg "Illegal Accept header: charset parameter"] [data "*/*"] [severity "2"] [ver "OWASP_CRS/4.0.1-dev"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [hostname "172.19.0.3"] [uri "/"] [unique_id "171027484857.712665"] [ref "v63,3t:lowercase"], client: 192.168.65.1, server: localhost, request: "POST / HTTP/1.1", host: "localhost"
2024/03/12 20:20:48 [info] 105#105: *1 ModSecurity: Warning. Matched "Operator `ValidateByteRange' with parameter `38,44-46,48-58,61,65-90,95,97-122' against variable `REQUEST_BODY' (Value: `exec:/bin/bash' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1799"] [id "920273"] [rev ""] [msg "Invalid character in request (outside of very strict set)"] [data "REQUEST_BODY=exec:/bin/bash"] [severity "2"] [ver "OWASP_CRS/4.0.1-dev"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "paranoia-level/4"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [hostname "172.19.0.3"] [uri "/"] [unique_id "171027484857.712665"] [ref "o5,1o9,1v135,14t:urlDecodeUnio5,1o9,1v135,14t:urlDecodeUni"], client: 192.168.65.1, server: localhost, request: "POST / HTTP/1.1", host: "localhost"
2024/03/12 20:20:48 [info] 105#105: *1 ModSecurity: Warning. Matched "Operator `PmFromFile' with parameter `unix-shell.data' against variable `ARGS_NAMES:exec:/bin/bash' (Value: `exec:/bin/bash' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf"] [line "556"] [id "932160"] [rev ""] [msg "Remote Command Execution: Unix Shell Code Found"] [data "Matched Data: bin/bash found within ARGS_NAMES:exec:/bin/bash: exec:/bin/bash"] [severity "2"] [ver "OWASP_CRS/4.0.1-dev"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "application-multi"] [tag "language-shell"] [tag "platform-unix"] [tag "attack-rce"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/152/248/88"] [tag "PCI/6.5.2"] [hostname "172.19.0.3"] [uri "/"] [unique_id "171027484857.712665"] [ref "o6,8v135,14t:cmdLine,t:normalizePath"], client: 192.168.65.1, server: localhost, request: "POST / HTTP/1.1", host: "localhost"
2024/03/12 20:20:48 [error] 105#105: *1 [client 192.168.65.1] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:BLOCKING_INBOUND_ANOMALY_SCORE' (Value: `20' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "222"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 20)"] [data ""] [severity "0"] [ver "OWASP_CRS/4.0.1-dev"] [maturity "0"] [accuracy "0"] [tag "modsecurity"] [tag "anomaly-evaluation"] [tag "OWASP_CRS"] [hostname "172.19.0.3"] [uri "/"] [unique_id "171027484857.712665"] [ref ""], client: 192.168.65.1, server: localhost, request: "POST / HTTP/1.1", host: "localhost"

@airween
Copy link

airween commented Mar 12, 2024

I made a quick inspection:

  • libmodsecurity3's own regression test does not log any rule which does not have log action - rule 949110 isn't in the log
  • ftwrunner logs only lines what get from the library; it also does not get any info about 949110
  • I think the line severity (eg. [info], [error]) is produced by Nginx, not ModSecurity. ModSecurity produces the log from position ModSecurity: ... - but I think the severity info comes from the library.

This is what I have to figure out, how.

@theseion
Copy link
Contributor

That makes sense and is consistent with the effect of changing the nginx log level. But the library must choose the desired log level for the message somewhere...

@dune73
Copy link
Member

dune73 commented Mar 13, 2024

Initially, I thought nginx might assign "error" if a module triggers a 403 and it assigns "info" if no such thing happens. But I think that must be unlikely, since the logging instruction and the return value instructing the webserver to block a request are separate. Or at least this is how I see the processing working without deeper knowledge of the code base.

If @airween's finding is confirmed and it's indeed nginx assigning the loglevel, then I guess we need to set the loglevel to info on the container and piss off users with the noise.

For the record: the CIS NGINX Benchmark v2.0.1 recommends loglevel info (section 3.3, page 77).

@ne20002
Copy link
Author

ne20002 commented Mar 14, 2024

That makes sense and is consistent with the effect of changing the nginx log level. But the library must choose the desired log level for the message somewhere...

Usually the developer decides on what level a log statement is logged. It must be somewhere in the code.

@ne20002
Copy link
Author

ne20002 commented Mar 14, 2024

I played a bit I can work with log level info on the modescurity-crs:nginx container. :)

The log spam can be simply ignored/filtered with a combination of cat and grep. So this is not an issue.

As the Crowdsec parser is now looking on the log line of the detection rule, is will also detect line logged with DETECTION_PARANOIA_LEVEL higher than BLOCKING_PARANOIA_LEVEL. But this is not a CRS problem.
:)

@dune73
Copy link
Member

dune73 commented Mar 14, 2024

Yes, correct about the higher PL. Which brings me back to my statement: CrowdSec better look at the blocking message than rule detection alerts.

The distinction between detection and blocking paranoia level was made in order to fight false positives without blocking on them. If CrowdSec ignores that context and blocks IPs, it falls into this trap and executes on a false positive.

@airween : Are you sure ModSec3 can't be made to log on level warn?

@airween
Copy link

airween commented Mar 14, 2024

@airween : Are you sure ModSec3 can't be made to log on level warn?

Let me check this - I can try to take a look soon.

@dune73
Copy link
Member

dune73 commented Mar 14, 2024

Thank you. I get the feeling this is really important or we need to change the settings of the container. We need to deliver a container that actually works in prod and delivers the right amount of logs in the right format.

@airween
Copy link

airween commented Mar 17, 2024

Here is how the libmodsecurity3 handles the transactions.

Libmodsecurity3 has a ModSecurityIntervention class, which can store the result of a transaction.

The application that embeds the library needs to create one, and pass it to the function msc_intervention() at the end of the each phases, which fills it based on rules in executed phase.

If the SecRuleEngine is On, and a triggered rule on the examined phase has a disruptive action (eg. deny), then the intervention object's status field will be 403 (or if the rule controls it, then that value), the log will hold the log message from the disruptive rule, and the disruptive field will be 1. In this case, the library's "inside" logger won't be called. The application needs to read the log field and send it to its logger. If the rule does not have disruptive action but has a log message, then that will be sent through the previously set callback function (see later).

If the SecRuleEngine is NOT On, even there is a disruptive action at a rule, the intervention object's status field will be 200, the disruptive will be 0, and the log will be empty. Each log message will send through log callback function.

This is by design from the beginning, I don't see any change in the logic lately.

Note, that severity of log entries set up by Nginx, not the library. Here is where the connector sets the library's callback function for any non-disruptive log entries (with severity info), and here is where the disruptive case is handling with severity error.

If somebody is interesting, I can provide a sample C code to demonstrate it.

@theseion
Copy link
Contributor

Thanks @airween. I think we can only do one thing from our side and that is to change default log level for nginx to info. The rest will have to be done by CrowdSec.

@dune73
Copy link
Member

dune73 commented Mar 18, 2024

Thank you for this detailed analysis @airween.

This is now pretty much a ModSecurity problem, but the conversation started here, so I'll continue here.

Summing things up:

Nginx knows these log levels:

  • debug
  • info
  • notice
  • warn
  • error
  • crit
  • alert
  • emerg

Based on the behavior of ModSecurity, it determines that rule alerts that are not directly blocking a request should be info, ignoring the fact that the rule alerts are the root cause of a likely subsequent blocking of the same request and that the alert says "Warning".

How do other modules ever log on level notice or warn? And can the design be changed to follow other modules so we can log an warn?

On Apache, we could cope with the situation by assigning different modules a different loglevel, Nginx is less flexible, so we pretty much force people to use either the cumbersome Audit Log, or to eat all the noise that comes with running at info in production.

@ne20002
Copy link
Author

ne20002 commented Mar 20, 2024

I believe to change the log level to warn would be a good idea. This is an error.log of the Nginx with log level set to info:

2024/03/20 10:15:24 [notice] 9217#9217: ModSecurity-nginx v1.0.3 (rules loaded inline/local/remote: 0/823/0)
2024/03/20 10:15:28 [notice] 1#1: ModSecurity-nginx v1.0.3 (rules loaded inline/local/remote: 0/823/0)
2024/03/20 10:15:28 [notice] 1#1: using the "epoll" event method
2024/03/20 10:15:28 [notice] 1#1: nginx/1.25.3
2024/03/20 10:15:28 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14) 
2024/03/20 10:15:28 [notice] 1#1: OS: Linux 6.1.0-18-amd64
2024/03/20 10:15:28 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2024/03/20 10:15:28 [notice] 1#1: start worker processes
2024/03/20 10:15:28 [notice] 1#1: start worker process 90
2024/03/20 10:15:28 [notice] 1#1: start worker process 91
2024/03/20 10:15:28 [notice] 1#1: start worker process 92
2024/03/20 10:15:28 [notice] 1#1: start worker process 93
2024/03/20 10:15:28 [notice] 1#1: start worker process 94
2024/03/20 10:15:28 [notice] 1#1: start worker process 95
2024/03/20 10:15:28 [notice] 1#1: start worker process 96
2024/03/20 10:15:28 [notice] 1#1: start worker process 97
2024/03/20 10:15:59 [info] 91#91: *3 epoll_wait() reported that client prematurely closed connection, so upstream connection is closed too while sending request to upstream, client: 135.181.116.160, server: , request: "POST /inbox HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "fedi.0x42.ch"
2024/03/20 10:15:59 [info] 92#92: *5 client 127.0.0.1 closed keepalive connection

where there are then lot of entries like the last two lines (mainly the last line).

Besindes this I only have the Modsecurity statement for the detection rule with level info and the log statement of the blocking rule with level error.

@dune73
Copy link
Member

dune73 commented Mar 20, 2024

I generally agree. Yet, the Center for Internet security advocates running Nginx on level "info" in the CIS Nginx Benchmark, a widespread hardening guide.

Personally, I am a bit torn on that question, but Nginx should definitely log ModSec warnings on level warn, there can be no doubt about that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants