Closed airween closed 3 years ago
@airween appreciate if you'll be able to check this one: https://github.com/defanator/ModSecurity-nginx/commit/51cbaf3c97addd581973a62a6103221831a11c7e
(I'm still trying to figure out the better way(s) of handling this.)
Hi @defanator,
so many thanks for quick work - looks like it works as we expect:
$ curl -vvv 'http://localhost/modsec-full/ncl_items.shtml'
* Expire in 0 ms for 6 (transfer 0x55aac808bf50)
...
* Expire in 1 ms for 1 (transfer 0x55aac808bf50)
* Trying ::1...
* TCP_NODELAY set
* Expire in 149997 ms for 3 (transfer 0x55aac808bf50)
* Expire in 200 ms for 4 (transfer 0x55aac808bf50)
* connect to ::1 port 80 failed: Connection refused
* Trying 127.0.0.1...
* TCP_NODELAY set
* Expire in 149997 ms for 3 (transfer 0x55aac808bf50)
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /modsec-full/ncl_items.shtml HTTP/1.1
> Host: localhost
> User-Agent: curl/7.64.0
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< Server: nginx/1.19.6
< Date: Mon, 25 Jan 2021 16:02:18 GMT
< Content-Type: text/html
< Content-Length: 153
< Connection: keep-alive
<
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.19.6</center>
</body>
</html>
In the error.log I got:
2021/01/25 16:02:18 [error] 31577#31577: *2 [client 127.0.0.1] ModSecurity: Access denied with code 403 (phase 1). Matched "Operator `Eq' with parameter `1' against variable `TX:TRIGGER_PHASE1' (Value: `1' ) [file "/etc/nginx/modsec/main.conf"] [line "10"] [id "901112"] [rev ""] [msg ""] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "127.0.0.1"] [uri "/modsec-full/ncl_items.shtml"] [unique_id "1611590538"] [ref ""], client: 127.0.0.1, server: , request: "GET /modsec-full/ncl_items.shtml HTTP/1.1", host: "localhost"
2021/01/25 16:02:18 [error] 31577#31577: *2 [client 127.0.0.1] ModSecurity: Access denied with code 403 (phase 3). Matched "Operator `Eq' with parameter `1' against variable `TX:TRIGGER_PHASE3' (Value: `1' ) [file "/etc/nginx/modsec/main.conf"] [line "12"] [id "901131"] [rev ""] [msg ""] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "127.0.0.1"] [uri "/modsec-full/ncl_items.shtml"] [unique_id "1611590538"] [ref ""], client: 127.0.0.1, server: , request: "GET /modsec-full/ncl_items.shtml HTTP/1.1", host: "localhost"
2021/01/25 16:02:18 [info] 31577#31577: *2 ModSecurity: Warning. Matched "Operator `Eq' with parameter `1' against variable `TX:TRIGGER_PHASE5' (Value: `1' ) [file "/etc/nginx/modsec/main.conf"] [line "14"] [id "901151"] [rev ""] [msg "This is the phase 5."] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "127.0.0.1"] [uri "/modsec-full/ncl_items.shtml"] [unique_id "1611590538"] [ref ""] while logging request, client: 127.0.0.1, server: , request: "GET /modsec-full/ncl_items.shtml HTTP/1.1", host: "localhost"
I'm using the temporary small rule set above (with 5 rules and 3 SecAction
's).
But I'm still wondering why did phase:3 rule was triggered when phase:1 rule intervened the transaction? (Phase:5 obliviously triggered)
Thanks again!
But I'm still wondering why did phase:3 rule was triggered when phase:1 rule intervened the transaction? (Phase:5 obliviously triggered)
That's a great question.
According to the https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-(v2.x)#processing-phases diagram, phase 3 means "response headers", right?
Exactly.
But if one of the request header contains an attack, and the engine blocks the request (with deny
action), I don't think that it needs to check the next phases (except 5th).
The next question is: if the phase:3 rules activated, then it means the engine got the response from the server/upstream? But the server/upstream sends back the response headers only when it got the request body too (eg. if the request is POST
).
So the request header contains an attack what we wants to block, it no matters, the Nginx sends the request body (without inspect), server got it? (I don't know is that true or not, just asking).
Here is the relevant part of Apache's error.log:
[Mon Jan 25 16:36:55.880628 2021] [:error] [pid 645628] [client ::1:50687] [client ::1] ModSecurity: Warning. Matched phrase "nikto" at REQUEST_HEADERS:User-Agent. [file "/usr/share/modsecurity-crs/rules/REQUEST-913-SCANNER-DETECTION.conf"] [line "54"] [id "913100"] [msg "Found User-Agent associated with security scanner"] [data "Matched Data: nikto found within REQUEST_HEADERS:User-Agent: nikto"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.1"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-reputation-scanner"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/118/224/541/310"] [tag "PCI/6.5.10"] [hostname "localhost"] [uri "/"] [unique_id "YA7zpwcT9vAISkIAefAufgAAAAA"]
[Mon Jan 25 16:36:55.890242 2021] [:error] [pid 645628] [client ::1:50687] [client ::1] ModSecurity: Access denied with code 403 (phase 1). Operator GE matched 5 at TX:anomaly_score. [file "/usr/share/modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "166"] [id "949111"] [msg "Inbound Anomaly Score Exceeded in phase 1 (Total Score: 5)"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.1"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "localhost"] [uri "/"] [unique_id "YA7zpwcT9vAISkIAefAufgAAAAA"]
[Mon Jan 25 16:36:55.891256 2021] [:error] [pid 645628] [client ::1:50687] [client ::1] ModSecurity: Warning. Operator GE matched 5 at TX:inbound_anomaly_score. [file "/usr/share/modsecurity-crs/rules/RESPONSE-980-CORRELATION.conf"] [line "91"] [id "980130"] [msg "Inbound Anomaly Score Exceeded (Total Inbound Score: 5 - SQLI=0,XSS=0,RFI=0,LFI=0,RCE=0,PHPI=0,HTTP=0,SESS=0): individual paranoia level scores: 5, 0, 0, 0"] [ver "OWASP_CRS/3.3.1"] [tag "event-correlation"] [hostname "localhost"] [uri "/"] [unique_id "YA7zpwcT9vAISkIAefAufgAAAAA"]
Here I'm using CRS with blocking_early feature, so the 1st line just a warning, where engine sets the anomaly score. The second line shows that the engine blocked the transaction - and there isn't more phase, only the logging (phase:5).
(I don't know is that true or not, just asking).
Meanwhile I've checked this functionality: if any phase:1 rule intervened transaction, then the request body won't send to server.
@airween could you please check https://github.com/SpiderLabs/ModSecurity-nginx/pull/240 incl. new test case for the issue? Does it look correct?
Hi @defanator,
looks like nothing changed in behavior since the previous version:
* Expire in 0 ms for 6 (transfer 0x5569115f7f50)
...
* Expire in 1 ms for 1 (transfer 0x5569115f7f50)
* Trying ::1...
* TCP_NODELAY set
* Expire in 149997 ms for 3 (transfer 0x5569115f7f50)
* Expire in 200 ms for 4 (transfer 0x5569115f7f50)
* connect to ::1 port 80 failed: Connection refused
* Trying 127.0.0.1...
* TCP_NODELAY set
* Expire in 149997 ms for 3 (transfer 0x5569115f7f50)
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /index.php HTTP/1.1
> Host: localhost
> User-Agent: curl/7.64.0
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< Server: nginx/1.19.6
< Date: Wed, 03 Feb 2021 14:25:59 GMT
< Content-Type: text/html
< Content-Length: 153
< Connection: keep-alive
<
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.19.6</center>
</body>
</html>
* Connection #0 to host localhost left intact
error.log:
2021/02/03 14:25:43 [notice] 19742#19742: ModSecurity-nginx v1.0.1 (rules loaded inline/local/remote: 0/42/0)
2021/02/03 14:25:43 [notice] 19742#19742: using the "epoll" event method
2021/02/03 14:25:43 [notice] 19742#19742: nginx/1.19.6
2021/02/03 14:25:43 [notice] 19742#19742: built by gcc 8.3.0 (Debian 8.3.0-6)
2021/02/03 14:25:43 [notice] 19742#19742: OS: Linux 4.19.0-9-amd64
2021/02/03 14:25:43 [notice] 19742#19742: getrlimit(RLIMIT_NOFILE): 131072:131072
2021/02/03 14:25:43 [notice] 19743#19743: start worker processes
2021/02/03 14:25:43 [notice] 19743#19743: start worker process 19744
2021/02/03 14:25:59 [error] 19744#19744: *1 [client 127.0.0.1] ModSecurity: Access denied with code 403 (phase 1). Matched "Operator `Eq' with parameter `1' against variable `TX:TRIGGER_PHASE1' (Value: `1' ) [file "/etc/nginx/modsec/main.conf"] [line "10"] [id "901112"] [rev ""] [msg ""] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "127.0.0.1"] [uri "/index.php"] [unique_id "1612362359"] [ref ""], client: 127.0.0.1, server: , request: "GET /index.php HTTP/1.1", host: "localhost"
2021/02/03 14:25:59 [error] 19744#19744: *1 [client 127.0.0.1] ModSecurity: Access denied with code 403 (phase 3). Matched "Operator `Eq' with parameter `1' against variable `TX:TRIGGER_PHASE3' (Value: `1' ) [file "/etc/nginx/modsec/main.conf"] [line "12"] [id "901131"] [rev ""] [msg ""] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "127.0.0.1"] [uri "/index.php"] [unique_id "1612362359"] [ref ""], client: 127.0.0.1, server: , request: "GET /index.php HTTP/1.1", host: "localhost"
2021/02/03 14:25:59 [info] 19744#19744: *1 ModSecurity: Warning. Matched "Operator `Eq' with parameter `1' against variable `TX:TRIGGER_PHASE5' (Value: `1' ) [file "/etc/nginx/modsec/main.conf"] [line "14"] [id "901151"] [rev ""] [msg "This is the phase 5."] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "127.0.0.1"] [uri "/index.php"] [unique_id "1612362359"] [ref ""] while logging request, client: 127.0.0.1, server: , request: "GET /index.php HTTP/1.1", host: "localhost"
The second Access denied with code 403
is still presents, which is weird.
The response is (still) correct now.
@airween I've just updated PR with https://github.com/SpiderLabs/ModSecurity-nginx/pull/240/commits/4f26b48998db5119ca818b0909b6b14e08ebb544 - this is kinda experimental change but it addresses the issue with extra processing. Could you please check once again? TIA!
I think I've used that commit:
$ git branch
issue238
issue238-approach2
* issue238-fix
master
$ git log -1
commit 4f26b48998db5119ca818b0909b6b14e08ebb544 (HEAD -> issue238-fix, defan/issue238-fix)
Author: Andrei Belov <defanator@gmail.com>
Date: Wed Feb 3 18:13:38 2021 +0300
Avoid extra processing of subsequent interventions if one was already triggered
$ git describe
v1.0.1-20-g4f26b48
Then I recompiled the Nginx with ./configure --with-compat --add-dynamic-module=../ModSecurity-nginx-issue238 ...
, make ... make install
, check the module time and date (it's freshly compiled), and restart Nginx.
This gives me the result above.
@airween that's weird because that commit effectively removes 2nd error log entry for phase 3 - here's what I'm getting here locally with the test case introduced:
test@vagrant:~$ cat /tmp/nginx-test-17DrQtuh35/error.log
2021/02/03 18:16:44 [notice] 7947#7947: ModSecurity-nginx v1.0.1 (rules loaded inline/local/remote: 26/0/0)
2021/02/03 18:16:44 [notice] 7947#7947: using the "epoll" event method
2021/02/03 18:16:44 [notice] 7947#7947: nginx/1.19.6
2021/02/03 18:16:44 [notice] 7947#7947: built by gcc 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)
2021/02/03 18:16:44 [notice] 7947#7947: OS: Linux 4.15.0-128-generic
2021/02/03 18:16:44 [notice] 7947#7947: getrlimit(RLIMIT_NOFILE): 131072:131072
2021/02/03 18:16:44 [notice] 7947#7947: start worker processes
2021/02/03 18:16:44 [notice] 7947#7947: start worker process 7948
2021/02/03 18:16:44 [error] 7948#7948: *1 [client 127.0.0.1] ModSecurity: Access denied with code 403 (phase 1). Matched "Operator `Eq' with parameter `1' against variable `TX:TRIGGER_PHASE1' (Value: `1' ) [file "<<reference missing or not informed>>"] [line "9"] [id "901111"] [rev ""] [msg ""] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "127.0.0.1"] [uri "/early-block"] [unique_id "161237620483.421439"] [ref ""], client: 127.0.0.1, server: localhost, request: "GET /early-block HTTP/1.0", host: "localhost"
2021/02/03 18:16:44 [info] 7948#7948: *1 ModSecurity: Warning. Matched "Operator `Eq' with parameter `1' against variable `TX:TRIGGER_PHASE5' (Value: `1' ) [file "<<reference missing or not informed>>"] [line "13"] [id "901151"] [rev ""] [msg "This is the phase 5."] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "127.0.0.1"] [uri "/early-block"] [unique_id "161237620483.421439"] [ref ""] while logging request, client: 127.0.0.1, server: localhost, request: "GET /early-block HTTP/1.0", host: "localhost"
2021/02/03 18:16:44 [notice] 7947#7947: signal 3 (SIGQUIT) received from 7945, shutting down
2021/02/03 18:16:44 [notice] 7948#7948: gracefully shutting down
2021/02/03 18:16:44 [notice] 7948#7948: exiting
2021/02/03 18:16:44 [notice] 7948#7948: exit
2021/02/03 18:16:44 [notice] 7947#7947: signal 17 (SIGCHLD) received from 7948
2021/02/03 18:16:44 [notice] 7947#7947: worker process 7948 exited with code 0
2021/02/03 18:16:44 [notice] 7947#7947: exit
ModSecurity-nginx v1.0.1 (rules loaded inline/local/remote: 26/0/0)
can you show me your config?
Just because I have
ModSecurity-nginx v1.0.1 (rules loaded inline/local/remote: 0/42/0)
How can I debug that I'm using the right connector? Eg. increased log level shows the git version...? Output of strings ngx_http_modsecurity_module.so
?
Ah, sorry - I was confused and compiled the module but used a previously installed code.
I can confirm, after the last commit the connector works as we expect:
2021/02/03 19:49:02 [notice] 29997#29997: ModSecurity-nginx v1.0.1 (rules loaded inline/local/remote: 0/42/0)
2021/02/03 19:49:02 [notice] 29997#29997: using the "epoll" event method
2021/02/03 19:49:02 [notice] 29997#29997: nginx/1.19.6
2021/02/03 19:49:02 [notice] 29997#29997: built by gcc 8.3.0 (Debian 8.3.0-6)
2021/02/03 19:49:02 [notice] 29997#29997: OS: Linux 4.19.0-9-amd64
2021/02/03 19:49:02 [notice] 29997#29997: getrlimit(RLIMIT_NOFILE): 131072:131072
2021/02/03 19:49:02 [notice] 29999#29999: start worker processes
2021/02/03 19:49:02 [notice] 29999#29999: start worker process 30000
2021/02/03 19:49:11 [error] 30000#30000: *1 [client 127.0.0.1] ModSecurity: Access denied with code 403 (phase 1). Matched "Operator `Eq' with parameter `1' against variable `TX:TRIGGER_PHASE1' (Value: `1' ) [file "/etc/nginx/modsec/main.conf"] [line "10"] [id "901112"] [rev ""] [msg ""] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "127.0.0.1"] [uri "/index.php"] [unique_id "1612381751"] [ref ""], client: 127.0.0.1, server: , request: "GET /index.php HTTP/1.1", host: "localhost"
2021/02/03 19:49:11 [info] 30000#30000: *1 ModSecurity: Warning. Matched "Operator `Eq' with parameter `1' against variable `TX:TRIGGER_PHASE5' (Value: `1' ) [file "/etc/nginx/modsec/main.conf"] [line "14"] [id "901151"] [rev ""] [msg "This is the phase 5."] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "127.0.0.1"] [uri "/index.php"] [unique_id "1612381751"] [ref ""] while logging request, client: 127.0.0.1, server: , request: "GET /index.php HTTP/1.1", host: "localhost"
with this curl
request:
$ curl -vvv 'http://localhost/index.php'
* Expire in 0 ms for 6 (transfer 0x5655364b8f50)
...
* Expire in 1 ms for 1 (transfer 0x5655364b8f50)
* Trying ::1...
* TCP_NODELAY set
* Expire in 149997 ms for 3 (transfer 0x5655364b8f50)
* Expire in 200 ms for 4 (transfer 0x5655364b8f50)
* connect to ::1 port 80 failed: Connection refused
* Trying 127.0.0.1...
* TCP_NODELAY set
* Expire in 149997 ms for 3 (transfer 0x5655364b8f50)
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /index.php HTTP/1.1
> Host: localhost
> User-Agent: curl/7.64.0
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< Server: nginx/1.19.6
< Date: Wed, 03 Feb 2021 19:49:11 GMT
< Content-Type: text/html
< Content-Length: 153
< Connection: keep-alive
<
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.19.6</center>
</body>
</html>
* Connection #0 to host localhost left intact
Sorry for my mistake.
@airween no problem, thanks for confirming.
I've added a test case for this to the modsecurity.t, so the config can be found here: https://github.com/defanator/ModSecurity-nginx/blob/issue238-fix/tests/modsecurity.t#L40-L126
(You also may set the TEST_NGINX_LEAVE=yes env. variable to keep temporary nginx-test-*
directories after prove
run, that could be helpful in investigating particular cases.)
Thanks, I can check that later.
Is there any timing for merge (and/or make new release)?
@airween this change may have an effect on a couple of other opened issues/PRs (#90, #204) but given the state of those I think I'll merge this one and proceed with further investigation on possible error_page
effects a bit later.
For the release - I'd ask @zimmerle on their input here. I'm not the right guy here for making releases. :)
Ah, okay - thanks! :)
As we discussed in ModSecurity issue #2465, the nginx connector tries to do all the processing as early as possible.
The unexpected behavior is the server doesn't send the response headers to client. It can reproduced with this minimal rule set:
The request can be anything, the set contains 3 rules which will triggered always in phase 1, 3 and 5.
The disrputive action of these rules are
deny
which means the rule wants to terminate the process, but Nginx continues.