Manipulating Session Cookies In Application.cfc's Pseudo Constructor Triggers New Session Creation
So last night I was laying in bed thinking about ColdFusion session cookies when it occurred to me that I didn't know what would happen if I manipulated session cookies from within the pseudo constructor of the Application.cfc. Even more disturbing was that I realized that I didn't even know when the session cookies were applied! Did ColdFusion rely on the Application.cfc for session definition? Or was that wired up separately, using the Application.cfc only for event handlers? From previous testing and methodologies, I know of course that session management itself can be turned on and off dynamically within the Application.cfc pseudo constructor; but this seemed very different from session association itself. Needless to say, I was super excited to jump out of bed this morning and start coding.
To test this, I created a very simple Application.cfc ColdFusion component that did nothing more that set up the application, the session, and define the OnSessionStart() event handler:
<cfcomponent output="false" hint="I define the application settings and event handlers."> <!--- Define the application. ---> <cfset this.name = hash( getCurrentTemplatePath() ) /> <cfset this.applicationTimeout = createTimeSpan( 0, 0, 10, 0 ) /> <cfset this.sessionManagement = true /> <cfset this.sessionTimeout = createTimeSpan( 0, 0, 5, 0 ) /> <!--- Define the page request. ---> <cfsetting requesttimeout="5" showdebugoutput="false" /> <cffunction name="onSessionStart" access="public" returntype="void" output="false" hint="I initialize the session."> <!--- Set session values. ---> <cfset session.dateInitialized = now() /> <cfset session.views = 0 /> <!--- Return out. ---> <cfreturn /> </cffunction> </cfcomponent>
As you can see from the code above, when the OnSessionStart() event handler fires, it stores two values into the session - the date on which it was created and the default view count. Then, I set up a really simple index.cfm page to output the session and cookie information:
<cfoutput> <h1> Cookie Manipulation And Session Triggers </h1> <!--- Increment the number of views. ---> <cfset session.views++ /> <!--- Output view count for this session. ---> Views: #session.views#<br /> <br /> <!--- Dump session. ---> <cfdump var="#session#" label="session" /> <br /> <!--- Dump cookies. ---> <cfdump var="#cookie#" label="cookie" /> </cfoutput>
After running this page a few times, we get the expected behavior - the dateInitialized session key remains constant (being set only once during session initialization) and the views count increments once per page.
Then, I took the CFID value output in the page dump and went into the Application.cfc pseudo constructor and updated it manually to be a different value (going from 8792 to 8793):
Application.cfc (with cookie manipulation)
<cfcomponent output="false" hint="I define the application settings and event handlers."> <!--- Define the application. ---> <cfset this.name = hash( getCurrentTemplatePath() ) /> <cfset this.applicationTimeout = createTimeSpan( 0, 0, 10, 0 ) /> <cfset this.sessionManagement = true /> <cfset this.sessionTimeout = createTimeSpan( 0, 0, 5, 0 ) /> <!--- Define the page request. ---> <cfsetting requesttimeout="5" showdebugoutput="false" /> <!--- Explicitly set session cookies. ---> <cfset cookie.cfid = "8793" /> <cfset cookie.cftoken = "83411851" /> <cffunction name="onSessionStart" access="public" returntype="void" output="false" hint="I initialize the session."> <!--- Set session values. ---> <cfset session.dateInitialized = now() /> <cfset session.views = 0 /> <!--- Return out. ---> <cfreturn /> </cffunction> </cfcomponent>
Now, when I run the index.cfm page again with this new ColdFusion code, I get the following output:
If you look at the above screen shot, there's a few things to notice:
The SESSION-based CFID and CFTOKEN now reflect the values that I manually put into the COOKIES scope in the Application.cfc pseudo constructor (meaning, they were implicitly transferred from the COOKIE scope to the SESSION scope).
The DateInitialized value went from 08:45:31 to 8:51:23 (NOTE: This was not due to session timeout - the previous session was active during page refresh moments beforehand).
These facts lead us to understand that the OnSessionStart() event handler was invoked by the ColdFusion application framework. And, since we are not calling this method manually, we can safely say that by changing the cookie values from within the Application.cfc pseudo constructor it actually forces ColdFusion to create a truly new session for the user.
And, what's more, if you look at the cookies displayed in my browser cache, you'll see that ColdFusion also sent the new session values back to the browser:
So, it seems that by manually manipulating the session cookies from within ColdFusion's Application.cfc pseudo constructor, not only are we triggering the creation of a completely new session, but ColdFusion also broadcasts this new session back to the browser (as it would with a naturally created session) such that the browser can send back the appropriate cookies on subsequent requests. Pretty cool stuff. I'm not sure how this can be leveraged (or abused) just yet, but I like having the power to actually have ColdFusion create completely new sessions.
Want to use code from this post? Check out the license.
Last time you posted about this (http://tinyurl.com/mpkywt), I played around with it a bit and came up with a few realizations about it.
It seems that ColdFusion does let you change the CFID value to something else and will give you a new session, but it will only allow you to change it to a CFID that has existed once before.
ColdFusion creates the CFID piece of the Session Token (the session token is the CFID and the CFToken combined) by using the highly secure cryptographic algorithm ++. In other words, it increments the last CFID by 1. Which means, in your case, that there had been 8791 sessions started since your last server restart before you started this test.
What's interesting is that ColdFusion will let you change your CFID cookie value to anything that is at or below the highest CFID value currently assigned to any user, but since the combination of your CFID and CFTOKEN (since we have not changed the CFTOKEN) are not a valid session identifier, then a new session is created.
If you try to set CFID to anything higher than the highest currently assigned value, then ColdFusion will ignore that value and give you the next CFID it would normally give when it starts the new session. So in your case, you tried to force the CFID value 8793 and so it appeared that you forced that value to CF, but that's the CFID it would have assigned anyway. If you have tried forcing 4534534 it still would have given you 8793.
I have not tested this recently, so I am going from memory. It is possible that I am wrong here. Also, and more importantly, my testing did not reveal that any of this is possible with the CFToken, which is the more important half of the session token.
There is a possibility that this CFID behavior could be used for a session fixation attack, but I am not sure. I have tried to fixate sessions with ColdFusion before and have never been successful, but I have not tried it since this discussion of reusing session tokens and manipulating CFID has begun, so maybe it is something we should try to do.
From some quick testing, it looks like I can set both the CFID and CFTOKEN values to any number to effectively create a new session. I just tried this:
<cfset cookie.cfid = "4534534" />
... and a new session was created (with a much higher CFID that had been used to date). I confirmed that this CFID was actually set in the browser cookies as well.
Then, keeping the same CFID as above, I changed the CFTOKEN:
<cfset cookie.cftoken = "8341185199" />
... and a new session was created as well.
After each change, I then commented out the code to see if the values persisted, which they did.
It seems, unless I'm not seeing something, that changing either the CFID or the CFTOKEN will cause a truly new session to be created and the new cookies to be send back to the browser.
Yeah, you're right. I'm not sure what I was doing then when I tested this. Cause I clearly remember not being able to set the CFID higher than anything that was previously set. Regardless, I just tried it again, and it did allow me to set pretty much anything I wanted for both the CFID and CFToken.
Just for kicks, I also tried it with JEE session management enabled and it did NOT allow me to change the JSESSIONID token. When I tried, it completely reset the token and gave me a new session.
FYI, I did all of my testing my changing the cookies at the client, not in the pseudo-constructor.
So, if I'm understanding, JSessionID can be tweaked to create a new session, but it doesn't keep the tweaked ID in the new session. Interesting.
You can also force CF to create a new session by just expiring the existing cookies. You don't need to create new values - CF will do that for you if it doesn't find the session cookies.
cfheader name="Set-Cookie" value="CFID=0;expires=Sat, 01-Jan-2000 00:00:00 GMT;HTTPOnly;"
cfheader name="Set-Cookie" value="CFTOKEN=0;expires=Sat, 01-Jan-2000 00:00:00 GMT;HTTPOnly;"
That should do it nicely. You can also use cfcookie to do it, but we do a lot of other advanced session management that won't work with cfcookie, so this was cut and paste from existing code.
For instance, we manually write the session cookies only once the user has authenticated, precisely to avoid the session fixation attacks that Jason mentioned.
Sounds cool. I like the strategy of not giving cookies values until someone is logged in. Very cool!
The set up is actually very easy too and adds protection against a couple of exploits. All you have to do in the pseudo-constructor of Application.cfc is configure session management like this:
cfset this.sessionManagement = true
cfset this.setClientCookies = false
This tells CF that you're going to manage the session cookies manually. It still creates server-side cfids and cftokens, it just doesn't send the cookies to the browser. Effectively, the two values will change for every request until your user logs in. (This is exactly what we want in order to avoid session fixation attacks).
So in order to have the session stick, you just need to set the cookies when the user authenticates. For instance, in our app, the code flows something like this:
if User.Login() is successfull
Inside SetupSession, you then need to send the session cookies to the browser.
cfheader name="Set-Cookie" value="CFID=#session.cfid#;path=/;HTTPOnly;"
cfheader name="Set-Cookie" value="CFTOKEN=#session.cftoken#;path=/;HTTPOnly;"
That's all there is to it - you've just waited until the user has logged in to actually give them a session and send the session cookies, and the cookies they received were never sent in an un-authenticated request.
Now in order to make sure that users get a brand new set of cookies when their session expires, you need to clean up any old cookies in onSessionStart, which is where the code I posted earlier resides in our app.
cfif StructKeyExists(cookie, "cfid") or StructKeyExists(cookie, "cftoken")
cfheader name="Set-Cookie" value="CFID=0;path=/;expires=Sat, 01-Jan-2000 00:00:00 GMT;HTTPOnly;"
cfheader name="Set-Cookie" value="CFTOKEN=0;path=/;expires=Sat, 01-Jan-2000 00:00:00 GMT;HTTPOnly;"
It may seem odd that we're deleting the sesison cookies inside the onSessionStart method, but remember that onSessionStart is fired both when a new user hits the app, and when a user with an expired session hits the app. In the latter case, an old cfid and cftoken will exist in the user's cookie, and we don't want to use the old session identifiers any more. So we just delete the cookies, which forces CF to generate new ones.
It's a bit more complicated than relying on CF to do the work for you, but this, coupled with the use of HTTPOnly cookies, prevents a huge class of session stealing and session fixation attacks. It may seem like overkill for some, but we're in an industry where this type of secured, non-repeatable session is required.
I hope that all made sense!
Yes, I think that all makes sense. But, just to work through the work-flow of a user login it would be this:
1. User submits login form.
2. Page gets posted to server.
3. OnSessionStart() fires (since cookies haven't stuck yet).
4. Any existing cookies are deleted.
5. Login data gets processed.
6. Session tokens (which were created for this request) are stored into cookies and sent back to browser.
7. Login-based cookies are re-sent (to hook into previous page's session).
Is that about accurate as far as workflow goes?
The only change that I would make is to note that onSessionStart will also fire if there are cookies, but the session associated with those cookies has expired, or the user has modified the cookies. And that tampering is exaclty what we're protecting against :)
3. OnSessionStart() fires (since cookies haven't stuck yet or a previous session has expired or the cookies have been modified).
4. Any existing cookies are deleted.
@Roland and @Ben,
This is a really interesting discussion. There are a couple of concerns I have about your methods, Roland.
1. Because you are not persisting a session token with each request, and instead are waiting until the user is logged in before you write the session cookies, I believe that your users are then starting a new session with every request they make to every page on your site until they are logged in.
To me, this seems like the makings of a "memory leak" in your application. Especially if you load up the session with anything (like a user object or shopping cart) for later use when they are logged in.
I tried this on my machine and turned on the server monitor. Sure enough, every time I refreshed the page, a new session showed up in the server monitor and those sessions will persist until they timeout. In a high traffic site, I think this could be very bad.
2. By not giving an anonymous user a session, you prevent your users, and yourself, from some of the benefits of having persistent data for anonymous users. It might not affect your application specifically, but I know there are many applications out there that would need persistent data for anonymous users. Like having the ability to start a shopping cart before logging in
I think there are better ways to handle the session fixation issues.
1. Use JEE session management
2. Rotate sessionIDs in onSessionStart() which I discuss here: http://tinyurl.com/r7nc98
3. Strip out session ids from URLs at the webserver level, with a WAF or even in onRequestStart() so they cannot be persisted
There may be other solutions, and it's possible that I am missing something.
Yes, a new session is generated for each request, but prior to logging in, we don't store anything in the session, so in our application, this isn't a concern. In a shopping cart style app, you're absolutely correct that this wouldn't work.
As far as the memory leak - I forgot to include this in my code earlier. It also goes in the pseudo-constructor.
cfif StructKeyExists(cookie, "cfid") and StructKeyExists(cookie, "cftoken")
cfset this.sessionTimeout = CreateTimeSpan(0, 0, 20, 0)>
cfset this.sessionTimeout = CreateTimeSpan(0, 0, 0, 1)
So yes, many sessions could be created, but if they're not authenticated, they disappear after one second. We've load tested this heavily, and it doesn't appear to have any real impact on the server.
The reason that we don't use JEE sessions is that in the past, we have seen way too many "Invalid Session" issues with them, even when there was no legitimate reason. Cookies were fine and untampered with, and the session was still well within its timeout period. It's like at some point, some sessions just decided to stop working. And it wasn't all sessions - it'd be just one or two, completely at random. It wasn't when the server wass maxed out, and there were no configration issues we could find. We couldn't even come up with a consistent way of making the error occur. We even tested 3 different OS's.
To be frank, we spent months of troubleshooting effort, and didn't make any progress, so we just gave up on them. The way we set our cookies manually, we get all of the benefits of JEE sessions anyway (except the ability to programatically invalidate a session).
I didn't know that you could selectively set different timeouts for sessions on a per-request basis like that. I would have thought that if you changed that in the pseudo-constructor like that, that it would affect all active sessions. But I guess it doesn't. Interesting indeed.
I's be interested to know if others have had the issue with mysterious JEE session timeouts like that. I have certainly heard of such mysterious timeouts before, but I don't know if they were with JEE session, CF sessions or some combination.
I didn't know I could change the timeouts on a per-session basis like that either - in fact, I assumed I couldn't until I had a few too many beers one night and tried it in a moment of desperation :)
Mysterious is the perfect word to describe the timeouts. This happened before we were manually tinkering with the sessions too - nothing but plain old automatic CF sessions. And it only happened with JEE, never with CFID/CFTOKEN.
Our apps are fairly high traffic, so we assumed that it was a load issue initially. But we could never reporoduce it consistently, and tweaking the JVM settings didn't fix it. I did extensive research - even palcing a call to Adobe about the issue - but nothing yielded results. My googling even uncovered several people with the same issue, but never an answer.
In the end, we just rolled our own, since the troubleshooting was starting to eat into productivity.
Being able to set the session time on a per-request basis is a trick that I learned from Michael Dinowitz. I've used to manage the bot-based sessions on my blog.
Within applications, I've also used it to set different timeouts based on user-type.
None of this is really all that graceful as it must be done inside the pseudo constructor (which forces some hackery outside the typical ColdFusion application framework).
I know this is an old post, but thank you!!!!!
Its funny but, I've been working on an older app fixing security issues and adding some new functionality. So far every time I have run into something (especially session fixation and cookie stuff) I end up on your blog.
So I owe you some beers if we're ever at a conference together.
That's awesome - I'm glad I keep ending up in your search :) The ColdFusion application framework is something that is more complex and powerful than most people think about. I'm actually going to be doing a presentation on it in two months. So, I'll be sure to post it on the blog when it's recorded.
Can I ask, what's the reason behind this:
<cfset this.name = hash(getCurrentTemplatePath()) />
I know what it's doing, just not sure why.
I take this approach so that I make sure that no two apps (most likely) will ever have overlapping names. Since the likelihood of two calls to hash() returning the same value on two different strings is so low, using hash() to define the application name based on the file path pretty much ensures that no two apps will have the same name.
Since application name is pretty much an irrelevant property, it just seems like one more thing I now no longer have to worry about.
Using something like <cfset this.name = hash(getCurrentTemplatePath()) /> or <cfset this.name=createUUID()" /> has always confused me. I see people do it but I am not sure why. It seems like it should cause problems.
Here are my thoughts.
I believe it was you who taught me that Application.cfc gets recreated on every request. But if the application name changes between one request and the next, then ColdFusion sees this as a new application starting up doesn't it?
And if CF sees it as a new application, then it fires onApplicationStart() again and creates a new APPLICATION scope for that new application.
Now, if what I am suggesting it true, what happens to your application when the current template that your user browses to changes?
I try to name my applications uniquely so that I don't have to worry about it.
<cfset this.name = "MNStrokeRegistryTool" />
No one else in my org should be creating an application with that name, and I only end up with one application scope.
Am I completely off about the app scope? It seems like using a dynamic application scope is asking for trouble and for memory problems.
Just to clear up one thing - createUUID() for app name will *deffinitely* mess you up :) Yeah, that will create a new name for your app on every page request, which, as you will say, cause onApplicationStart() to run every page (since you effectivey have a new app each page).
hash( getCurrentTemplatePath() ) should always return the Application.cfc file path (if it is called from with Application.cfc); so, it should be safe.
Perhaps a bit of background on why I learned to use this. When I was working at my previous job, we built a lot of admin systems. And, there was a *lot* of copy-paste-modify magic going on. Most of the time, when someone started a project, they would copy the last one and start to modify it.
As you might image, we ended up with a lot of applications named "Admin". This caused all kinds of errors. One second your app is working fine; then, 2 seconds later, it starts throwing all kinds of variable errors because unbeknownst to you, someone across the room just copy/pasted your app and started running it.
So, after struggling with that for a while, I just started using this hash() approach. Ideally, you wouldn't have to; but once I started using it, I just never ran into a reason to not use it.
Now it makes more sense. I didn't realize it would grab the App.cfc path and not the path of the template that is the final destination. Clearly I didi not try it :)
Thanks for clearing that up. I always wondered if I was being silly by just uniquely naming my apps.
Yeah, getBaseTemplatePath() should get the file requested ala cgi.script_name. getCurrentTemplatePath() should get the page executing the given line of code (with some exceptions).
Copy-n-paste makes a man do crazy things :)
Unfortunately I'm running into a bit of a problem with the latest install of CF10. My SERVER.COLDFUSION.PRODUCTVERSION variable says I'm running: 10,0,7,283649
When attempting to set cookies cfid or cftoken in the pseudo constructor using either <cfcookie name="cfid"> or <cfset cookie.cfid> I receive the following server error:
"The system has attempted to use an undefined value, which usually indicates a programming error, either in your code or some system code.
Null Pointers are another name for undefined values."
It is important to note that I can set other cookies (i.e. cookie.foo='bar') in the pseudo constructor without receiving this error.
It appears that Coldfusion 10 (or at least this version) is restricting the manipulation of these particular variables in the pseudo constructor. In which case, is there any other place in Application.cfc that the session can be hijacked?
Maybe I'm missing something...