Description
Hi,
I have a question about the exact intended semantic of MATCHED_VARS / MATCHED_VARS_NAMES
The documentation is a little bit vague with
Similar to MATCHED_VAR_NAME except that it is a collection of all matches for the current operator check.
SecRule ARGS pattern "chain,deny,id:28"
SecRule MATCHED_VARS_NAMES "@eq ARGS:param"
And all the examples and also the CoreRuleset always have this form: The second rule in the chain is accessing the matches from the first rule. So far, so good.
But what should happen if we have multiple rules (without chain)
From the documentation and my intuition, I would assume that the request GET /?foo=1&bar=2&baz=2
against these rules
SecRule ARGS "@rx 1" "id:1, phase:1, pass"
SecRule ARGS "@rx 2" "id:2, phase:1, pass"
SecRule MATCHED_VARS_NAMES "@contains ARGS:foo" "id:3, phase:1, deny, status:403"
would not block in rule 3, because MATCHED_VARS_NAMES
should only contain the matches from rule 2: ARGS:bar
and ARGS:baz
at this point in time.
My assumption is:
After execution of rule 1:
- MATCHED_VAR_NAME = "ARGS:foo",
- MATCHED_VARS_NAMES = { "ARGS:foo" }
After execution of rule 2:
- MATCHED_VAR_NAME = "ARGS:baz" (or "ARGS:bar" depending on the order)
- MATCHED_VARS_NAMES = { "ARGS:bar", "ARGS:baz" }
So the 3rd rule should not match.
But when I add this as a regression test
[
{
"enabled":1,
"version_min":300000,
"title":"Testing Variables :: MATCHED_VARS_NAMES (3/3)",
"request":{
"uri":"/?foo=1&bar=2&baz=2",
"method":"GET"
},
"expected":{
"http_code": 200
},
"rules":[
"SecRuleEngine On",
"SecRule ARGS \"@rx 1\" \"id:1,phase:1,pass\"",
"SecRule ARGS \"@rx 2\" \"id:2,phase:1,pass\"",
"SecRule MATCHED_VARS_NAMES \"@contains ARGS:foo\" \"id:3,phase:1,deny,status:403\""
]
}
]
and run it with the latest build
commit aab47091b1b95add953a54bbd8769540d2dfc0fe (HEAD -> v3/master, origin/v3/master, origin/HEAD)
Merge: 990d99b1 797f7dc4
Date: Sun May 11 10:54:47 2025 +0200
the test is failing with
Test failed. From: test-cases/regression/avi.json.
Test name: Testing Variables :: MATCHED_VARS_NAMES (3/3).
Reason:
HTTP code mismatch. expecting: 200 got: 403
Debug log:
[174732390351.534433] [] [4] Initializing transaction
[174732390351.534433] [] [4] Transaction context created.
[174732390351.534433] [] [4] Starting phase CONNECTION. (SecRules 0)
[174732390351.534433] [] [9] This phase consists of 0 rule(s).
[174732390351.534433] [] [4] Starting phase URI. (SecRules 0 + 1/2)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Adding request argument (GET): name "foo", value "1"
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Adding request argument (GET): name "bar", value "2"
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Adding request argument (GET): name "baz", value "2"
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Starting phase REQUEST_HEADERS. (SecRules 1)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] This phase consists of 3 rule(s).
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] (Rule: 1) Executing operator "Rx" with param "1" against ARGS.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Target value: "1" (Variable: ARGS:foo)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Matched vars updated.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Target value: "2" (Variable: ARGS:bar)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Target value: "2" (Variable: ARGS:baz)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Rule returned 1.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Running (disruptive) action: pass.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [8] Running action pass
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] (Rule: 2) Executing operator "Rx" with param "2" against ARGS.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Target value: "1" (Variable: ARGS:foo)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Target value: "2" (Variable: ARGS:bar)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Matched vars updated.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Target value: "2" (Variable: ARGS:baz)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Matched vars updated.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Rule returned 1.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Running (disruptive) action: pass.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [8] Running action pass
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] (Rule: 3) Executing operator "Contains" with param "ARGS:foo" against MATCHED_VARS_NAMES.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Target value: "ARGS:foo" (Variable: MATCHED_VARS_NAMES:ARGS:foo)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Matched vars updated.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Target value: "ARGS:bar" (Variable: MATCHED_VARS_NAMES:ARGS:bar)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Target value: "ARGS:baz" (Variable: MATCHED_VARS_NAMES:ARGS:baz)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Rule returned 1.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Running action: status
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Running (disruptive) action: deny.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [8] Running action deny
[174732390351.534433] [/?foo=1&bar=2&baz=2] [8] Skipping this phase as this request was already intercepted.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Appending request body: 0 bytes. Limit set to: 0.000000
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Starting phase REQUEST_BODY. (SecRules 2)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] This phase consists of 0 rule(s).
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Starting phase RESPONSE_HEADERS. (SecRules 3)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] This phase consists of 0 rule(s).
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] Appending response body: 0 bytes. Limit set to: 0.000000
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Starting phase RESPONSE_BODY. (SecRules 4)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Response body is disabled, returning... 2
[174732390351.534433] [/?foo=1&bar=2&baz=2] [4] Starting phase LOGGING. (SecRules 5)
[174732390351.534433] [/?foo=1&bar=2&baz=2] [9] This phase consists of 0 rule(s).
[174732390351.534433] [/?foo=1&bar=2&baz=2] [8] Checking if this request is suitable to be saved as an audit log.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [8] Checking if this request is relevant to be part of the audit logs.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [5] Audit log engine was not set.
[174732390351.534433] [/?foo=1&bar=2&baz=2] [8] Request was relevant to be saved. Parts: 4430
Error log:
ModSecurity: Warning. Matched "Operator `Rx' with parameter `1' against variable `ARGS:foo' (Value: `1' ) [file "avi.json"] [line "2"] [id "1"] [rev ""] [msg ""] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname ""] [uri "/"] [unique_id "174732390351.534433"] [ref "o0,1v10,1"]
ModSecurity: Warning. Matched "Operator `Rx' with parameter `2' against variable `ARGS:baz' (Value: `2' ) [file "avi.json"] [line "3"] [id "2"] [rev ""] [msg ""] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname ""] [uri "/"] [unique_id "174732390351.534433"] [ref "o0,1v16,1o0,1v22,1"]
[client ] ModSecurity: Access denied with code 403 (phase 1). Matched "Operator `Contains' with parameter `ARGS:foo' against variable `MATCHED_VARS_NAMES:ARGS:foo' (Value: `ARGS:foo' ) [file "avi.json"] [line "4"] [id "3"] [rev ""] [msg ""] [data ""] [severity "0"] [ver ""] [maturity "0"] [accuracy "0"] [hostname ""] [uri "/"] [unique_id "174732390351.534433"] [ref "o0,8v30,8"]
I would consider this as a bug because apparently the collection MATCH_VARS_NAMES is never cleaned and collect all the data from all the previous rules.
If this is intended, I think at least a documentation update is needed and maybe the CoreRuleset rules should be adapted (other project, I know)