ColdFusion in Context: Session Persistence

Once a client has logged in, how long does the client's session persist? What happens when the client leaves the machine for a smoke break and the browser window is still open, still viewing the site? Suppose the client went to another site before she left but didn't close the browser window? Suppose the client did close the browser but didn't lock the screen? Can someone else walk up to the machine (or perhaps use his own machine) and continue the session without going through the secure login process the client had to use to gain access in the first place? Because your customer will want to quantify these security risks before accepting them or asking you to making adjustments, you have know the answer to these questions.

This tip will explore each of these situations. Along the way, you'll work with session and client variables.

Problems

First, it's appropriate to note that session persistence isn't a ColdFusion problem; a Web "session" is a logical concept for which there are many valid definitions and implementations. For example, I use Yahoo! to list links that lead to specific content, follow some of those links, and come back to Yahoo! to chase other links until I'm satisfied. When did my session with Yahoo! end? How long was the session? Did I have one session at Yahoo! or several? How does Yahoo! know I'm coming back? (It doesn't.) At what point should it conclude that I'm not coming back and consider me a fresh customer when I eventually do return (an hour or day later)?

People juggle multiple tasks. Surprised? Regardless of whether you provide links to external sites as part of your site, users may click on a link in E-mail which seizes control of the last browser window opened, and that browser window may be pointing at your site. Thus, it may be perfectly valid for someone to leave your site momentarily and then hit the back button to return. It may be perfectly valid for a user to stop browsing long enough to take a phone message, write a report, look up something on Yahoo!, or go to the bathroom. It may even be valid for the user to have multiple windows open (or even multiple browsers) pointing to various pages of your application.

The extent to which you want to support these kinds of behavior is a tradeoff between convenience and security. You can't tell if the user has left the machine or is just doing something else in front of it (unless Big Brother is watching). You usually can't even tell if the user's browser is still pointed at your site or has moved to another one.

The working definition of a Web session usually revolves around idle time. If a user hasn't asked you for a page in a certain length of time, you assume the session is over and act accordingly. However, some sites may actually dump you off after a length of time even if you are still moving within the site.

The good news is that ColdFusion will automatically remove a session's variables from memory if the client lets enough time go by between page requests. (If the client makes a page request within the time limit, the timer starts over again.)

So how long is long enough? The default is twenty minutes. A different default may be set through ColdFusion Administrator. Your cfapplication tag can set the session timeout to any length that doesn't exceed the maximum set through ColdFusion Administrator and the timeout for the overall application.

The bad news is that someone else can use that time to continue the session without logging in. If the client's machine is available with the browser window still open, then the browser is ready for immediate use (after pressing the back button if the client has left the site).

If you're tracking sessions with URLs, then if someone else was watching and has written the token/id combination down, then that person doesn't even need the client's machine. It doesn't take much knowledge to add variables to the end of the URL in a browser window and jump in when the client walks away from an active session.

Is your client safe from attacks from a co-worker's machine if you used cookies? Not entirely. The default methods of using cookies for session and client management create cookies that remain on the client's disk drive when the browser is closed. Even if you turned off this default and put the id/token combination in temporary cookies only stored in memory, if you're running WebTrends or a similar piece of software, you'll find that it places a permanent cookie containing this combination on the client's machine. Anyone with a moment's access to the client's machine can copy the cookie file and (with a little knowledge) prepare his machine to take over at the right moment. Need I say more?

If you're running a clustered site, then you're probably using client variables in a database instead of session variables in memory. Client variables will remain in the database for days of inactivity rather than hours - the default is 90 days in the registry or 10 in a database - unless you do something explicitly about them. Further, this limit is global, not limited by application. (Client variables are really meant to track clients, not sessions. It takes a bit of work to use them for session management.)

URL Demo with Session Variables

To see the problem for yourself, here's code for a trivial application using three pages that work with session tracking information in the URL where you can easily see it: appset1.cfm, door1.cfm, and work1.cfm.

The include file, appset1.cfm, defines an application named time1. Its application variables remain active for 10 minutes; its session variables time out after 20 seconds of inactivity (to make this demo go faster). The default would be to track the session using cookies; we override that here. This kind of code would usually be in Application.cfm, but we're going to vary these parameters later.

<cfapplication name="fred"
applicationtimeout="#createTimeSpan(0,0,10,0)#"
sessionmanagement="yes"
sessiontimeout="#createTimeSpan(0,0,0,20)#"
setclientcookies="no">

Here is a trivial login page, door1.cfm, that includes appset1.cfm, goes to work1.cfm or complains depending on what the user entered, and provides a place for the login entry. It deletes cookies only for this example so that you won't see confusing results if cookies accidentally get set.

<cfinclude template="appset1.cfm">

<cfcookie name="cfid" expires="now">
<cfcookie name="cftoken" expires="now">

<cfif isDefined("form.Nickname")>
  <cfif Nickname is "Frank">
    <cfset session.ok="1">
    <cfset session.userid="7">
    <cfset work="work1.cfm?cfid=#session.cfid#&cftoken=#session.cftoken#">
    <cflocation url="#work#">
  <cfelse>
    Wrong nickname: use "Frank"<br>
  </cfif>
</cfif>

<form action="door1.cfm" name="door" method="post">
Enter employee nickname (say "Frank"): 
<input type="text" name="Nickname" value="" size="10" maxlength="8">
<input type="submit" name="submit" value="OK?">
</form>

Here is a trivial work page, work1.cfm. Whenever you push the button, it will increment the number in the form field to simulate work. If you're idle long enough for the session variables to time out, it takes you back to door1.cfm.

<cfinclude template="appset1.cfm">

<cfif not isDefined("session.ok")>
  <cflocation url="door1.cfm">
</cfif>

You are logged in as Frank,<br>
<cfoutput>
UserID #session.userid#, Session ID #session.cfid#,
and token #session.cftoken#<p>
</cfoutput>
Pressing the button simulates work;<br>
staying idle too long logs you out.

<cfif not isDefined("form.myNum")>
  <cfset hold=0>
<cfelse>
  <cfset hold=#form.myNum#+1>
</cfif>

<cfform name="work" action="work1.cfm?cfid=#session.cfid#&cftoken=#session.cftoken#" method="post">
<cfinput type="text" name="myNum" value="#hold#">
<input type="submit" name="bang" value="Submit">
</cfform>

When you go to door1.cfm to try this out, you'll see that the session id and token have nothing to do with the user's logical identity ("Frank"). The session ID is not the user's ID in the system. The session ID gets reused as other individuals come to the Web site. Therefore, if a shoulder surfer doesn't care whose account he takes over, he can simply record what he sees in the URL and keep trying it from time to time until he catches someone for whom this session ID/token combination corresponds to an active session.

Try to hijack this session. While looking at work1.cfm, copy the URL, close the browser, open the browser, copy the URL into it, and try to go there. (You could just open another window, but that's not as dramatic.) If the variables haven't timed out, you can do it, even from a completely different machine. You're in the working area of the application, and you didn't have to log in.

If you're on the client's machine, you don't even have to have written down the id and token; just re-open the browser. The pulldown history for the URL window will get you some likely candidates; the last one is likely to be correct for our example.

URL Demo with Client Variables

For sites clustered across multiple platforms, you're more likely to use client variables that can be stored in a central database than you are to use session variables that would have to reside in memory on a single platform. Here's code for a trivial application using three pages that do "session" tracking with client variables in the URL where you can easily see them: appset2.cfm, door2.cfm, and work2.cfm.

The include file, appset2.cfm, defines an application named time2. Its application variables remain active for 10 minutes. It has no session variables. However, it will assign cfid at the client level. The default would be to track the client using cookies; we override that here. This kind of code would usually be in Application.cfm, but we want to be able to run another example in this directory.

<cfapplication name="time2"
applicationtimeout="#createTimeSpan(0,0,10,0)#"
clientmanagement="yes"
setclientcookies="no">

Here is a trivial login page, door2.cfm, that includes appset2.cfm, goes to work2.cfm or complains depending on what the user entered, and provides a place for the login entry. It deletes cookies only for this example so that you won't see confusing results if cookies accidentally get set.

<cfinclude template="appset2.cfm">

<cfcookie name="cfid" expires="now">
<cfcookie name="cftoken" expires="now">

<cfif isDefined("form.Nickname")>
  <cfif Nickname is "Frank">
    <cfset client.ok="1">
    <cfset client.userid="7">
    <cfset work="work2.cfm?cfid=#client.cfid#&cftoken=#client.cftoken#">
    <cflocation url="#work#">
  <cfelse>
    Wrong nickname: use "Frank"<br>
  </cfif>
</cfif>

<form action="door2.cfm" name="door" method="post">
Enter employee nickname (say "Frank"): 
<input type="text" name="Nickname" value="" size="10" maxlength="8">
<input type="submit" name="submit" value="OK?">
</form>

Here is a trivial work page, work2.cfm, that goes back to door2.cfm if you're idle long enough to exceed your time limit. If the last visit was more than 20 seconds ago, it will delete client.ok. If client.ok or the last visit aren't defined, it will return to door2.cfm. Otherwise, Whenever you push the button, it will increment the number in the form field.

<cfinclude template="appset2.cfm">

<cfif isDefined("client.LastVisit")
and (dateDiff("s", client.LastVisit, now()) gt 20)>
  <cfset dummy=deleteClientVariable("ok")>
</cfif>

<cfif (not isDefined("client.ok")) or (not isDefined("client.LastVisit"))>
  <cflocation url="door2.cfm">
</cfif>

You are logged in as Frank,<br>
<cfoutput>Userid #client.Userid#, Client ID #client.cfid#,
and token #client.cftoken#<p>
</cfoutput>
Pressing the button simulates work;<br>
staying idle too long logs you out.

<cfif not isDefined("form.myNum")>
  <cfset hold=0>
<cfelse>
  <cfset hold=#form.myNum#+1>
</cfif>

<cfform name="work" action="work2.cfm?cfid=#client.cfid#&cftoken=#client.cftoken#" method="post">
<cfinput type="text" name="myNum" value="#hold#">
<input type="submit" name="bang" value="Submit">
</cfform>

Go to door2.cfm and try this out. Except that client variables have to be simple - no arrays or structures are allowed, you can create and access them as if they were in memory. This application acts about the same as the one that uses session variables (and has the same weaknesses).

What Do These Examples Prove?

These examples provide reasonable approaches to timing out an idle session. Many real-life applications don't exhibit this level of control. It's still possible for someone to hijack an active session if timing and standard tokens are your only protection, but it's harder to do when sessions are terminated on the server after a chosen period of inactivity instead of being left open on the server for hours or days.

To make it harder to use a client's machine to hijack a "session" the client isn't using, logical sessions should be terminated after a chosen period of inactivity. Whether working with session variables in memory or client variables in a database, ColdFusion gives you tools to easily determine when to end the session and to do it.

The key to ending a session defined by session variables is to force the browser out of the working area of the application when an active session is no longer detected. Testing any session variable for its existence will do.

The key to ending a "session" defined by client variables is to change the variable corresponding to being logged in when browser has been idle beyond a reasonable period of time and to force the browser out of the working area of the application when this happens. Comparing client.LastVisit to now() is the key to this technique. Technically, a visit to ANY application on your server will update client.LastVisit, but that's probably OK from a practical standpoint.

Of course, there's nothing stopping you from rolling your own methods. After logon, you'll need something the client's browser can pass back to you so the client won't have to log on over again as she moves from page to page. You'll need to notice the time of the client's last page request and compare it to the current time (or equivalent). And, you'll have to use this information for "session" management.

Beyond Variable Timeout

The ease with which an active session can be hijacked varies depending on how the "session" is tracked (via URL, form, or cookie), but each of these methods is vulnerable. URL modification takes no skill. Forms can be modified prior to submission. Cookies can be edited on a personal computer.

By reducing the number of open sessions at any given time, timeouts reduce the likelihood that an intruder will simply stumble onto an active session by using a valid ID/token combination, but it still happens. People have actually wound up inside applications by following URLs captured by search engines. While this is accidental and infrequent, shoulder surfing is not.

If an additional token based on time is added to the combination of variables required to use pages in the working area of your application, the likelihood that someone can simply guess his way inside is greatly lessened. Consider adding an encrypted time token to the variables you use to track sessions of individuals who are logged into your application. Due to its encryption, it would be difficult to spoof. Because this token would be needed for access and could not be reused later, it would greatly strengthen your security. =Marty=

[The timeout has been reduced from the 45 seconds in the original example to 20 seconds to make its action more obvious.]