Menu

As simple as possible, as complex as necessary

ColdFusion Security Hotfix changes session behaviour

10 March 2011

Last month Adobe released a security Hotfix addressing a number of different vulnerabilities.

Although not mentioned officially, Shilpi (of the CF engineering team), flagged up the need to make sure any CFID/CFTOKEN cookies in apps sharing a domain had the domain and path attributes specified to avoid session mix-ups.

We duly made these changes and rolled out the patch to our CF9.0.1 production server.

Very soon we started getting reports from customers that they couldn't log in to their apps, or rather that they could, but were logged out on the next click. The problem seemed to be intermittent, but fairly widespread. We could replicate it sometimes in some apps, in some browsers but not all the time. There didn't seem to be any pattern, such as just one browser or app or customer.

Faced with apparently broken apps and unhappy customers we decided to just roll back the Hotfix.

We didn't seem to be the only ones having issues, and earlier this week, Adobe released an update to the Hotfix, but the same complaints from users started coming in as soon as we applied it.

A comment by Shilpi suggested the problem was with the logic we (and others) were using to set the CFID/CFTOKEN cookies.

Common technique

For years we have been using a technique (learnt from Hal Helms) to ensure that the session identifying cookies expire when the browser is closed, so that a new cookie - and therefore session - is created when the browser is opened again:

  1. Stop CF from automatically creating the cookies. The cookies CF creates will not expire on browser close so we need to prevent this happening.
  2. Manually set our own cookies to store the session identifiers and make sure they are "session only", i.e. they expire on browser close.

The code for this goes in Applicaton.cfc

<cfcomponent output="false">
		<cfscript>
			this.name = "sessionTest";
			this.sessionManagement = true;
			this.sessionTimeout = CreateTimeSpan( 0,0,0,10 );
			this.setClientCookies = false;
		</cfscript>
		<cffunction name="onSessionStart" returnType="void" output="false">
			<!--- Set session-only cookies --->
			<cfif NOT IsDefined( "cookie.cfid" ) OR NOT IsDefined( "cookie.cftoken" )>
				<!--- Only invoke if cookies don't exist --->
				<cfcookie name="cfid" value="#session.cfid#" domain=".#cgi.HTTP_HOST#" path="/test/sessiontest">
				<cfcookie name="cftoken" value="#session.cftoken#" domain=".#cgi.HTTP_HOST#" path="/test/sessiontest">
			</cfif>
		</cffunction>
	</cfcomponent>

Notice we only set the cookies if they don't already exist. Why? Because until last month's Hotfix, when creating a new session ColdFusion would happily use the CFID/CFTOKEN values found in any existing cookies it found in the browser, rather than generating new values. NB: The session itself would be completely new, but if the cookies were still present from an earlier expired session, they would be re-used as identifiers, saving the need to write new cookies.

However, with the Hotfix, it seems that ColdFusion will now always create new CFID/CFTOKEN values for every new session, whether or not CFID/CFTOKEN cookies exist already. So using our technique, if the cookies do exist, the new CFID/CFTOKEN values will not be written and the session will be lost on the next request.

The reason for the change is apparently to prevent session fixation whereby an attacker could email a link to a user containing a hidden token, asking them to log in. If the server used the token as the session ID, the attacker would thereby have access to the user's session. Presumably, always forcing a new session ID would prevent this.

Demonstration

To show how things have changed, I wrote the following single page application which uses the Application.cfc above:

<cfscript>
	if( NOT IsDefined( "session.testvar" ) ){
		session.testvar	=	Now();
	}
	sessionTracker	=	CreateObject( "java","coldfusion.runtime.SessionTracker" );
	activeSessions =	sessionTracker.getSessionCollection( "sessiontest" );
</cfscript>
<cfdump label="session variables" var="#session#">
<cfdump label="cookies" var="#cookie#">
<cfdump label="server sessions for sessiontest application" var="#activeSessions#">

Here we're dumping the current session scope so we can see the variables in play. Setting a test variable that will store the time when the session starts will prove that a new session has been created each time it changes. Below that is a dump of the cookies sent by the browser. Finally the sessionTracker java object allows us to see the actual session from the server point of view.

Before the Hotfix

Running this without the Hotfix I get the following on the first request:

screenshot

All normal: the cookies didn't exist so we created them with the current session ID. We wait more than 10 seconds for the session to expire (since the timeout is set to 10 seconds) and then refresh which gives us:

screenshot

The testvar has changed proving we've got a new session, but the CFID is exactly the same. CF hasn't generated a new ID, but used the existing cookie values.

After the Hotfix

Now we apply the Hotfix (switching it on via the JVM argument -Dcoldfusion.session.protectfixation=true and restarting CF), close the browser to kill our cookies, relaunch it and hit the app, which gives us:

screenshot

Again, since the cookies had been wiped our onSessionStart function created them with the current session ID. All is well until we wait for the session to time out and refresh again:

screenshot

Now ColdFusion has ignored the existing cookie still present in the browser, and generated a new CFID for the new session. Because of our conditional logic, the new session ID has not been written to our CFID cookies, so our session will be lost.

The solution

Obviously the fix is to get rid of the conditional test. After all onSessionStart() only runs once per session so there's no performance or other issue. But I am a "veteran" (=old) CF Developer and I first started using this technique in the days of Application.cfm where the code would run on every single request. Given ColdFusion's behaviour this was an appropriate and widely recommended practice, and in due course it just got copied to Application.cfc even though it could have been removed.

Application.cfm?

If however you are still using Application.cfm then a small tweak to the conditional should work:

<cfapplication name="sessionTest2" sessionmanagement="true" sessiontimeout="#CreateTimeSpan( 0,0,0,10 )#" setclientcookies="false">
	<cfif NOT StructKeyExists( cookie,"CFID" ) OR ( cookie.CFID NEQ session.CFID )>
		<cfcookie name="cfid" value="#session.cfid#" domain=".#cgi.HTTP_HOST#" path="/test/sessiontest2">
		<cfcookie name="cftoken" value="#session.cftoken#" domain=".#cgi.HTTP_HOST#" path="/test/sessiontest2">
	</cfif>

Here we're not only checking that the CFID cookie exists but that its value is the same as the current session ID (you should probably also test the CFTOKEN as well).

Posted on . Updated

Comments

  • Formatting comments: See this list of formatting tags you can use in your comments.
  • Want to paste code? Enclose within <pre><code> tags for syntax higlighting and better formatting and if possible use script. If your code includes "self-closing" tags, such as <cfargument>, you must add an explicit closing tag, otherwise it is likely to be mangled by the Disqus parser.
Back to the top