Many of you probably know that I sell UI Flex Components under the name of Flextras. Part of the decision I made when starting the company is to open up a merchant account and process credit cards on the site, instead of using a third party site such as PayPal. This gives us complete control over the branding experience.
As part of this decision to touch credit card data, the site is subject to routine PCI Compliance scans, and the scan often makes suggestions for items that must change on the site in order to stay compliant. I think the penalty for not changing this stuff is immediate death.
The latest scan failed me for a few issues that have been on the site since it launched. If it was fine for the first 18 months, why do I have to spend and money changing it now? But, I digress. I wanted to write about one of the things they are making me change this time around
The two issues are these:
- Cookies set by the site are not set to HTTPOnly cookies.
- When cookies are served over HTTPS they are not specified as secure cookies.
The scan information was very light, so I'm not sure which cookies they are referring to. The site's code doesn't set any cookies, however there are some tracking cookies set by analytic software and session tracking cookies set by ColdFusion.
So, the question is, how do I Set the ColdFusion session cookies (CFID and CFToken) to be HTTPOnly and Secure? A quick google search brings up these two Stack Overflow questions, which lead to this post by Jason Dean. Thank you Jason!
Jason explains how to modify the JSessionID to set the HTTPOnly and Secure properties on the cookie. I was easily able to encapsulate Jason's code into a method, which I put in Application.cfc:
<cffunction name="recreateSessionCookies" returntype="void" output="false">
Do some cookie Magic to make cookies be HTTPOnly
<!--- Expire the old Cookie --->
<cfcookie name="cfid" expires="now"/>
<cfcookie name="cftoken" expires="now"/>
<!--- Get the HTTP Response Object --->
<cfset response = getPageContext().getResponse() />
<!--- Set the specifics for the cookie --->
<cfset path = "/" />
<cfset domain = 'www.flextras.com' />
<cfif cgi.HTTPS is "on">
<cfset secure = "Secure" /> <!--- Use val of "Secure" or leave blank --->
<cfset secure = "" /> <!--- Use val of "Secure" or leave blank --->
<cfset HTTPOnly = "HTTPOnly" /> <!--- Use val of "HTTPOnly" or leave blank --->
header = "cfid" & "=" & session.cfid & ";domain=." & domain & ";path=" & path & ";" & secure & ";" & HTTPOnly;
header = "cftoken" & "=" & session.cftoken & ";domain=." & domain & ";path=" & path & ";" & secure & ";" & HTTPOnly;
Basically, the code expires the current cookies, and re-creates then using the same values. Along the way it specifies the HTTPOnly and Secure header. I call this method in OnSessionStart of the Application.cfc:
HTTPOnly works great. But, Secure has given me a bit of a head scratcher. If you set the secure value on a cookie, without using HTTPS the session is created new each time which defeats the purpose.
Ideally, I want to turn the secure setting on and off as people switch from secure to non-secure portions of the site, keeping the same same session users surf the site. ( This doesn't quite work as I had hoped, but more on that later ).
I create a session variable in onSessionStart, named previousURLSecure:
<cfset session.previousURLSecure = cgi.https>
This defaults the value. Now, in OnRequestStart I can previousURLSecure against cgi.https . If they have changed, that means that the site surfers have moved from secure to non-secure, or non-secure to secure and we need to swap the secure attribute of the cookie. This is the code in onRequestStart:
<cfif (session.previousURLSecure NEQ cgi.HTTPS)>
<cfset session.previousURLSecure = cgi.HTTPS>
It's a quick check, and if things have changed it calls the recreateSessionCookies method. It also saves the HTTPS state of the current URL.
This code is live on the Flextras site right now. So, go over and add something to your cart (non-secure), then sign in (secure). You should see the item in your cart.
The go to the address bar of your browser and change 'http' to 'https'. The items in your cart will vanish and your login go away. This does not happen if you never use the secure attribute when creating, or modifying, the cookies.
So my conclusion is that you can easily go from non-secure to secure cookies, but you can't go the reverse. If you use non-secure cookies they'll work for both HTTP and HTTPS. But, secure cookies are not accessible via HTTP. Am I right?
I'm thinking this may be expected behavior for security purposes to prevent some form of session hijacking or other hack attacks. IS it?
I thought of a few hacks, such as storing a copy of the session ID in an alternate cookie, and using that to reset CFID/CFToken. But, I doubt that will be acceptable from a PCI Compliance standpoint.
I'm worried that not even this approach will be acceptable to the PCI Compliance scanner. If not, I'll have to move the whole site to HTTPS; which may suck, especially for file downloads.
So, has anyone out there had any experience with this sort of thing? Got any suggestions for me?
If you can help, I have some "Friend and Family" discount passes to Flex Camp Wall Street coming up next week.