Chapter 8

Defending Session State

Now the general who wins a battle makes many calculations in his temple ere the battle is fought. The general who loses a battle makes but few calculations beforehand. Thus do many calculations lead to victory and few calculations to defeat: how much more no calculation at all! It is by attention to this point that I can foresee who is likely to win or lose.

Sun Tzu in The Art of War

ModSecurity has a robust session-based persistent storage mechanism that allows defenders to track and analyze a variety of data about application users.


Recipe 8-1: Detecting Invalid Cookies
This recipe shows you how to determine when attackers attempt to submit invalid cookie data.
Ingredients
  • OWASP ModSecurity Core Rule Set (CRS)
    • modsecurity_crs_40_appsensor_detection_point_2.3_session_exception.conf
  • ModSecurity
    • RESPONSE_HEADERS:Set-Cookie variable
    • REQUEST_HEADERS:Cookie variable
    • setsid action
    • setvar action
Session-Guessing Attacks
Although web application authentication serves as the front line defense against unauthorized access, developers tend to overlook an underlying weakness. An attacker does not have to successfully authenticate to the application to gain access. He must simply submit a valid SessionID when making his requests! This means that attackers often focus on analyzing the strength (length, character set, and entropy) of SessionIDs. If the SessionID data is weak and predictable, an attacker may be able to guess a valid value and thus assume another user’s active session.
Attackers may use many tools to analyze the strength of application SessionIDs, but we will show some examples using Burp Suite. In the proxy module of Burp Suite, you can right-click and send a previously captured transaction to another module. Figure 8-1 shows a session in the Sequencer module.

Figure 8-1: Burp Suite’s Sequencer module

c08f001.tif
As you can see, the Sequencer module has identified the amSessionId Set-Cookie response header data as the target token to analyze. When we click the “start capture” button, Burp Suite replays the same requests repeatedly. The purpose of this process is to obtain a large number of cookie samples so that they may be analyzed for randomness. As soon as you have captured at least 100 cookie samples, you may click the “analyse now” button to allow Sequencer to run its analysis tests. Figure 8-2 shows the resulting character-level analysis information for this cookie.

Figure 8-2: Burp Suite’s Sequencer module analysis results

c08f002.tif
The results indicate that the overall entropy of the amSessionId cookie data is extremely poor. The payload contains 12 characters, of which only certain character locations change. Figure 8-3 shows a dump of the actual amSessionId tokens.

Figure 8-3: Sample SessionIDs gathered by Sequencer

c08f003.tif
On the basis of this information, an attacker may attempt to guess valid session tokens by submitting them to the application within new Cookie request headers. If the tokens are correct, the application presents the attacker with the victim’s data. If they are wrong, the application will most likely redirect the attacker to a login page of some sort. It is during these cookie-guessing attempts that we want to generate alerts.
Detecting Invalid Cookie Submissions
How do you know if a cookie sent by a client is valid? The answer is surprisingly simple. The only valid cookies are the ones that the application sent to the client in a Set-Cookie response header. Therefore, the key to verifying that a cookie sent by a client is valid and not spoofed or forged is ensuring that the application sent it previously.

Caution
There is one exception to mention with regard to validating cookies, and that is if your application is legitimately creating cookie data using client-side code such as JavaScript. In this case, you should be careful to validate only cookie data issued by the application.

The OWASP ModSecurity Core Rule Set (CRS) has a rule file called modsecurity_crs_40_appsensor_detection_point_2.3_session_exception.conf. It tracks Set-Cookie response header data sent to the client and generates alerts if a client submits an invalid SessionID.
#
# -=[ OWASP AppSensor Detection Points - Session Exceptions (SE) 
# Category ]=-
#
# - https://www.owasp.org/index.php/
# AppSensor_DetectionPoints#SessionException
#
#
# -=[ Initiate Session-based Persistent Storage ]=-
#
# This rule will identify the outbound Set-Cookie/Set-Cookie2 SessionID
# data and then initiate the proper ModSecurity session-based persistent
# storage using setsid.
#
# We also set a Session Variable (session.valid) to mark this SessionID
# as valid since the application returns this to the client in a
# Set-Cookie header.
#
# This is used later to enforce -
# - 2.3.2 SE2: Adding New Cookie
#
# Capture Source IP Network Block Data.  This is used later to enforce -
# - 2.3.5 SE5: Source Location Changes During Session
#
# Capture User-Agent Hash Data.  This is used later to enforce -
# - 2.3.6 SE6: Change of User Agent Mid Session
#
SecRule RESPONSE_HEADERS:/Set-Cookie2?/ "(?i:(j?sessionid|(php)?sessid|
(asp|jserv|jw)?session[-_]?(id)?|cf(id|token)|sid)=([^s]+);s?)" 
"chain,phase:3,id:'981062',t:none,pass,nolog,capture,
setsid:%{TX.6},setvar:session.sessionid=%{TX.6},
setvar:session.valid=1"
        SecRule REMOTE_ADDR "^(d{1,3}.d{1,3}.d{1,3}.)"  "chain,
capture,setvar:session.ip_block=%{tx.1}"
                SecRule REQUEST_HEADERS:User-Agent ".*" "t:none,t:sha1,
t:hexEncode,setvar:session.ua=%{matched_var}"
By default, these rules look for the most common session cookie names used in public applications:
  • JSESSIONID
  • SESSIONID
  • PHPSESSID
  • SESSID
  • ASPSESSIONID
  • JSERVSESSION
  • JWSESSIONDID
  • SESSION_ID
  • SESSION-ID
  • CFID
  • CFTOKEN
  • SID
If you find that your application uses a different SessionID token name, you can update the regular expression. Here is an updated version that also tracks the amSessionId token:
SecRule RESPONSE_HEADERS:/Set-Cookie2?/ "(?i:(j?sessionid|(php)?sessid|
(asp|jserv|jw|am)?session[-_]?(id)?|cf(id|token)|sid)=([^s]+);s?)"
 "chain,phase:3,id:'981062',t:none,
pass,nolog,capture,setsid:%{TX.6},setvar:session.sessionid=%{TX.6},
setvar:session.valid=1"
        SecRule REMOTE_ADDR "^(d{1,3}.d{1,3}.d{1,3}.)"  "chain,
capture,setvar:session.ip_block=%{tx.1}"
                SecRule REQUEST_HEADERS:User-Agent ".*" "t:none,t:sha1,
t:hexEncode,setvar:session.ua=%{matched_var}"
Also note the bold actions. These actions use setsid to initiate the session-based persistent storage in ModSecurity and also use setvar to create a new variable that marks this session value as valid. Now, when a client submits a request with an amSessionId cookie value, the following rule inspects the data:
#
# -=[ SE2: Adding New Cookie ]=-
#
# - https://www.owasp.org/index.php/
# AppSensor_DetectionPoints#SE2:_Adding_New_Cookie
#
# These rules will validate that the SessionID being submitted by the
# client is valid
#
SecRule REQUEST_COOKIES:'/(j?sessionid|(php)?sessid|(asp|jserv|jw|am)
?session[-_]?(id)?|cf(id|token)|sid)/' ".*" "chain,phase:1,id:'981054',
t:none,block,msg:'Invalid SessionID Submitted.',
logdata:'SessionID Submitted: %{tx.sessionid}',
tag:'OWASP_AppSensor/SE2',setsid:%{matched_var},
setvar:tx.sessionid=%{matched_var},skipAfter:END_SESSION_STARTUP"
        SecRule &SESSION:VALID "!@eq 1" "t:none,
setvar:tx.anomaly_score=+%{tx.critical_anomaly_score},
setvar:tx.%{rule.id}-WEB_ATTACK/INVALID_SESSIONID-%{matched_var_name}=%
{tx.0}"
This rule uses the amSessionId value in a setsid action to open the persistent storage collection data for that token. The next step in the rule chain logic is to verify that the session collection has the valid variable set. If the session token was indeed handed out by the application in a Set-Cookie response header, ModSecurity would have set this value previously. On the other hand, if the cookie data is bogus, the variable would not exist, and this rule catches it. Figure 8-4 shows a request sent with the Live HTTP Headers Firefox web browser plug-in where it includes a bogus amSessionId value of 111021167669.

Figure 8-4: Sending a bogus amSessionID value

c08f004.tif
The ModSecurity rules catch this fake cookie data and generate an alert similar to this:
[Sat May 19 14:03:33 2012] [error] [client 192.168.1.103] ModSecurity:
 Warning. Match of "eq 1" against "&SESSION:VALID" required. [file 
"/usr/local/apache/conf/crs/base_rules/modsecurity_crs_40_appsensor
_detection_point_2.3_session_exception.conf"] [line "59"] 
[id "981054"] [msg "Invalid SessionID Submitted."] 
[data "SessionID Submitted: 111021167669"]
 [tag "OWASP_AppSensor/SE2"] [hostname "demo.testfire.net"]
 [uri "/bank/login.aspx"] [unique_id "T7fgdcCoqAEAASnwFusAAAAB"]


Recipe 8-2: Detecting Cookie Tampering
This recipe shows you how to identify when attackers attempt to change cookie data.
Ingredients
  • OWASP ModSecurity Core Rule Set (CRS)
    • modsecurity_crs_40_appsensor_detection_point_2.3_session_exception.conf
  • ModSecurity
    • RESPONSE_HEADERS:Set-Cookie variable
    • REQUEST_HEADERS:Cookie variable
    • setsid action
    • setvar action
Because applications take data submitted by clients within Cookie fields and act upon them, they become a ripe target for attackers. Cookie data may tell the application who you are, whether you have authenticated successfully, or what your role is within the application. Malicious users attempt to circumvent this logic by manipulating cookies to try to gain unauthorized access to data.
Sample Cookie-Based SQL Injection Attack
As an example, let’s look at the following dZemo bank login transaction:
POST /bank/login.aspx HTTP/1.1
Host: demo.testfire.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:12.0) 
Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;
q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Proxy-Connection: keep-alive
Referer: http://demo.testfire.net/bank/login.aspx
Content-Type: application/x-www-form-urlencoded
Content-Length: 42
 
Uid=bsmith&passw=Pa$$wd123&btnSubmit=Login
 
HTTP/1.1 302 Found
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Location: /bank/main.aspx
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: text/html; charset=utf-8
Content-Length: 136
Set-Cookie: ASP.NET_SessionId=ejexra45gejpurqfud5yif55; path=/; 
HttpOnly
Set-Cookie: amSessionId=174040318709; path=/
Set-Cookie: amUserInfo=UserName=YnNtaXRo&Password=UGEkJHdkMTIz; 
expires=Sat, 19-May-2012 23:07:46 GMT; path=/
Set-Cookie: amUserId=1; path=/
If the user submits the correct credentials, the application issues two new Set-Cookie response headers called amUserInfo and amUserId. The client sends these cookies in subsequent transactions, and the application allows access to certain resources. In the following request, however, an attacker is appending some SQL Injection code to the end of the amUserID cookie value:
GET / bank/transaction.aspx HTTP/1.1
Host: demo.testfire.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:12.0) 
Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;
q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Proxy-Connection: keep-alive
Referer: http://demo.testfire.net/bank/main.aspx
Cookie: ASP.NET_SessionId=ejexra45gejpurqfud5yif55; 
amSessionId=174040318709; 
amUserInfo=UserName=YnNtaXRo&Password=UGEkJHdkMTIz; 
amUserId=1 union select username,password,3,4 from users
Figure 8-5 shows the results of this attack. The attacker successfully injects a SQL query that dumps the username and password data from the back-end database.
To prevent this attack, we need to be able to track Set-Cookie response header data and ensure that these payloads are not manipulated.

Figure 8-5: Successful SQL Injection attack in amUserId cookie

c08f005.tif
Detecting Cookie Manipulation Attacks
To identify Cookie manipulations, we need to use ModSecurity’s session-based persistent storage to save Set-Cookie response header data. The OWASP ModSecurity Core Rule Set has a rule file called modsecurity_crs_40_appsensor_detection_point_2.3_session_exception.conf that tracks Set-Cookie response header data sent to the client:
#
# -=[ OWASP AppSensor Detection Points - Session Exceptions (SE) 
# Category ]=-
#
# - https://www.owasp.org/index.php/
# AppSensor_DetectionPoints#SessionException
#
#
# -=[ Initiate Session-based Persistent Storage ]=-
#
# This rule will identify the outbound Set-Cookie/Set-Cookie2 
# SessionID data and then initiate the proper ModSecurity 
# session-based persistent storage using setsid.
#
# We also set a Session Variable (session.valid) to mark this 
# SessionID as valid since the application returns this to the 
# client in a Set-Cookie header.
#
# This is used later to enforce -
# - 2.3.2 SE2: Adding New Cookie
#
# Capture Source IP Network Block Data.  This is used later to 
# enforce –
#
# - 2.3.5 SE5: Source Location Changes During Session
#
# Capture User-Agent Hash Data.  This is used later to enforce -
# - 2.3.6 SE6: Change of User Agent Mid Session
#
SecRule RESPONSE_HEADERS:/Set-Cookie2?/ "(?i:(j?sessionid|(php)?
sessid|(asp|jserv|jw)?session[-_]?(id)?|cf(id|token)|sid)=([^s]
+);s?)" "chain,phase:3,id:'981062',t:none,pass,nolog,
capture,setsid:%{TX.6},setvar:session.sessionid=%{TX.6},
setvar:session.valid=1"
        SecRule REMOTE_ADDR "^(d{1,3}.d{1,3}.d{1,3}.)"  
"chain,capture,setvar:session.ip_block=%{tx.1}"
                SecRule REQUEST_HEADERS:User-Agent ".*" 
"t:none,t:sha1,t:hexEncode,setvar:session.ua=%{matched_var}"
By default, these rules look for the most common session cookie names used in public applications:
  • JSESSIONID
  • SESSIONID
  • PHPSESSID
  • SESSID
  • ASPSESSIONID
  • JSERVSESSION
  • JWSESSIONDID
  • SESSION_ID
  • SESSION-ID
  • CFID
  • CFTOKEN
  • SID
This rule also matches the ASP.NET_SessionId cookie data and creates a local collection. When the login request that was shown previously is sent, the Set-Cookie response header data is inspected by the following ruleset:
#
# -=[ Save Set-Cookie Name/Value Pairs ]=-
#
SecRule RESPONSE_HEADERS:/Set-Cookie2?/ "^(.*?)=(.*?);" "chain,
phase:3,id:'981063',t:none,nolog,pass,capture,
setvar:'session.cookie_list=%{session.cookie_list} %{tx.0}'"
        SecRule SESSION:COOKIE_LIST ".*" "t:trimLeft,
setvar:session.cookie_list=%{matched_var}"
These rules take each Set-Cookie token and append it to a new local Cookie value that mimics what it will look like when a client resubmits this data within subsequent requests. Here is an example of the data that is stored in the session collection for the ASP.NET_SessionID value:
collection_store: Retrieving collection (name "default_SESSION", 
filename "/tmp/default_SESSION")
Wrote variable: name "__expire_KEY", value "1337464463".
Wrote variable: name "KEY", value "ejexra45gejpurqfud5yif55".
Wrote variable: name "TIMEOUT", value "3600".
Wrote variable: name "__key", value "ejexra45gejpurqfud5yif55".
Wrote variable: name "__name", value "default_SESSION".
Wrote variable: name "CREATE_TIME", value "1337460863".
Wrote variable: name "UPDATE_COUNTER", value "1".
Wrote variable: name "sessionid", value "174040318709".
Wrote variable: name "valid", value "1".
Wrote variable: name "ip_block", value "192.168.1.".
Wrote variable: name "ua", value
 "e0d145047e2c03dbd8f547b68fce532698d0e57e".
Wrote variable: name "cookie_list", value "ASP.NET_SessionId=
ejexra45gejpurqfud5yif55; amSessionId=174040318709;
 amUserInfo=UserName=JyBvciAnMSc9JzEnOy0t&Password=ZmRhZmE=; 
amUserId=1;".
Wrote variable: name "LAST_UPDATE_TIME", value "1337460863".
Persisted collection (name "default_SESSION", key
 "ejexra45gejpurqfud5yif55").
Notice that the bold entry has captured the new Set-Cookie data into a variable list that will then be validated on subsequent requests with the following ruleset:
SecRule REQUEST_COOKIES ".*" "chain,phase:1,id:'958233',t:none,block
,msg:'Invalid Cookie Data Submitted.',logdata:'Cookie Data: 
%{matched_var}',tag:'OWASP_AppSensor/SE1',
setvar:'tx.req_cookie_%{matched_var_name}=%{matched_var};'"
        SecRule TX:/REQ_COOKIE_/ "!@within %{session.cookie_list}"
 "setvar:tx.cookie_name=%{tx.1},setvar:tx.anomaly_score=+%
{tx.critical_anomaly_score},setvar:tx.%{rule.id}-WEB_ATTACK/
INVALID_SESSIONID-%{matched_var_name}=%{tx.0}"
If an attacker were to send the same SQL Injection attack in the amUserId cookie field, the following alert would be generated:
[Sun May 20 09:51:54 2012] [error] [client 192.168.1.103] 
ModSecurity: Warning. Match of "within %{session.cookie_list}" 
against "TX:req_cookie_REQUEST_COOKIES:amUserId" required. [file 
"/usr/local/apache/conf/crs/base_rules/modsecurity_crs_40_appsensor_
detection_point_2.3_session_exception.conf"] [line "66"] 
[id "958233"] [msg "Invalid Cookie Data Submitted."] [data "Cookie 
Data: 1 union select username,password,3,4 from users;"] 
[tag "OWASP_AppSensor/SE1"] [hostname "demo.testfire.net"] 
[uri "/bank/transaction.aspx"] 
[unique_id "T7j2@sCoAWcAAF2MIwcAAAAB"]


Recipe 8-3: Enforcing Session Timeouts
This recipe shows you how to utilize session timeouts to limit the length of active sessions.
Ingredients
  • OWASP ModSecurity Core Rule Set (CRS)
    • modsecurity_crs_40_appsensor_detection_point_2.3_session_exception.conf
  • ModSecurity
    • RESPONSE_HEADERS:Set-Cookie variable
    • REQUEST_HEADERS:Cookie variable
    • setsid action
    • setvar action
    • expirevar action
How long should an application session be valid? Five minutes? An hour? Forever? The answer depends greatly on your unique application and the processing that is required. The Open Web Application Security Project (OWASP) suggests the following expiration ranges in the Session Management Cheat Sheet1 document:
  • Between 2-5 minutes for high-value applications
  • Between 15-30 minutes for low-risk applications
Although the expiration ranges may differ, it is recommended that you set some type of session timeout. This will minimize an attacker’s window of opportunity for session hijacking if he is successful in obtaining your current SessionID. An application session timeout therefore is important, because it forces a user to reauthenticate after a period of time. This recipe discusses two types of session timeouts: session inactivity and total session duration.
Session Inactivity Timeout
Session inactivity occurs when a user does not interact with the application for a period of time. Consider the following scenario:
1. A user authenticates to the application.
2. The application has a session inactivity timeout of 5 minutes.
3. The user leaves her computer for 10 minutes.
4. Upon her return, she is forced to reauthenticate.
Let’s look at how to enforce a session inactivity limit.
Create a Session Collection
The first step in this process is to create a session-based collection within ModSecurity. The following ruleset monitors Set-Cookie response headers to common application SessionIDs. It then creates the persistent storage within ModSecurity and creates the session.valid variable that marks this session as legitimate:
SecRule RESPONSE_HEADERS:/Set-Cookie2?/ "(?i:(j?sessionid|(php)?sess
id|(asp|jserv|jw)?session[-_]?(id)?|cf(id|token)|sid)=([^s]+);
s?)"
 "chain,phase:3,id:'981062',t:none,pass,nolog,capture,
setsid:%{TX.6},setvar:session.sessionid=%{TX.6},
setvar:session.valid=1,expirevar:session.valid=3600"
        SecRule REMOTE_ADDR "^(d{1,3}.d{1,3}.d{1,3}.)"  
"chain,capture,setvar:session.ip_block=%{tx.1}"
                SecRule REQUEST_HEADERS:User-Agent ".*" 
"t:none,t:sha1,t:hexEncode,setvar:session.ua=%{matched_var}"
Check Inbound SessionID Data
When the client submits a SessionID in subsequent requests, the following rule accesses the corresponding local session storage and ensures that this is a valid session:
SecRule REQUEST_COOKIES:'/(j?sessionid|(php)?sessid|(asp|jserv|jw)?
session[-_]?(id)?|cf(id|token)|sid)/' ".*" "chain,phase:1,
id:'981054',t:none,block,msg:'Invalid SessionID Submitted.',
logdata:'SessionID Submitted: %{tx.sessionid}',
tag:'OWASP_AppSensor/SE2',setsid:%{matched_var},
setvar:tx.sessionid=%{session.key},
skipAfter:END_SE_PROFILE_ENFORCEMENT"
        SecRule &SESSION:VALID "!@eq 1" "setvar:!session.KEY,t:none,
setvar:tx.anomaly_score=+%{tx.critical_anomaly_score},setvar:tx.%
{rule.id}-WEB_ATTACK/INVALID_SESSIONID-%{matched_var_name}=%{tx.0}"
Verify Inactivity
If the SessionID is valid, we need to calculate the inactivity. This is the amount of time between the last time the client sent a valid request with this SessionID and the current transaction. Let’s look at the data currently held within the Session collection data:
collection_store: Retrieving collection (name "default_SESSION", 
filename "/tmp/default_SESSION")
Wrote variable: name "__expire_KEY", value "1337529432".
Wrote variable: name "KEY", value "x3yn3y555ciqpz55augjruzl".
Wrote variable: name "TIMEOUT", value "3600".
Wrote variable: name "__key", value "x3yn3y555ciqpz55augjruzl".
Wrote variable: name "__name", value "default_SESSION".
Wrote variable: name "CREATE_TIME", value "1337524978".
Wrote variable: name "UPDATE_COUNTER", value "6".
Wrote variable: name "sessionid", value "112917122164".
Wrote variable: name "__expire_valid", value "1337528578".
Wrote variable: name "ip_block", value "192.168.1.".
Wrote variable: name "ua", value "e0d145047e2c03dbd8f547b68fce532698
d0e57e".
Wrote variable: name "cookie_list", value "ASP.NET_SessionId=x3yn3y5
55ciqpz55augjruzl; amSessionId=112917122164;
 amUserInfo=UserName=JyBvciAnMSc9JzEnOy0t&Password=ZmRzZnM=; 
amUserId=1;".
Wrote variable: name "LAST_UPDATE_TIME", value "1337525832".
Wrote variable: name "active", value "1".
Persisted collection (name "default_SESSION", key 
"x3yn3y555ciqpz55augjruzl").
The bold variable data specifies the epoch time that the session collection was last updated. This variable is updated whenever the collection is updated. With this intelligence available to us, we can use the following rule to calculate the latency between requests:
SecRule SESSION:VALID "@eq 1" "id:'981073',phase:1,chain,t:none,
block,msg:'Session Activity Violation: Session Exceeded Idle 
Threshold.',logdata:'Session Idle for: %{tx.session_idle} seconds.',
setvar:session.active=1,setvar:tx.session_idle=%{time_epoch},
setvar:tx.session_idle=-%{session.last_update_time}"
        SecRule TX:SESSION_IDLE "@gt 300" "setvar:!session.valid"
 
SecRule SESSION:VALID "@eq 1" "id:'981074',phase:1,chain,t:none,
nolog,pass"
        SecRule TX:SESSION_IDLE "@lt 300" "setvar:session.active=1"
This ruleset checks the last update time of the session collection and verifies that it is not more than 5 minutes (300 seconds). It does this by subtracting the LAST_UPDATE_TIME data from the current TIME_EPOCH of the current transaction. We then check the resulting data to ensure that it is not more than 5 minutes (300 seconds). If this limit is exceeded, the ruleset removes the session.valid variable and generates an event similar to the following:
[Sun May 20 10:57:11 2012] [error] [client 192.168.1.103] 
ModSecurity: Warning. Operator GT matched 300 at TX:session_idle. 
[file "/usr/local/apache/conf/crs/base_rules/modsecurity_crs_40_
appsensor_detection_point_2.3_session_exception.conf"] [line "62"] 
[id "981073"] [msg "Session Activity Violation: Session Exceeded 
Idle Threshold."] [data "Session Idle for: 490 seconds."] 
[hostname "demo.testfire.net"]
 [uri "/bank/transfer.aspx"] [unique_id "T7kGR8CoqAEAAJySEtMAAAAA"]
Total Session Duration Timeout
Total session duration is a maximum limit of time that a session is valid, regardless of activity. Consider the following scenario:
1. A user authenticates to the application.
2. The application has a total session duration limit of one hour.
3. The user uses the application continually for an hour.
When the total session duration is met, the user is forced to reauthenticate to the application. Let’s see how to enforce the total session duration limit.
Create a Session Collection
The first step in this process is to create a session-based collection within ModSecurity. The following ruleset monitors Set-Cookie response headers to common application SessionIDs. It then creates the persistent storage within ModSecurity and creates the session.valid variable that marks this session as legitimate:
SecRule RESPONSE_HEADERS:/Set-Cookie2?/ "(?i:(j?sessionid|(php)?
sessid|(asp|jserv|jw)?session[-_]?(id)?|cf(id|token)|sid)=([^s]+);
s?)"
 "chain,phase:3,id:'981062',t:none,pass,nolog,capture,
setsid:%{TX.6},setvar:session.sessionid=%{TX.6},
setvar:session.valid=1,expirevar:session.valid=3600"
        SecRule REMOTE_ADDR "^(d{1,3}.d{1,3}.d{1,3}.)"
  "chain,capture,setvar:session.ip_block=%{tx.1}"
                SecRule REQUEST_HEADERS:User-Agent ".*" "t:none,
t:sha1,t:hexEncode,setvar:session.ua=%{matched_var}"
This is the same ruleset shown in the previous section, but this time we are focusing on the bold expirevar action. This action specifies that the session.valid variable will be removed after 3,600 seconds (one hour). You can adjust this timeout setting as appropriate for your application.
Here is how the session collection data looks after this limit is reached:
collection_store: Retrieving collection (name "default_SESSION", 
filename "/tmp/default_SESSION")
Wrote variable: name "__expire_KEY", value "1337529432".
Wrote variable: name "KEY", value "x3yn3y555ciqpz55augjruzl".
Wrote variable: name "TIMEOUT", value "3600".
Wrote variable: name "__key", value "x3yn3y555ciqpz55augjruzl".
Wrote variable: name "__name", value "default_SESSION".
Wrote variable: name "CREATE_TIME", value "1337524978".
Wrote variable: name "UPDATE_COUNTER", value "6".
Wrote variable: name "sessionid", value "112917122164".
Wrote variable: name "__expire_valid", value "1337528578".
Wrote variable: name "ip_block", value "192.168.1.".
Wrote variable: name "ua", value "e0d145047e2c03dbd8f547b68fce532698
d0e57e".
Wrote variable: name "cookie_list", value "ASP.NET_SessionId=x3yn3y5
55ciqpz55augjruzl; amSessionId=112917122164;
 amUserInfo=UserName=JyBvciAnMSc9JzEnOy0t&Password=ZmRzZnM=; 
amUserId=1;".
Wrote variable: name "LAST_UPDATE_TIME", value "1337525832".
Wrote variable: name "active", value "1".
Persisted collection (name "default_SESSION", key "x3yn3y555ciqpz55a
ugjruzl").
Removing key "valid" from collection.
Removing key "__expire_valid" from collection.
Removed expired variable "valid".
The bold entries show that the session.valid variable passed the expirevar setting and thus was removed from the collection. The following rule checks for the existence of the session.valid variable. If the variable has expired and been removed, this rule catches it:
SecRule REQUEST_COOKIES:'/(j?sessionid|(php)?sessid|(asp|jserv|jw)?s
ession[-_]?(id)?|cf(id|token)|sid)/' ".*" "chain,phase:1,id:'981054'
,t:none,block,msg:'Invalid SessionID Submitted.',logdata:'SessionID 
Submitted: %{tx.sessionid}',tag:'OWASP_AppSensor/SE2',
setsid:%{matched_var},setvar:tx.sessionid=%{session.key},
skipAfter:END_SE_PROFILE_ENFORCEMENT"
        SecRule &SESSION:VALID "!@eq 1" "setvar:!session.KEY,t:none,
setvar:tx.anomaly_score=+%{tx.critical_anomaly_score},
setvar:tx.%{rule.id}-WEB_ATTACK/INVALID_SESSIONID-%
{matched_var_name}=%{tx.0}"
Here is a sample debug log snippet showing the rule processing and the final alert that is generated:
Recipe: Invoking rule 100c23ba8; [file "/usr/local/apache/conf/crs/
base_rules/modsecurity_crs_40_appsensor_detection_point_2.3_session
_exception.conf"] [line "60"].
Rule 100c23ba8: SecRule "&SESSION:VALID" "!@eq 1" "setvar:!session.
KEY,t:none,setvar:tx.anomaly_score=+%{tx.critical_anomaly_score}
,setvar:tx.%{rule.id}-WEB_ATTACK/INVALID_SESSIONID-%
{matched_var_name}=%{tx.0}"
Transformation completed in 1 usec.
Executing operator "!eq" with param "1" against &SESSION:VALID.
Target value: "0"
Operator completed in 1 usec.
Setting variable: !session.KEY=1
Unset variable "session.KEY".
Setting variable: tx.anomaly_score=+%{tx.critical_anomaly_score}
Recorded original collection variable: tx.anomaly_score = "0"
Resolved macro %{tx.critical_anomaly_score} to: 5
Relative change: anomaly_score=0+5
Set variable "tx.anomaly_score" to "5".
Setting variable: tx.%{rule.id}-WEB_ATTACK/INVALID_SESSIONID-%
{matched_var_name}=%{tx.0}
Resolved macro %{rule.id} to: 981054
Resolved macro %{matched_var_name} to: SESSION
Set variable "tx.981054-WEB_ATTACK/INVALID_SESSIONID-SESSION" to "".
Resolved macro %{tx.sessionid} to: apulbkymllafsdiibvzhk355
Warning. Match of "eq 1" against "&SESSION:VALID" required. [file 
"/usr/local/apache/conf/crs/base_rules/modsecurity_crs_40_appsensor
_detection_point_2.3_session_exception.conf"] [line "59"] 
[id "981054"] [msg "Invalid SessionID Submitted."] [data "SessionID 
Submitted: apulbkymllafsdiibvzhk355"] [tag "OWASP_AppSensor/SE2"]


Recipe 8-4: Detecting Client Source Location Changes During Session Lifetime
This recipe shows you how to determine when a client’s GeoIP data changes during his current session.
Ingredients
  • MaxMind’s GeoLite City Database2
  • ModSecurity
    • SecGeoLookupDb directive
    • RESPONSE_HEADERS:Set-Cookie variable
    • REQUEST_HEADERS:Cookie variable
    • GEO collection variable
    • @geoLookup operator
    • @streq operator
    • setsid action
    • setvar action
When the application issues a SessionID Set-Cookie response header, it is important to capture certain characteristics associated with the network and the client’s geographic location. The rationale is that the client data should not change during a normal session. On the other hand, this data will almost certainly change during a session hijacking attack where an attacker has obtained a valid SessionID and uses it within his own malicious requests. By capturing this data, we can look for any changes during the course of a session and generate alerts.
Tracking the Client’s Network Block
One piece of client source information we can easily track is the network block he is coming from. This allows us to track the IP address block for each request using a specific SessionID token.
The following rule creates a session-based collection when the application issues the Set-Cookie response header:
SecRule RESPONSE_HEADERS:/Set-Cookie2?/ "(?i:(j?sessionid|(php)?sessid|
(asp|jserv|jw)?session[-_]?(id)?|cf(id|token)|sid)=([^s]+);s?)" 
"chain,phase:3,id:'981062',t:none,pass,nolog,capture,setsid:%{TX.6},
setvar:session.sessionid=%{TX.6},setvar:session.valid=1,
expirevar:session.valid=36"
        SecRule REMOTE_ADDR "^(d{1,3}.d{1,3}.d{1,3}.)"  "chain,
capture,setvar:session.ip_netblock=%{tx.1}"
                SecRule REQUEST_HEADERS:User-Agent ".*" "t:none,t:sha1,
t:hexEncode,setvar:session.ua=%{matched_var}"

Caution
Even though tracking source IP information has a high probability of detecting when an unauthorized user submits someone else’s active SessionID, it unfortunately has a rather high rate of false positives. This is mainly due to nonmalicious users’ legitimate use of proxy servers. Internet service providers (ISPs) often assign dynamic IP addresses to clients that may change during the course of a session. This is especially true of mobile networks and clients. Another scenario that causes false positives with IP-based tracking is when privacy-conscious clients use anonymizing open proxies such as the TOR network. Be careful when using this type of client IP address inspection. It is recommended that you correlate violations of these types of rules with other anomaly detection processes to confirm malicious intent.

The bold portion of the rule shows where we are using a regular expression to capture the client’s network block and save it in a session variable called ip_netblock. By network block, we mean capturing the first three octets of an IP address. As mentioned earlier, capturing the network block information, rather than the full address, helps reduce false positives. The following debug log data shows how this information looks within the session collection:
collection_store: Retrieving collection (name "default_SESSION", 
filename "/tmp/default_SESSION")
Wrote variable: name "__expire_KEY", value "1337529432".
Wrote variable: name "KEY", value "x3yn3y555ciqpz55augjruzl".
Wrote variable: name "TIMEOUT", value "3600".
Wrote variable: name "__key", value "x3yn3y555ciqpz55augjruzl".
Wrote variable: name "__name", value "default_SESSION".
Wrote variable: name "CREATE_TIME", value "1337524978".
Wrote variable: name "UPDATE_COUNTER", value "6".
Wrote variable: name "sessionid", value "112917122164".
Wrote variable: name "__expire_valid", value "1337528578".
Wrote variable: name "ip_netblock", value "195.191.165.".
Wrote variable: name "ua", value "e0d145047e2c03dbd8f547b68fce532698d0e
57e".
Wrote variable: name "cookie_list", value "ASP.NET_SessionId=
x3yn3y555ciqpz55augjruzl; amSessionId=112917122164;
 amUserInfo=UserName=JyBvciAnMSc9JzEnOy0t&Password=ZmRzZnM=; 
amUserId=1;".
Wrote variable: name "LAST_UPDATE_TIME", value "1337525832".
Wrote variable: name "active", value "1".
Persisted collection (name "default_SESSION", key 
"x3yn3y555ciqpz55augjruzl").
The bold line shows that the ip_netblock data for this session is "195.191.165.". With this data saved, we can use the following OWASP ModSecurity Core Rule Set rule to gather and compare the same data on subsequent transactions:
#
# -=[ SE5: Source Location Changes During Session ]=-
#
# - https://www.owasp.org/index.php/
# AppSensor_DetectionPoints#SE5:_Source_Location_Changes_During_Session
#
SecRule REMOTE_ADDR "^(d{1,3}.d{1,3}.d{1,3}.)" "chain,capture,
phase:1,id:'981059',t:none,block,msg:'Warning: Source Location Changed
 During Session. Possible Session Hijacking Attempt',logdata:'Original
 IP/Network Block Range: %{session.ip_block} and Current IP/Network 
Block: %{matched_var}',tag:'OWASP_AppSensor/SE5'"
        SecRule TX:1 "!@streq %{SESSION.IP_BLOCK}"
 "setvar:tx.sticky_session_anomaly=+1,setvar:'tx.msg=%{rule.msg}',
setvar:tx.anomaly_score=+%{tx.notice_anomaly_score},setvar:tx.%
{rule.id}-WEB_ATTACK/SESSION_HIJACK-%{matched_var_name}=%{tx.0}"
If an attacker attempts to use a captured SessionID from a different source network block, this rule would generate an alert. The following sample debug log processing identifies a mismatch with the network block data:
Recipe: Invoking rule 100c37a50; [file "/usr/local/apache/conf/crs/
base_rules/modsecurity_crs_40_appsensor_detection_point_2.3_session_
exception.conf"] [line "84"] [id "981059"].Rule 100c37a50: SecRule
 "REMOTE_ADDR" "@rx ^(\d{1,3}\.\d{1,3}\.\d{1,3}\.)" "phase:1,log,
chain,capture,id:981059,t:none,block,msg:'Warning: Source Location 
Changed During Session. Possible Session Hijacking Attempt',
logdata:'Original IP/Network Block Range: %{session.ip_block} and 
Current IP/Network Block: %{matched_var}',tag:OWASP_AppSensor/SE5"
Transformation completed in 0 usec.
Executing operator "rx" with param "^(\d{1,3}\.\d{1,3}\.\d{1,3}
\.)" against REMOTE_ADDR.
Target value: "62.109.24.153"
Added regex subexpression to TX.0: 62.109.24.
Added regex subexpression to TX.1: 62.109.24.
Operator completed in 25 usec.
Rule returned 1.
Match -> mode NEXT_RULE.Recipe: Invoking rule 100c39290; [file "/usr/
local/apache/conf/crs/base_rules/modsecurity_crs_40_appsensor_detection_
point_2.3_session_exception.conf"] [line "85"].
Rule 100c39290: SecRule "TX:1" "!@streq %{SESSION.IP_BLOCK}"
 "setvar:tx.sticky_session_anomaly=+1,setvar:tx.msg=%{rule.msg},
setvar:tx.anomaly_score=+%{tx.notice_anomaly_
score},setvar:tx.%{rule.id}-WEB_ATTACK/SESSION_HIJACK-%{matched_var_
name}=%{tx.0}"
Transformation completed in 0 usec.
Executing operator "!streq" with param "%{SESSION.IP_BLOCK}" against 
TX:1.
Target value: "62.109.24."
Resolved macro %{SESSION.IP_BLOCK} to: 195.191.165.
Operator completed in 9 usec.
Setting variable: tx.sticky_session_anomaly=+1
Recorded original collection variable: tx.sticky_session_anomaly = "0"
Relative change: sticky_session_anomaly=0+1
Set variable "tx.sticky_session_anomaly" to "1".
Setting variable: tx.msg=%{rule.msg}
Resolved macro %{rule.msg} to: Warning: Source Location Changed During 
Session. Possible Session Hijacking Attempt
Set variable "tx.msg" to "Warning: Source Location Changed During 
Session. Possible Session Hijacking Attempt".
Setting variable: tx.anomaly_score=+%{tx.notice_anomaly_score}
Original collection variable: tx.anomaly_score = "20"
Resolved macro %{tx.notice_anomaly_score} to: 2
Relative change: anomaly_score=20+2
Set variable "tx.anomaly_score" to "22".
Setting variable: tx.%{rule.id}-WEB_ATTACK/SESSION_HIJACK-%
{matched_var_name}=%{tx.0}
Resolved macro %{rule.id} to: 981059
Resolved macro %{matched_var_name} to: TX:1
Resolved macro %{tx.0} to: 62.109.24.
Set variable "tx.981059-WEB_ATTACK/SESSION_HIJACK-TX:1" to "62.109.24.".
Resolved macro %{session.ip_block} to: 195.191.165.
Resolved macro %{matched_var} to: 62.109.24.
Warning. Match of "streq %{SESSION.IP_BLOCK}" against "TX:1" required.
 [file "/usr/local/apache/conf/crs/base_rules/modsecurity_crs_40_
appsensor_detection_point_2.3_session_exception.conf"] [line "84"]
 [id "981059"] [msg "Warning: Source Location Changed During Session.
 Possible Session Hijacking Attempt"] [data "Original IP/Network Block
 Range: 195.191.165. and Current IP/Network Block: 62.109.24."]
 [tag "OWASP_AppSensor/SE5"]
Tracking the Client’s GeoIP Data
To utilize the GeoIP data within ModSecurity, you must import it with the SecGeoLookupDb directive. After this directive is configured, you need to create a ModSecurity rule that passes the client’s IP address (REMOTE_ADDR variable) to the ModSecurity @geoLookup operator:
SecGeoLookupDb /usr/local/apache/conf/GeoLiteCity.dat
SecRule REMOTE_ADDR "@geoLookup" "id:'999015',phase:1,t:none,pass,nolog"
Recipe 4-1 in Chapter 4 contains more details on setting up GeoIP lookups within ModSecurity. These rules create GeoIP data within the ModSecurity GEO collection. From this data, we can access various collection data:
Recipe: Invoking rule 100c38690; [file "/usr/local/apache/conf/crs/
base_rules/modsecurity_crs_40_appsensor_detection_point_2.3_session_
exception.conf"] [line "88"] [id "999015"].
Rule 100c38690: SecRule "REMOTE_ADDR" "@geoLookup " "phase:1,id:999015,
t:none,pass,nolog"
Transformation completed in 1 usec.
Executing operator "geoLookup" with param "" against REMOTE_ADDR.
Target value: "128.98.1.11"
GEO: Looking up "128.98.1.11".
GEO: Using address "128.98.1.11" (0x8062010b). 2153906443GEO:
 rec="x4dx51x34x00x4dx61x6cx76x65x72x6ex00x00x0fx6bx23
xc1x1cx1bx4dx50x32x00x4dx6fx6fx72x65x00x00x3cx9bx23xbc
x0fx1bx4dx48x33x00x42x65x6cx6cx65x20x49x73x6cx65"
GEO: country="x4d"
GEO: region="x51x34x00"
GEO: city="x4dx61x6cx76x65x72x6ex00"
GEO: postal_code="x00"
GEO: latitude="x0fx6bx23"
GEO: longitude="xc1x1cx1b"
GEO: dma/area="x4dx50x32"
GEO: 128.98.1.11={country_code=GB, country_code3=GBR, country_name=
United Kingdom, country_continent=EU, region=Q4, city=Malvern, 
postal_code=, latitude=52.116699, longitude=-2.316700, dma_code=0,
 area_code=0}
Operator completed in 26330 usec.
For our initial purposes, we want to track the GeoIP country name variable data when the application issues an application Set-Cookie SessionID. We can do that by updating a rule covered earlier to issue a new setvar action:
SecRule RESPONSE_HEADERS:/Set-Cookie2?/ "(?i:(j?sessionid|(php)?sessid|
(asp|jserv|jw)?session[-_]?(id)?|cf(id|token)|sid)=([^s]+);s?)" 
"chain,phase:3,id:'981062',t:none,pass,nolog,capture,setsid:%{TX.6},
setvar:session.sessionid=%{TX.6},setvar:session.valid=1,
expirevar:session.valid=3600,setvar:session.country_name=%
{geo.country_name}"
        SecRule REMOTE_ADDR "^(d{1,3}.d{1,3}.d{1,3}.)"  "chain,
capture,setvar:session.ip_block=%{tx.1}"
                SecRule REQUEST_HEADERS:User-Agent ".*" "t:none,t:sha1,
t:hexEncode,setvar:session.ua=%{matched_var}"
Here is how the updated session collection data looks:
collection_store: Retrieving collection (name "default_SESSION", 
filename "/tmp/default_SESSION")
Wrote variable: name "__expire_KEY", value "1337629837".
Wrote variable: name "KEY", value "hkcblojym40urbfupdxgdc45".
Wrote variable: name "TIMEOUT", value "3600".
Wrote variable: name "__key", value "hkcblojym40urbfupdxgdc45".
Wrote variable: name "__name", value "default_SESSION".
Wrote variable: name "CREATE_TIME", value "1337626236".
Wrote variable: name "UPDATE_COUNTER", value "1".
Wrote variable: name "sessionid", value "15365997210".
Wrote variable: name "valid", value "1".
Wrote variable: name "__expire_valid", value "1337629836".
Wrote variable: name "country_name", value "United Kingdom".
Wrote variable: name "ip_block", value "128.98.1.".
Wrote variable: name "ua", value "e0d145047e2c03dbd8f547b68fce532698d0
e57e".
Wrote variable: name "cookie_list", value 
"ASP.NET_SessionId=hkcblojym40urbfupdxgdc45; 
amSessionId=15365997210; 
amUserInfo=UserName=JyBvciAnMSc9JzEnOy0t&Password=ZHNmcw==; 
amUserId=1;".
Wrote variable: name "LAST_UPDATE_TIME", value "1337626237".
Persisted collection (name "default_SESSION", key 
"hkcblojym40urbfupdxgdc45").

Note
For the purposes of this recipe, we are tracking only the GeoIP country name data for the transaction. It is true that this level of tracking may help identify when international criminals steal and attempt to use your SessionID, but it could be improved. For instance, you could easily extend these checks to track the city name as well. This would also catch many domestic attackers who attempt to use your SessionID.

Now that we have saved the original GeoIP country name data, we can validate it for all client requests using the same SessionID. Here is a sample ruleset:
SecRule &SESSION:COUNTRY_NAME "@eq 1" "chain,id:'981080',phase:1,
t:none,block,msg:'Warning: GeoIP Location Change During Session. 
Possible Session Hijacking Attempt.',logdata:'Original GeoIP 
Country Name: %{session.country_name} and Current GeoIP Country 
Name: %{geo.country_name}',tag:'OWASP_AppSensor/SE5'"
        SecRule GEO:COUNTRY_NAME "!@streq %{session.country_name}"
 "setvar:tx.sticky_session_anomaly=+1,setvar:'tx.msg=%{rule.msg}',
setvar:tx.anomaly_score=+%{tx.notice_anomaly_score},setvar:tx.%
{rule.id}-WEB_ATTACK/SESSION_HIJACK-%{matched_var_name}=%{tx.0}"
If an attacker attempts to use this particular SessionID from a different country location, our rule catches it. Here is some sample debug log processing:
Recipe: Invoking rule 100c38f78; [file "/usr/local/apache/conf/crs
/base_rules/modsecurity_crs_40_appsensor_detection_point_2.3_
session_exception.conf"] [line "90"] [id "981080"].
Rule 100c38f78: SecRule "&SESSION:COUNTRY_NAME" "@eq 1" "phase:2,
log,chain,id:981080,t:none,block,msg:'Warning: GeoIP Location 
Change During Session. Possible Session Hijacking Attempt.',
logdata:'Original GeoIP Country Name: %{session.country_name} and
 Current GeoIP Country Name: %{geo.country_name}',
tag:OWASP_AppSensor/SE5"
Transformation completed in 0 usec.
Executing operator "eq" with param "1" against 
&SESSION:COUNTRY_NAME.
Target value: "1"
Operator completed in 2 usec.
Rule returned 1.
Match -> mode NEXT_RULE.
Recipe: Invoking rule 100c3a6a8; [file "/usr/local/apache/conf/crs
/base_rules/modsecurity_crs_40_appsensor_detection_point_2.3_
session_exception.conf"] [line "91"].
Rule 100c3a6a8: SecRule "GEO:COUNTRY_NAME" "!@streq %
{session.country_name}" "setvar:tx.sticky_session_anomaly=+1,
setvar:tx.msg=%{rule.msg},setvar:tx.anomaly_score=+%
{tx.notice_anomaly_score},setvar:tx.%{rule.id}-WEB_ATTACK/SESSION_
HIJACK-%{matched_var_name}=%{tx.0}"
Transformation completed in 0 usec.
Executing operator "!streq" with param "%{session.country_name}" 
against GEO:COUNTRY_NAME.
Target value: "Spain"
Resolved macro %{session.country_name} to: United Kingdom
Operator completed in 17 usec.
Setting variable: tx.sticky_session_anomaly=+1
Recorded original collection variable: tx.sticky_session_anomaly 
= "0"
Relative change: sticky_session_anomaly=0+1
Set variable "tx.sticky_session_anomaly" to "1".
Setting variable: tx.msg=%{rule.msg}
Resolved macro %{rule.msg} to: Warning: GeoIP Location Change 
During Session. Possible Session Hijacking Attempt.
Set variable "tx.msg" to "Warning: GeoIP Location Change During 
Session. Possible Session Hijacking Attempt.".
Setting variable: tx.anomaly_score=+%{tx.notice_anomaly_score}
Original collection variable: tx.anomaly_score = "20"
Resolved macro %{tx.notice_anomaly_score} to: 2
Relative change: anomaly_score=20+2
Set variable "tx.anomaly_score" to "22".
Setting variable: tx.%{rule.id}-WEB_ATTACK/SESSION_HIJACK-%
{matched_var_name}=%{tx.0}
Resolved macro %{rule.id} to: 981080
Resolved macro %{matched_var_name} to: GEO:COUNTRY_NAME
Resolved macro %{tx.0} to: Cookie
Set variable "tx.981080-WEB_ATTACK/SESSION_HIJACK-GEO:COUNTRY_
NAME" to "Cookie".
Resolved macro %{session.country_name} to: United Kingdom
Resolved macro %{geo.country_name} to: Spain
Warning. Match of "streq %{session.country_name}" against
 "GEO:COUNTRY_NAME" required. [file "/usr/local/apache/conf/crs/
base_rules/modsecurity_crs_40_appsensor_detection_point_2.3_
session_exception.conf"] [line "90"] [id "981080"] [msg "Warning: 
GeoIP Location Change During Session. Possible Session Hijacking 
Attempt."] [data "Original GeoIP Country Name: United Kingdom and
 Current GeoIP Country Name: Spain"] [tag "OWASP_AppSensor/SE5"]


Recipe 8-5: Detecting Browser Fingerprint Changes During Sessions
This recipe demonstrates how to identify changes to the client’s browser fingerprint during a session.
Ingredients
  • ModSecurity
    • SecContentInjection directive
    • RESPONSE_HEADERS:Set-Cookie variable
    • REQUEST_HEADERS:Cookie variable
    • GEO collection variable
    • @streq operator
    • setsid action
    • setvar action
    • append action
Tracking User-Agent Field Changes
Besides tracking source location data, as shown in Recipe 8-4, we can inspect certain characteristics of the web client. Specifically, we can easily track the client’s User-Agent string value and ensure that it does not change during the course of a session. If it does change, this may indicate some type of session hijacking scenario. First, let’s look at the following ruleset that captures a hash of the current client’s User-Agent value when the application issues a Set-Cookie response header:
SecRule RESPONSE_HEADERS:/Set-Cookie2?/ "(?i:(j?sessionid|(php)?sess
id|(asp|jserv|jw)?session[-_]?(id)?|cf(id|token)|sid)=([^s]+);s?)
" "chain,
phase:3,id:'981062',t:none,pass,nolog,capture,setsid:%{TX.6},
setvar:session.sessionid=%{TX.6},setvar:session.valid=1,
expirevar:session.valid=3600,
setvar:session.country_name=%{geo.country_name}"
        SecRule REMOTE_ADDR "^(d{1,3}.d{1,3}.d{1,3}.)"  
"chain,capture,setvar:session.ip_block=%{tx.1}"
                SecRule REQUEST_HEADERS:User-Agent ".*" "t:none,
t:sha1,t:hexEncode,setvar:session.ua=%{matched_var}"
The bold rule captures the current User-Agent value, applies the sha1 and hexEncode transformation functions, and saves the resulting hash value in the session.ua variable. Here is the corresponding debug log processing:
Recipe: Invoking rule 100c10e40; [file "/usr/local/apache/conf/crs/
base_rules/modsecurity_crs_40_appsensor_detection_point_2.3_session_
exception.conf"] [line "28"].
Rule 100c10e40: SecRule "REQUEST_HEADERS:User-Agent" "@rx .*" 
"t:none,t:sha1,t:hexEncode,setvar:session.ua=%{matched_var}"
T (0) sha1: "xe0xd1Ex04~,x03xdbxd8xf5Gxb6x8fxceS&x98xd0
xe5~"
T (0) hexEncode: "e0d145047e2c03dbd8f547b68fce532698d0e57e"
Transformation completed in 46 usec.
Executing operator "rx" with param ".*" against 
REQUEST_HEADERS:User-Agent.
Target value: "e0d145047e2c03dbd8f547b68fce532698d0e57e"
Operator completed in 4 usec.
Setting variable: session.ua=%{matched_var}
Resolved macro %{matched_var} to: e0d145047e2c03dbd8f547b68fce532698
d0e57e
Set variable "session.ua" to "e0d145047e2c03dbd8f547b68fce532698d0e5
7e".
This results in the following variables being saved within the current session collection:
collection_store: Retrieving collection (name "default_SESSION", 
filename "/tmp/default_SESSION")
Wrote variable: name "__expire_KEY", value "1337633023".
Wrote variable: name "KEY", value "0gyuwb45siv51grhll12qm45".
Wrote variable: name "TIMEOUT", value "3600".
Wrote variable: name "__key", value "0gyuwb45siv51grhll12qm45".
Wrote variable: name "__name", value "default_SESSION".
Wrote variable: name "CREATE_TIME", value "1337629422".
Wrote variable: name "UPDATE_COUNTER", value "1".
Wrote variable: name "sessionid", value "16305106414".
Wrote variable: name "valid", value "1".
Wrote variable: name "__expire_valid", value "1337633022".
Wrote variable: name "country_name", value "".
Wrote variable: name "ip_block", value "192.168.1.".
Wrote variable: name "ua", value "e0d145047e2c03dbd8f547b68fce532698
d0e57e".
Wrote variable: name "cookie_list", value 
"ASP.NET_SessionId=0gyuwb45siv51grhll12qm45; 
amSessionId=16305106414; 
amUserInfo=UserName=JyBvciAnMSc9JzEnOy0t&Password=amZkbHNqZg==; 
amUserId=1;".
Wrote variable: name "LAST_UPDATE_TIME", value "1337629423".
Persisted collection (name "default_SESSION", key 
"0gyuwb45siv51grhll12qm45").
The bold entry shows that we have saved the User-Agent field hash value. We can use the following sample rule to validate that any subsequent User-Agent field hash values match the original value during the current session:
#
# -=[ SE6: Change of User Agent Mid Session ]=-
#
# - https://www.owasp.org/index.php/
# AppSensor_DetectionPoints#SE6:_Change_of_User_Agent_Mid_Session
#
 
SecRule SESSION:UA "!@streq %{request_headers.user-agent}" "phase:1,
id:'981060',t:none,t:sha1,t:hexEncode,block,
setvar:tx.sticky_session_anomaly=+1,msg:'Warning: User-Agent Changed
 During Session. Possible Session Hijacking Attempt',
logdata:'Original User-Agent Hash: %{session.ua} and Current 
User-Agent Hash: %{matched_var}',tag:'OWASP_AppSensor/SE6',
setvar:'tx.msg=%{rule.msg}',
setvar:tx.anomaly_score=+%{tx.notice_anomaly_score},
setvar:tx.%{rule.id}-WEB_ATTACK/SESSION_HIJACK-%{matched_var_name}=%
{tx.0}"
If an attacker sends a request with this SessionID, but he uses a different User-Agent value, our rule generates an alert. Here is some sample debug log processing:
Rule 100c3baf0: SecRule "SESSION:UA" "!@streq %
{request_headers.user-agent}" "phase:1,log,id:981060,t:none,t:sha1,
t:hexEncode,block,setvar:tx.sticky_session_anomaly=+1,msg:'Warning:
 User-Agent Changed During Session. Possible Session Hijacking 
Attempt',logdata:'Original User-Agent Hash: %{session.ua} and 
Current User-Agent Hash: %{matched_var}',tag:OWASP_AppSensor/SE6,
setvar:tx.msg=%{rule.msg},
setvar:tx.anomaly_score=+%{tx.notice_anomaly_score},setvar:tx.%
{rule.id}-WEB_ATTACK/SESSION_HIJACK-%{matched_var_name}=%{tx.0}"
T (0) sha1: "xf9#xf0_x038{Nxdfxe5Sxa8xd6Ex9dx9bAx96x84
xa8"
T (0) hexEncode: "f923f05f03387b4edfe553a8d6459d9b419684a8"
Transformation completed in 28 usec.
Executing operator "!streq" with param 
"%{request_headers.user-agent}" against SESSION:ua.
Target value: "f923f05f03387b4edfe553a8d6459d9b419684a8"
Resolved macro %{request_headers.user-agent} to: Mozilla/5.0 
(Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like 
Gecko) Chrome/19.0.1084.46 Safari/536.5
Operator completed in 19 usec.
Setting variable: tx.sticky_session_anomaly=+1
Recorded original collection variable: tx.sticky_session_anomaly = 
"0"
Relative change: sticky_session_anomaly=0+1
Set variable "tx.sticky_session_anomaly" to "1".
Setting variable: tx.msg=%{rule.msg}
Resolved macro %{rule.msg} to: Warning: User-Agent Changed During 
Session. Possible Session Hijacking Attempt
Set variable "tx.msg" to "Warning: User-Agent Changed During 
Session. Possible Session Hijacking Attempt".
Setting variable: tx.anomaly_score=+%{tx.notice_anomaly_score}
Original collection variable: tx.anomaly_score = "20"
Resolved macro %{tx.notice_anomaly_score} to: 2
Relative change: anomaly_score=20+2
Set variable "tx.anomaly_score" to "22".
Setting variable: tx.%{rule.id}-WEB_ATTACK/SESSION_HIJACK-%
{matched_var_name}=%{tx.0}Resolved macro %{rule.id} to: 981060
Resolved macro %{matched_var_name} to: SESSION:ua
Resolved macro %{tx.0} to: 192.168.1.
Set variable "tx.981060-WEB_ATTACK/SESSION_HIJACK-SESSION:ua" to 
"192.168.1.".
Resolved macro %{session.ua} to:
 e0d145047e2c03dbd8f547b68fce532698d0e57e
Resolved macro %{matched_var} to:
 f923f05f03387b4edfe553a8d6459d9b419684a8
Warning. Match of "streq %{request_headers.user-agent}" against
 "SESSION:ua" required. [file "/usr/local/apache/conf/crs/base_rules
/modsecurity_crs_40_appsensor_detection_point_2.3_session_exception.
conf"] [line "101"] [id "981060"] [msg "Warning: User-Agent Changed 
During Session. Possible Session Hijacking Attempt"] [data "Original
 User-Agent Hash: e0d145047e2c03dbd8f547b68fce532698d0e57e and 
Current User-Agent Hash: f923f05f03387b4edfe553a8d6459d9b419684a8"]
 [tag "OWASP_AppSensor/SE6"]
Web Client Device Fingerprinting
Web client fingerprinting is a centerpiece of modern web fraud detection systems that goes way beyond simply capturing the User-Agent field submitted by clients within web transactions. For instance, common web client fingerprinting usually includes sending client executable code that queries the browser for various settings:
  • Current screen size
  • Time zones
  • Browser plug-ins
  • Language settings
When the client-side fingerprinting code is complete, it needs to create a new cookie value to pass back the data to the web application for evaluation. The advantage of using client fingerprinting is that it allows you to accomplish two important tasks:
  • Identify clients using real web browsers. If the web client is some type of automated program or script, it most likely will not properly process client-side code such as JavaScript. Without this processing, if a client does not submit the proper fingerprinting cookie, he is easily blocked.
  • Uniquely identify clients even when their source address locations change. Even if the client IP address changes, the actual browser fingerprint data does not change. This fact makes this detection superior to relying on tracking source location changes.
The first step in this process is to use ModSecurity to inject JavaScript code links without outbound HTML response bodies. Here is some code that achieves this goal:

SecContentInjection On
SecStreamOutBodyInspection On
 
#
# -=[ Send Browser Fingerprint Code ]=-
#
SecRule RESPONSE_STATUS "@streq 200" "chain,id:'981802',phase:4,
t:none,nolog,pass"
  SecRule RESPONSE_HEADERS:Content-Type "@beginsWith text/html" 
"chain"
    SecRule &SESSION:KEY "@eq 1" "chain"
      SecRule STREAM_OUTPUT_BODY "@rsub s/</head>/<script type=
"text/javascript" src="/md5.js"></script><script type="
text/javascript" src="/fingerprint.js"></script></head>/"
 "capture,setvar:session.fingerprint_code_sent=1"
When this rule runs, it inserts our JavaScript calls within the HTML head tag, as shown in Figure 8-6.

Figure 8-6: Injected browser fingerprint JavaScript calls

c08f006.tif
The first call is for the file md5.js,3 which is a helper file that provides md5 hashing capabilities. The second file is called fingerprint.js and is based on the “browser fingerprint” JavaScript code created by security researcher Gareth Heyes.4 If you view the html source of this page, it shows the following script contents:
var probeDetails = '';
probe = {};
probe.createIdent = function() {
        var ident;
        ident = '';
        ident += screen.width;
        ident += screen.height;
        ident += screen.availWidth;
        ident += screen.availHeight;
        ident += screen.colorDepth;
        ident += navigator.language;
        ident += navigator.platform;
        ident += navigator.userAgent;
        ident += navigator.plugins.length;
        ident += navigator.javaEnabled();
                ident += '72';
        ident = hex_md5(ident);
        this.ident = ident.substr(0, this.identLength);
 
}
probe.setIdentLength = function(len) {
        this.identLength = len;
}
probe.getIdent = function() {
        return this.ident;
}
probe.setIdentLength(10);
probe.createIdent();
document.cookie="browser_hash=" + probe.getIdent() + "; domain=" + 
document.domain + "; path=/";
The first bold section of code shows the various web browser characteristics we are correlating for our browser fingerprint. It then takes the combined values and creates a truncated md5 hash value that we use to set a new cookie value for the domain called browser_hash. When new web requests are sent back to the web server, they now contain our new cookie value:
GET /bank/main.aspx HTTP/1.1
Host: demo.testfire.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:12.0) 
Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;
q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Proxy-Connection: keep-alive
Referer: http://demo.testfire.net/bank/transfer.aspx
Cookie: ASP.NET_SessionId=faa4rb55hwy0fymk4xl1ap22; 
amSessionId=22266361732; 
amUserInfo=UserName=JyBvciAnMSc9JzEnOy0t&Password=ZmFhZmFz; 
amUserId=1; 
browser_hash=ca1275e21c
The first time we receive a request with a browser_hash cookie, we can use the following rule:
#
# -=[ Save the initial Browser Fingerprint Hash in the Session 
# Collection ]=-
#
SecRule &SESSION:BROWSER_HASH "@eq 0" "chain,id:'981803',phase:1,
t:none,nolog,pass"
        SecRule REQUEST_COOKIES:BROWSER_HASH ".*" 
"setvar:session.browser_hash=%{matched_var}"
This rule saves the initial browser_hash data within the current session-based collection, as shown here:
collection_store: Retrieving collection (name "default_SESSION", 
filename "/tmp/default_SESSION")
Wrote variable: name "__expire_KEY", value "1337654403".
Wrote variable: name "KEY", value "faa4rb55hwy0fymk4xl1ap22".
Wrote variable: name "TIMEOUT", value "3600".
Wrote variable: name "__key", value "faa4rb55hwy0fymk4xl1ap22".
Wrote variable: name "__name", value "default_SESSION".
Wrote variable: name "CREATE_TIME", value "1337650782".
Wrote variable: name "UPDATE_COUNTER", value "3".
Wrote variable: name "sessionid", value "22266361732".
Wrote variable: name "valid", value "1".
Wrote variable: name "__expire_valid", value "1337654382".
Wrote variable: name "country_name", value "".
Wrote variable: name "ip_block", value "192.168.1.".
Wrote variable: name "ua", value "3e88ff512caabf1ce4ac43da3c778fa721
f06ea9".
Wrote variable: name "cookie_list", value "ASP.NET_SessionId=faa4rb5
5hwy0fymk4xl1ap22; amSessionId=22266361732;
 amUserInfo=UserName=JyBvciAnMSc9JzEnOy0t&Password=ZmFhZmFz; 
amUserId=1;".
Wrote variable: name "LAST_UPDATE_TIME", value "1337650803".
Wrote variable: name "active", value "1".
Wrote variable: name "fingerprint_code_sent", value "1".
Wrote variable: name "browser_hash", value "ca1275e21c".
Persisted collection (name "default_SESSION", key "faa4rb55hwy0fymk4
xl1ap22").
Now that we have browser fingerprint data for the current session, we can revalidate it on every request with the following new rules:
#
# -=[ If Browser Fingerprint JS was sent previously, then enforce 
# the existence of the browser_hash Cookie field. ]=-
#
SecRule SESSION:FINGERPRINT_CODE_SENT "@eq 1" "chain,id:'981804',
phase:1,t:none,block,msg:'Warning: Browser Fingering Cookie 
Missing.'"
  SecRule &REQUEST_COOKIES:BROWSER_HASH "@eq 0"
 
SecRule SESSION:FINGERPRINT_CODE_SENT "@eq 1" "chain,id:'981805',
phase:1,t:none,block,msg:'Warning: Browser Fingering Cookie 
Mismatch.',logdata:'Expected Browser Fingerprint: 
%{session.browser_hash}. Browser Fingerprint Received:
%{request_cookies.browser_hash}'"
  SecRule &REQUEST_COOKIES:BROWSER_HASH "@eq 1" "chain"
    SecRule REQUEST_COOKIES:BROWSER_HASH "!@streq 
%{session.browser_hash}"
The first rule ensures that the client actually submits the browser_hash cookie data. If it is missing, odds are that the client is not a real web browser and is most likely some type of automated program or script.
The second rule ensures that any browser_hash cookie submitted matches the initial browser_hash data we received. If these browser_hashes do not match, something has changed with the browser fingerprint characteristics we track. Most likely, it means that this is a different client using the SessionID token. Some debug log data shows processing when a browser mismatch is found:
Recipe: Invoking rule 10098c020; [file "/usr/local/apache/conf/crs/
base_rules/modsecurity_crs_40_appsensor_detection_point_2.3_session_
exception.conf"] [line "99"] [id "981805"].
Rule 10098c020: SecRule "SESSION:FINGERPRINT_CODE_SENT" "@eq 1" 
"phase:1,log,chain,id:981805,t:none,block,msg:'Warning: Browser 
Fingering Cookie Mismatch.',logdata:'Expected Browser Fingerprint:
 %{session.browser_hash}. Browser Fingerprint Received:
 %{request_cookies.browser_hash}'"
Transformation completed in 6 usec.
Executing operator "eq" with param "1" against 
SESSION:fingerprint_code_sent.
Target value: "1"
Operator completed in 2 usec.
Rule returned 1.
Match -> mode NEXT_RULE.
Recipe: Invoking rule 100811030; [file "/usr/local/apache/conf/crs/
base_rules/modsecurity_crs_40_appsensor_detection_point_2.3_session_
exception.conf"] [line "100"].
Rule 100811030: SecRule "&REQUEST_COOKIES:BROWSER_HASH" "@eq 1" 
"chain"
Transformation completed in 1 usec.
Executing operator "eq" with param "1" against 
&REQUEST_COOKIES:BROWSER_HASH.
Target value: "1"
Operator completed in 2 usec.
Rule returned 1.
Match -> mode NEXT_RULE.
Recipe: Invoking rule 100811640; [file "/usr/local/apache/conf/crs/
base_rules/modsecurity_crs_40_appsensor_detection_point_2.3_session_
exception.conf"] [line "101"].
Rule 100811640: SecRule "REQUEST_COOKIES:BROWSER_HASH" "!@streq %
{session.browser_hash}"
Transformation completed in 0 usec.
Executing operator "!streq" with param "%{session.browser_hash}" 
against REQUEST_COOKIES:browser_hash.
Target value: "ecfd017596"
Resolved macro %{session.browser_hash} to: ca1275e21c
Operator completed in 17 usec.
Resolved macro %{session.browser_hash} to: ca1275e21c
Resolved macro %{request_cookies.browser_hash} to: ecfd017596
Warning. Match of "streq %{session.browser_hash}" against
 "REQUEST_COOKIES:browser_hash" required. [file "/usr/local/apache/
conf/crs/base_rules/modsecurity_crs_40_appsensor_
detection_point_2.3_session_exception.conf"] [line "99"] 
[id "981805"] [msg "Warning: Browser Fingering Cookie Mismatch."] 
[data "Expected Browser Fingerprint: ca1275e21c. Browser 
Fingerprint Received: ecfd017596"]

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.146.255.127