From f9d2fa124a51b9cee234d6a39cd72ed5609d4574 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Sun, 1 Nov 2026 18:54:46 +0100 Subject: [PATCH 1/2] Document that SESSION requires setsid The SESSION entry said that the collection becomes available only after setsid, which is not what libModSecurity v3 does: it accepts SESSION without setsid and stores the data without a session key, so every transaction that does the same shares it. In an LMDB build the keys don't include the collection name either, so such a variable can overwrite, and be overwritten by, a variable with the same name in another collection that has no key, for example GLOBAL without initcol. Say that SESSION must be initialized with setsid, add a warning with the v3 behavior, and link the setsid entry to it. Refs owasp-modsecurity/ModSecurity#2442 --- content/3-reference-manual/3-3-variables.md | 6 +++++- content/3-reference-manual/3-5-actions.md | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/content/3-reference-manual/3-3-variables.md b/content/3-reference-manual/3-3-variables.md index 5b32be6..fa812d3 100644 --- a/content/3-reference-manual/3-3-variables.md +++ b/content/3-reference-manual/3-3-variables.md @@ -1196,7 +1196,11 @@ This variable contains the local port that the web server (or reverse proxy) is {{< secmeta v2="yes" v3="yes" >}} -This variable is a collection that contains session information. It becomes available only after setsid is executed. In ModSecurity v2 a selector is mandatory (SESSION:name or SESSION:/regex/). For where libModSecurity v3 stores the data, see GLOBAL. +This variable is a collection that contains session information. It must be initialized with the [setsid]({{% relref "3-reference-manual/3-5-actions#setsid" %}}) action before it is read or written. In ModSecurity v2 a selector is mandatory (SESSION:name or SESSION:/regex/). For where libModSecurity v3 stores the data, see GLOBAL. + +{{% notice warning %}} +Use SESSION only after setsid has run with a non-empty session token, for example by checking the token in the rule that calls setsid, as in the example below. libModSecurity v3 does not reject SESSION without setsid: the data is then stored without a session key, so it is shared by every transaction that does the same, whatever session it belongs to. In a build that uses LMDB, it can also overwrite, and be overwritten by, a variable with the same name in another collection that has no key either, for example GLOBAL used without initcol. +{{% /notice %}} The following example shows how to initialize SESSION using setsid, how to use setvar to increase the SESSION.score values, how to set the SESSION.blocked variable, and finally, how to deny the connection based on the value: diff --git a/content/3-reference-manual/3-5-actions.md b/content/3-reference-manual/3-5-actions.md index 3f4ad57..91c66c2 100644 --- a/content/3-reference-manual/3-5-actions.md +++ b/content/3-reference-manual/3-5-actions.md @@ -1033,7 +1033,7 @@ SecRule REQUEST_COOKIES:PHPSESSID "!@rx ^$" \ Note -After the initialization takes place, the variable SESSION will be available for use in the subsequent rules. This action understands application namespaces (configured using SecWebAppId), and will use one if it is configured. +After the initialization takes place, the variable SESSION will be available for use in the subsequent rules. Don't use SESSION before setsid has run; see [SESSION]({{% relref "3-reference-manual/3-3-variables#session" %}}) for what happens in libModSecurity v3 if you do. This action understands application namespaces (configured using SecWebAppId), and will use one if it is configured. Setsid takes an individual variable, not a collection. Variables within an action, such as setsid, use the format [collection].[variable] . From 0ceb34ffc867cec138035cda46aa2ee0c1ffdc6b Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 3 Nov 2026 10:46:07 +0100 Subject: [PATCH 2/2] Skip the SESSION rules in the example if setsid didn't run The warning tells to use SESSION only after setsid, but the example only skipped the setsid call without a session cookie: rules 71-73 still ran, and every request without the cookie added to the same shared score. Add a rule that skips the SESSION rules to a SecMarker if SESSIONID is empty, renumber the rules after it, and say in the warning and in the introduction of the example how the example does it. --- content/3-reference-manual/3-3-variables.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/content/3-reference-manual/3-3-variables.md b/content/3-reference-manual/3-3-variables.md index fa812d3..418aecb 100644 --- a/content/3-reference-manual/3-3-variables.md +++ b/content/3-reference-manual/3-3-variables.md @@ -1199,10 +1199,10 @@ This variable contains the local port that the web server (or reverse proxy) is This variable is a collection that contains session information. It must be initialized with the [setsid]({{% relref "3-reference-manual/3-5-actions#setsid" %}}) action before it is read or written. In ModSecurity v2 a selector is mandatory (SESSION:name or SESSION:/regex/). For where libModSecurity v3 stores the data, see GLOBAL. {{% notice warning %}} -Use SESSION only after setsid has run with a non-empty session token, for example by checking the token in the rule that calls setsid, as in the example below. libModSecurity v3 does not reject SESSION without setsid: the data is then stored without a session key, so it is shared by every transaction that does the same, whatever session it belongs to. In a build that uses LMDB, it can also overwrite, and be overwritten by, a variable with the same name in another collection that has no key either, for example GLOBAL used without initcol. +Use SESSION only after setsid has run with a non-empty session token, for example by skipping the rules that use SESSION when SESSIONID is empty, as in the example below. libModSecurity v3 does not reject SESSION without setsid: the data is then stored without a session key, so it is shared by every transaction that does the same, whatever session it belongs to. In a build that uses LMDB, it can also overwrite, and be overwritten by, a variable with the same name in another collection that has no key either, for example GLOBAL used without initcol. {{% /notice %}} -The following example shows how to initialize SESSION using setsid, how to use setvar to increase the SESSION.score values, how to set the SESSION.blocked variable, and finally, how to deny the connection based on the value: +The following example shows how to initialize SESSION using setsid, how to skip the SESSION rules if there is no session, how to use setvar to increase the SESSION.score values, how to set the SESSION.blocked variable, and finally, how to deny the connection based on the value: ``` seclang # Initialize session storage @@ -1213,9 +1213,17 @@ SecRule REQUEST_COOKIES:PHPSESSID "!@rx ^$" \ nolog,\ setsid:%{REQUEST_COOKIES.PHPSESSID}" +# Skip the SESSION rules if setsid didn't run +SecRule SESSIONID "@rx ^$" \ + "id:71,\ + phase:2,\ + pass,\ + nolog,\ + skipAfter:END_SESSION_RULES" + # Increment session score on attack SecRule REQUEST_URI "@rx ^/cgi-bin/finger$" \ - "id:71,\ + "id:72,\ phase:2,\ pass,\ t:none,\ @@ -1224,10 +1232,12 @@ SecRule REQUEST_URI "@rx ^/cgi-bin/finger$" \ setvar:SESSION.score=+10" # Detect too many attacks in a session -SecRule SESSION:score "@gt 50" "id:72,phase:2,pass,setvar:SESSION.blocked=1" +SecRule SESSION:score "@gt 50" "id:73,phase:2,pass,setvar:SESSION.blocked=1" # Enforce session block -SecRule SESSION:blocked "@eq 1" "id:73,phase:2,deny,status:403" +SecRule SESSION:blocked "@eq 1" "id:74,phase:2,deny,status:403" + +SecMarker END_SESSION_RULES ``` ## SESSIONID