As the description from this CVE,
I will download two versions 2018.0.15 and 2018.0.16 to diff. We can easily install Adobe Coldfusion with docker via link: https://hub.docker.com/r/adobecoldfusion/coldfusion2018
LFI
We should only diff two patch 15 and 16. The result looks good. Quickly I can realize the change:
In coldfusion.runtime.JSONUtils#convertToTemplateProxy
, server check allowNonCFCDeserialization
and not accept deserialization from CFClient with ending file .cfc
.
In vulnerable version, convertToTemplateProxy
takes the input as a Map, get the key _metadata
as an Object, if it's not null, the key classname
of the metatdata
object will be taken. This value called serverClass will be checked if start with "/"
and not null. Finally, server will get the real path from this value via FusionContext.getRealPath() in coldfusion.filter.FusionContext#getRealPath(String, boolean)
, totally return the same path from ServletContext.getRealPath()
. This function return the real path in disk file system, so we can use it to exploit LFI. (Link).
The pageFile value goes to coldfusion.runtime.TemplateProxyFactory#resolveName(coldfusion.runtime.TemplateProxy, coldfusion.runtime.NeoPageContext, File, String, boolean, Map, HashSet, boolean)
Continue go to coldfusion.runtime.TemplateProxyFactory#getCFCInstance
Continue to coldfusion.runtime.TemplateClassLoader#newInstance(ServletContext, String, coldfusion.runtime.VariableScope, coldfusion.runtime.LocalScope)
Goes to coldfusion.runtime.TemplateClassLoader#findClass(ServletContext, String)
Goes to coldfusion.util.SoftCache#get
Goes to get_statsOn
or get_statsOff
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
.
Finally translateJava
will perform action: the file will be compiled on the fly as source code for a Java ColdFusion component or module.
The root cause is in coldfusion.runtime.JSONUtils#convertToTemplateProxy.
This function called when we perform action:
coldfusion.runtime.JSONUtils#deserializeJSON(Object, boolean, boolean)
→coldfusion.runtime.JSONUtils#parseJSON
→coldfusion.runtime.JSONUtils#parseObject
→coldfusion.runtime.JSONUtils#convertToTemplateProxy
Found a place that deserializeJSON is called, i got coldfusion.filter.ComponentFilter#invoke
get input from _variables
To trigger this vuln, we have to bypass three check:
-
pagePath
not end with "cfr" -
method
is not null -
CFClientCall must be true
The pagePath is the path to any file in webroot folder that accessible, so can be any .cfc
file for example (because this filetype can be accessed directly).
The method
is taken from url, so can be set as any value via GET parameter.
The context is a FusionContext, and CFCClientCall has been taken from a GET parameter called _cfclient
and set value to true
In short, our endpoint will look like:
/path/to/accessible/file.cfc?method=hehe&_cfclient=true
And our payload will look like:
_variables={"_metadata":{"classname":"../../../../../../../../../../../etc/passwd"}}
Some POC:
RCE
ColdFusion Markup Language (CFML) includes a set of tags that you use in ColdFusion pages to interact with data sources, manipulate data, and display output. CFML tag syntax is similar to HTML element syntax.
Attacker can take advantage of CFML to execute a shell command. For example <cfexecute name='calc'></cfexecute>
will pop a calc.
In this scenario, firstly attacker send a request has the payload and it will be in log. Secondly, attacker use the above LFI to navigate to the log file. As we said before, NeoTranslator will translate the contents of an arbitrary file the attacker can specify as Java class of ColdFution, which is CFML. This technique is similar with log poisoning that we usually see in PHP.
So firstly we will send a request with payload:
_variables={<cfexecute name='sh touch /tmp/test.txt'></cfexecute>}
The result is 500 JSON parsing failure at character 1:'_' in _variables={<cfexecute name='touch /tmp/test.txt'></cfexecute>}
And we can see in the log file logs/coldfusion-out.log:
Now, we will send the payload: _variables={"_metadata":{"classname":"../logs/coldfusion-out.log"}}
And the result: