Search code examples
jqueryajaxsecuritycoldfusioncsrf

How to restrict a remote coldfusion function from being called directly? (AJAX)


I have a Coldfusion server with several applications. Many of these applications use a generic set of component functions for both server-side rendering and AJAX calls. Let's call this generic component file:

serverfunctions.cfc

These functions are stored in a folder separate from other applications, although the component files in each of these applications (cf)include the generic component file as a template. Let's call these app-specific component file:

appfunctions.cfc

AJAX requests go to appfunctions.cfc even if they are for methods in serverfunctions.cfc. This cuts down on redundant code and also allows me to check for CSRF attacks in the application.cfc OnRequestStart method before processing the AJAX request.

However...

I would like to enforce a requirement that EVERY AJAX request has to go through the appfunctions.cfc component, and serverfunctions.cfc CANNOT be called directly from the client. This way, other programmers on this codebase are prevented from "accidentally" setting up AJAX calls without proper CSRF protection.

So far I have tried to adjust the access attribute on the remote functions but that only returns "403 - Forbidden".


Solution

  • Drawing inspiration from @BradWood's comments, I have come to an answer.

    tl;dr: Create a server mapping for the serverfunctions folder and extend it from the appfunctions component.

    As stated in the question, my goal is to force AJAX calls for server-level functions through application-level components to enforce a preemptive CSRF token check (which has to be done at the application-session level and keeps the server-level functions totally stateless), while keeping redundant code per-application down to a minimum.

    A blog post by Ben Nadel helped bring this all together: http://www.bennadel.com/blog/2115-extending-the-application-cfc-coldfusion-framework-component-with-a-relative-path-proxy.htm

    Here is a code example:

    server/serverfunctionsfolder/serverfunctions.cfc

    <CFCOMPONENT>
    <cffunction name="getAHatForYourCat" access="package" returntype="string" returnformat="plain">
      <cfargument name="cat" type="string" required="yes" default="cat">
      <cfreturn cat & " and a hat.">
    </cffunction>
    </CFCOMPONENT>
    

    server/app/appfunctions.cfc

    <CFCOMPONENT extends="/serverfunctionsfolder/serverfunctions">
    <cffunction name="getAHatForYourCat" access="remote" returntype="string" returnformat="plain">
      <cfargument name="cat" type="string" required="yes" default="cat">
      <cfreturn Super.getAHatForYourCat(cat)>
    </cffunction>
    </CFCOMPONENT>
    

    server/app/Application.cfc (OPTIONAL-ISH)

    (This depends on how you want to do it. You can create a mapping in application.cfc as shown below for this example, although I ultimately made the mapping in the ColdFusion administrator.)

    <cffunction name="onRequestStart">
        <cfset this.mappings['/serverfunctionsfolder'] = ExpandPath("../serverfunctionsfolder/")>
    </cffunction>
    

    server/app/appscript.js

    function getACatAndAHat(cat)
    {
      var saveUrl = "components/appfunctions.cfc?method=getAHatForYourCat";
      var saveData = {};
      saveData['cat'] = cat;
      $.ajax({
        type:"POST",
        url: saveUrl,
        data: saveData
      }).done(function(data) {
        alert(data);
      }).fail(function(data) {
         alert("Get a hat for your cat failed");
       });  
    }
    
    // Method call with this argument returns "A cat and a hat."
    getACatAndAHat("A cat");
    

    I did not include the code to generate an anti-CSRF token, prepend it to AJAX requests, or validate the token with OnRequestStart because implementations may vary and it isn't entirely relevant to the question or answer.