I just checked off one of the requirements for one of my projects at work: protect from cross-site request forgeries, or CSRF. It was much easier to accomplish than I expected-- here are the details.
To prevent this kind of attack from targeting your own site, you can require that a session-based FORM or URL token be passed with each request. If it doesn't exist or doesn't match what you last sent down to the browser, then you'll know that the request didn't originate from your site. So first, I dropped some code into the
onRequestStart function of my Application.cfc file which creates a new token for each page reqest and stores it in session:
<!--- If a token doesn't exist in the form scope ---> <cfif not IsDefined("FORM.token")> <!--- Create one and store it in session ---> <cfset FORM.token = '' /> <cfset SESSION.token = CreateUUID() /> </cfif> <cfif IsDefined("FORM.fieldNames") and FORM.token neq SESSION.token><!--- Else if a token does exist, but it doesn't match what's in session ---> <!--- Abort all page processing ---> <cfabort /> </cfif>
Some of you might wonder why I don't use a
cfparam instead of checking whether the form token exists; the answer is that I need to check whether that variable exists so I know whether to create a new value in session. So what now? Just embed the token in your forms:
<form action="anypage.cfm" method="post"> <input type="hidden" name="token"" value="#SESSION.token#" /> ... </form>
This will be enough to stop all CSRF attacks, at least for my form-based site, in seven lines of code. The code makes a new session token for each request, except when one is submitted. If the token doesn't exist or doesn't match, the page is aborted. Now, if your site allows actions based on query string variables, or even if you just call a specific URL (e.g., calling "deleteAccount.cfm" deletes your account), you'll want to modify the code to also 1) pass the token in each link in the site, and 2) look for the token in the query string as well as in the form scope. Has anyone else found a different technique?
[Note: This posting was altered to prevent page processing from proceeding when a form token was not passed. Thanks to Jason Dean for pointing out this important mistake!]