Tuesday, February 27, 2024

What Does ColdFusion's verifyClient() Do?

I recently saw a ColdFusion question about verifyClient and remote CFC functions.  I already have strong opinions about why you don't want to use remote CFC functions, but I was unfamiliar with verifyClient.  That led me to take a look at how it works and I thought it was interesting enough to write about.  

The verifyClient functionality was added in ColdFusion 8.  (It's not supported in Lucee.)  verifyClient can be used as an attribute in a remote CFC method:

or as a standalone function.  The documentation for the standalone function states:


"Use this function to help prevent security attacks where an unauthorized party attempts to perform an action on the server, such as changing a password. As a general rule, use this feature for Ajax requests to the server to perform sensitive actions, such as updating passwords.If you call this function, you must enable client management or session management in your application; otherwise, you do not get an error, but ColdFusion does not verify clients. Use this function only on pages that respond to client-side ColdFusion Ajax features, such as bind expressions. These features include code that correctly sends the security token when needed."


That description is a little unclear to me -- but I think this is intended to be a "security token" similar to a CSRF Token, so that an attacker can't do something like getting a victim user to load some HTML similar to the following, which could then silently change their password (*):


<img src="https://myapp.tld/utils.cfc?method=changepass&newpass1=oops&newpass2=oops">


(*) And a change password function should require the user's current password anyway, but that's a completely different topic.


Used in this way to prevent things like Cross Site Request Forgery, verifyClient could have some security benefit.  However, it is important to note that we are only verifying the client (the web browser) here -- that is, that the user is actively controlling his browser.  In this context, the client is completely distinct from the user, and your application still needs to perform any user-based authentication and authorization checks prior to completing any sensitive actions.  

Let's use a very simple remote CFC method that enforces a verifyClient check as an example:


<cffunction name="test" output=yes access="remote" verifyClient="yes"> 
     <cfreturn "winner">
</cffunction>


If we try to access that method without a security token, we get the following error:



That error message is a little confusing and misleading though, since as you'll see, we don't actually need a valid login to access the page.  Remember, we're really only validating the client (browser) here and not the user.

So how does this all work?  The methods that we care about are verifyClient() and constructClientId() in coldfusion.tagext.html.ajax.AjaxUtils.class:




The verfyClient() method compares a _cf_clientid value passed in a URL or FORM parameter against a server-side clientId value.  This clientId value is created by the constructClientId() method, which generates an MD5 hash of the session.urltoken value.  And what is session.urltoken?  We can either probe the session.urltoken value ourselves, or refer to the documentation that indicates it's a string in the format of:

CFID=my_cfid_value&CFTOKEN=my_cftoken_value

We can then grab our CFID and CFTOKEN cookies, generate an uppercase MD5 hash with them, and use it to successfully pass the verifyClient check.  All users will have CFID and CFTOKEN cookies, even if they are not authenticated users with authenticated sessions:



It's also worth noting that cookie values aren't an ideal choice for a CSRF token, since they can sometimes be exploitable if the application is affected by flaws including session prediction, session fixation, and cookie theft (such as via Cross Site Scripting, if the cookies don't have the HTTPOnly flag set).


Wrapping things up -- keep in mind that we are only verifying the client (browser) with verifyClient, and are not performing any authentication or authorization checks based on the requesting user.  So if you need to prevent direct unauthorized access, you will want to add additional layers of access control, authentication, and authorization checks.  Plus you probably don't want to use remote CFC methods anyway. 

I'd also like to thank Adobe PSIRT and the Adobe ColdFusion team, who confirmed that verifyClient is meant to prevent CSRF-type attacks and enforce that one user's _cf_clientid token can't be used by another user, browser, or session.  I suspect that verifyClient was an earlier native option for some degree of CSRF protection in AJAX applications that was subsequently overshadowed by a more general CSRF solution with CSRFGenerateToken and CSRFVerifyToken added in ColdFusion 10.


[ NB: Despite the similar name, the _cf_clientid URL parameter is unrelated to ColdFusion's <cfclient> tag and associated _cfclient URL parameter, or to Lucee's cf_client_* client management cookies. ]


No comments:

Post a Comment