A cross-site request forgery (CSRF) occurs when a hacker takes advantage of the fact that users don't always log out of the websites and web applications they visit. The hacker creates a URL or a form on a website they control that passes valid data to a valid destination on the target website and hopes that a user who is still authenticated to that target website clicks that malicious URL or form. If such a user falls into the trap, the target website will process the request just as if the user had executed the action within the target website under normal circumstances.
One common method for preventing CSRF attacks is to generate a unique value every time a user visits a form on the website and store that value both within the user's session and within the form itself as a hidden field. When the form is submitted, the value in the form is checked against the value stored within the user's session, and if they don't match the form submission isn't processed. The next time the user encounters a form (even if it's the same form), a new unique value is generated. Without a way of knowing what that unique value is at any given time, the hacker cannot build a form or construct a URL that simulates a legitimate request, and the attack fails.
Rather than have to remember to create these unique values and include them within every form (or every URL that executed some sort of data operation), and then check the validity of the submitted value on each processing page, I wanted to see if there was a way I could build CSRF security into the structure of my Model-Glue applications.
First, I wrote two new controller functions in my authentication/authorization controller CFC:
<cffunction name="secureTransmissionURL" access="public" returntype="void" output="false"> <cfargument name="event" type="any"> <cfif arguments.event.valueExists("token") EQ false> <cfset session.token= CreateUUID()> </cfif> <cfset arguments.event.setValue("secureMyself","index.cfm?token=" & Hash(session.token,"SHA-256") & "&" & arguments.event.getValue("eventValue") & "=")> </cffunction> <cffunction name="validateTransmissionURL" access="public" returntype="void" output="false"> <cfargument name="event" type="any"> <cfif arguments.event.valueExists("token") EQ false> <cfset arguments.event.setValue("validationProblem","noToken")> <cfset arguments.event.addResult("invalidTransmission")> <cfelseif arguments.event.getValue("token") NEQ Hash(session.token,"SHA-256")> <cfset arguments.event.setValue("validationProblem","tokenMismatch")> <cfset arguments.event.addResult("invalidTransmission")> </cfif> </cffunction>
The secureTransmissionURL() function looks to see if there is a variable named "token" stored within the event object (for those unfamiliar with Model-Glue, the event object serves as a container for all of the variables associated with the request, including form and URL variables). If no token variable exists in the requet, a variable named "token" is created or updated within the session scope and given a unique UUID value.
In the final line of the secureTransmissionURL() function, a new variable called "secureMyself" is created within the event object, and is given a value that serves as the starting point for any URL I want to protect against CSRF attacks within my Model-Glue application. This line deserves a bit of background information...
In Model-Glue (as with many of the ColdFusion frameworks), all requests run through the index.cfm page, and you dictate how the request should be routed by adding a particular variable (called the "eventValue" variable in Model-Glue) to the URL. In Model-Glue, the default name for that variable is "event," so if you kept that default, the URLs you would use to navigate within your Model-Glue application would look like this:
Because it is possible to change that eventValue variable name from "event" to something else ("action", "goto", etc) with a simple change to the Model-Glue configuration settings in your application, it's not safe to hard-code "index.cfm?event=" into your URLs in your pages. So the standard practice is to use an event variable automatically provided by Model-Glue called "myself" to build your URLs. So instead of writing out "index.cfm?event=mainMenu", you would do something like this on your web page:
<cfset deleteRecordURL= event.getValue("myself") & "deleteRecord"> <cfoutput> ... <a href="#deleteRecordURL#&recordId=12">Delete record 12</a> ... </cfoutput>
(Note: normally the name of the event/event handler, in this case "deleteRecord", would actually come from an event variable as well, but I wanted to keep my example simple)
The "secureMyself" variable created in that final line of secureTransmissionURL() serves as a CSRF-secured alternative to the "myself" variable provided by Model-Glue. It adds the hashed value of the UUID value stored in the session scope to the "token" URL variable in front of the eventValue variable in the URL string, allowing me to use "secureMyself" to build my URLs in place of "myself" where needed (note: I would still use "myself" to build URLs that don't directly process data, such as a URL that sent the user to a menu page):
<cfset deleteRecordURL= event.getValue("secureMyself") & "deleteRecord"> <cfoutput> ... <a href="#deleteRecordURL#&recordId=12">Delete record 12</a> ... </cfoutput>
When the secured URL is rendered by ColdFusion, it ends up looking something like this:
The second function, the validateTransmissionURL(), is meant to be executed prior to processing a CSRF-secured page request. It checks to see if a token variable was included in the request, and if so it checks if the value of the submitted token matches the hashed value of the token variable stored in the session. If either of those conditions fail, a result of "invalidTransmission" is added to the event object, which effectively prevents the request from proceeding to the code that processes the request (that interacts with your persistent data).
After adding these two functions, I then incorporated them into my application using the following two event types:
<event-types> <event-type name="permitted"> <before> <broadcasts> <message name="checkAuthorization" /> <message name="secureTransmissionURL" /> </broadcasts> <results> ...<!--Whatever you do if user is not authenticated yet--> </results> </before> </event-type> <event-type name="validateTransmission"> <before> <broadcasts> <message name="validateTransmissionURL" /> </broadcasts> <results> <result name="invalidTransmission" append="validationProblem" do="invalidTransmission" redirect="true" /> </results> </before> </event-type> </event-types>
If you're unfamilar with event types in Model-Glue, I encourage you to read the documentation on the Model-Glue wiki. In my application, all of my event handlers that are only accessible to the user after they've authenticated/logged in are wrapped with the "permitted" event type defined above. By adding a message broadcast that calls my secureTransmissionURL() controller function to that event type, I ensure that the "secureMyself" event variable is available to use on all of my authenticated view pages.
The "validateTransmission" event type, which will make sure that the submitted URL contains the proper token value, will be applied only to my event handlers that process a form submission or execute a database transaction based on URL variables submitted in the request. If the token is missing or incorrect, Model-Glue redirects the action to the "invalidTransmission" event handler, which will react to the possible CSRF attack. My "invalidTransmission" event handler presents an normal error message to the user (as it's the user, not the hacker, that will see the result of the attack) but notifies me that a possible CSRF attack has occurred.
Once I had these controller functions and event types in place, all I needed to do to secure my forms and hyperlinks against CSRF attacks was to use the "secureMyself" event variable to build the URLs for those forms and hyperlinks and to add the event type "validateTransmission" to those event handlers that processed submissions from those forms and hyperlinks.
If you want to learn more about securing against CSRF attacks, I would recommend that you read some of the blog posts that helped me understand CSRF attacks: