Monday, January 26, 2026

More Scope Injection for Fun and Profit (or, why those security updates broke your functions)


Introduction 

Like "Big Two-Hearted River," my last post also has a Part II.  I had previously spent countless hours trying to create a UDFMethod object from user-controlled data via the Service Layer endpoints and eventually gave up decided it was just not possible.  But I had learned more about how lots of ColdFusion internals work -- including variable casting, object types, and functions.  And I kept at it.  While I wasn't able to achieve remote code execution, I ultimately did find a few new scope injection vulnerabilities later on.  These vulnerabilities highlight a broader and often overlooked risk: assumptions about scope isolation and variable safety can quietly break down at the framework level. When that happens, even well-intentioned application logic can become vulnerable in unexpected ways.

The corresponding patches for these vulnerabilities introduced breaking changes with the security fixes -- specifically, the requirement to explicitly declare all arguments for remote functions from APSB25-52 and the changes around scope precedence and variable name reserved words from APSB25-105. Read on as we explore the technical details of how they work and what the underlying risks are.


Turning Failure into Success

I spent a lot of time testing remote functions when trying to build working exploits for the Service Layer endpoints:  building test cases, making minor adjustments, and tracing program flow in my code and the ColdFusion runtime.  (And I was already familiar with the attack surface of remote methods based on some previous vulnerability research.)  One of the things that I noticed was that the searchImplicitScopes setting didn't affect how variables within a remote method were resolved. 

As of ColdFusion 2023 U7 and ColdFusion 2021 U13, ColdFusion sets searchImplicitScopes=false as a default setting, which means that unscoped variables should throw an exception.  Consider the following code, which would be called as a remote method in a CFC:

<cfscript>

remote boolean function test() { 
         if (isAdmin) { 
            writeoutput("yay.  you are admin.");
            return true;
        }  else { 
         return false
        } 
}
</cfscript>


In a .cfm file, our if(isAdmin) check would throw an exception because of the unscoped variable.
But the code paths and servlets for .cfm files and .cfc files are different:  .cfm files are handled by CfmServlet (coldfusion.CfmServlet.class) while .cfc files are handled by CFCServlet (coldfusion.xml.rpc.CFCServlet.class).  One difference between the two is how scopes are searched and variables are resolved.  And while the code above is a contrived example, it was enough to get me thinking about the security implications here.

I also noticed something interesting with the getbuildnumber remote method in the Admin API.  This is pre-compiled code, like most of CF Admin, so we can't obtain the exact original CFML source.  But it's a simple endpoint that takes no arguments and just returns the version of ColdFusion, so we can approximate the functionality with the following CFML code, if we want to be able to play around with a remote CFC method that we can fully control:

<cfscript>
remote string function getbuildnumber() { 
     return server.coldfusion.productversion; 
}
</cfscript>


As expected, this endpoint returns the ColdFusion version when it's called:


But if we pass in a URL parameter named server.coldfusion.productversion this is the value that is returned:


Let's look at a portion of this method after we decompile it:



As we trace the code flow for the _resolveAndAutoscalarize() call, we wind up at this resolveCanonicalName() call in coldfusion.runtime.CfJspPage.class.  As shown above, the call looks like:

_resolveAndAutoscalarize("SERVER", new String[] { "coldfusion", "productversion" });
     
The same arguments are passed to resolveCanonicalName().  If our variable name is two levels deep (keys.length == 1), such as Application.ApplicationName, we'll do a search in the specified scope for that key name.  But in this case we have a three-level variable that we're trying to resolve (server.coldfusion.productversion), so we wind up calling coldfusion.runtime.NeoPageContext.SymTab_resolveSplitName() on the full variable name: 



We can follow the code path to understand how the dotted variable names are searched and resolved, looking at additional functions in coldfusion.runtime.NeoPageContext.class.  And we can see that the ARGUMENTS scope is the first one that is searched.   





With this understanding, it should now be clear what's happening.  When we pass values into our CFC method -- via the URL scope in this example -- they're stored in the ARGUMENTS scope.   We pass in a value for FORM.coldfusion.server.productversion as an argument to getbuildnumber(), and it's stored in ARGUMENTS.coldfusion.server.productversion.  And when the getbuildnumber() method tries to resolve the three-level variable coldfusion.server.productversion, it finds the value we just set in the ARGUMENTS scope before the real value in the SERVER scope.

We can also attach a remote JVM debugger and observe that the server.coldfusion.productversion variable passed in the URL scope gets added to the ARGUMENTS scope of our remote CFC function, and is found by the variable resolution process. 




So what's the impact here?  We’re able to influence resolution of split-name variables (three or more segments) because they are resolved via SymTab_resolveSplitName(), which searches the ARGUMENTS scope before protected scopes.  This could let us control program flow and break security assumptions.  And while the actual impact is dependent on the specific functionality of vulnerable application code, the bottom line is that we’re able to influence how values are resolved, even when they’re expected to be safe and isolated from user control.

This vulnerability was assigned CVE-2025-43559 and addressed in APSB25-52.  The security fix was a potential breaking change that required the arguments to remote methods to be explicitly declared.  More information is available in the associated Tech Note.


An Example of this Vulnerability in CFAdmin - Authenticated Blind SSRF

As a real example of some vulnerable code, let's take a look at a portion of the code from the checkFileExists() remote method in /CFIDE/administrator/updates/download.cfc:




This code is meant to be called automatically, as part of the process when downloading and installing ColdFusion updates.  The method concatenates Application.updateSettings.downloadHome with the filepath URL parameter to construct the path that's passed to the FileExists() call.  As you may know, FileExists() and most file I/O functions in ColdFusion support URLs and other Apache VFS Providers as file paths, which can lead to SSRF when the user can control the path.  So a request like the following:

http://server:8500/CFIDE/administrator/updates/download.cfc?method=checkfileexists&filepath=hello.txt&application.updateSettings.downloadHome=http://my-oast-host.com/

will cause an outbound request to http://my-oast-host.com//hello.txt


This may be a low-risk vulnerability -- a potential attacker must be authenticated to CF Admin to exploit it and it's blind SSRF -- but it’s still useful as a concrete illustration of how scope injection breaks implicit trust boundaries. The mechanics are the same ones that could affect custom application code in less constrained environments.  This vulnerability was assigned CVE-2025-54234 and fixed in APSB25-52.


Exploiting Scope Injection in Non-Remote Functions


But scope injection can affect more than just remote functions.  Adobe's documentation on passing parameters to methods includes examples of passing a user-controlled scope object (such as FORM) as the argumentCollection:


So let's consider the following code, saved as invoke.cfm.  We have a public method named "hello", and when we call it we pass the FORM scope struct as the argumentCollection.

<cfscript>
my.output = hello(argumentCollection=#FORM#);
writeoutput(my.output);
</cfscript>

<cffunction name="hello" access="public" returntype="string" >
    <cfargument name="name" type="string" required="true" >
    <cfoutput>Within the method, server.os.name is: #Server.OS.Name#<br></cfoutput>
    <cfreturn "Hello, #name#!">
</cffunction>


Sending a request like the following:

POST /invoke.cfm HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 70

name=Brian&server.os.name=USER-CONTROLLED-VALUE


demonstrates how, in a public function (not a remote function), the user is able to influence variable resolution by injecting split-name keys into the argumentCollection structure:



This is one more instance of how user input can be dangerous, as we're blindly trusting and consuming the entire FORM scope struct for our argumentCollection.  A more secure approach would be to get individual function arguments from individual user-controlled variables.  

This vulnerability was assigned CVE-2025-61809 and addressed in APSB25-105.  This was a potential breaking change that changed the scope precedence and added additional reserved words for variable names, per the Tech Note


A Note on Lucee


Lucee stores and resolves scopes and variables differently than Adobe ColdFusion.  Based on my testing, Lucee does not appear to be impacted by these issues.  However, I still would recommend the following actions (repeated again in the section below) as general secure application development advice:
  • Do not use remote CFC functions.  Refactor all remote functions to alternate approaches, such as REST API endpoints.
  • Do not pass entire scope structures (Form, URL, etc.) as the argumentCollection to functions. Use explicitly defined arguments rather than consuming an entire scope struct. 


Remediation Recommendations


Across these vulnerabilities, the underlying issue is consistent: implicit behavior around argument handling and scope resolution can undermine assumptions made by both developers and the platform itself.  The fixes Adobe released address these behaviors directly, but application-level code patterns still matter.  Avoid design choices like using remote methods or passing entire scopes into functions.
  • Upgrade ColdFusion to a version that is not affected by these vulnerabilities. 
  • Review all remote functions for the use and resolution of variables that are three or more levels deep.  Due to the large attack surface of remote functions, refactor all remote functions to alternate approaches, such as REST API endpoints.
  • Review all functions (regardless of their access attribute), and refactor any that pass in an entire scope struct as its argumentCollection variable.  Use explicitly defined arguments rather than consuming an entire scope struct.  


Timeline


2025-02-05 - Vulnerability (CVE-2025-43559) reported to Adobe PSIRT

2025-02-12 - Vulnerability (CVE-2025-54234) reported to Adobe PSIRT

2025-05-13 - Adobe Security Bulletin APSB25-52 released

2025-08-07 - APSB25-52 updated to include CVE-2025-54234

2025-08-14 - Vulnerability (CVE-2025-61809) reported to Adobe PSIRT

2025-12-09 - Adobe Security Bulletin APSB25-105 released

2026-01-24 - DistrictCon presentation delivered outlining the vulnerabilities

2026-01-26 - Blog post published


No comments:

Post a Comment