April 23, 2025
This article explores the technical details of the CrushFTP vulnerability: CVE-2025-2825, also known as CVE-2025-31161.

I want to begin this analysis by acknowledging the excellent work of the ProjectDiscovery researchers. I followed their research, performed code analysis to understand the vulnerability, and tested multiple scenarios, uncovering additional bugs in the software.
The researchers at the Outpost24 team discovered the Authentication bypass vulnerability in the CrushFTP software. The vulnerability received a CVSS score of 9.8 (Critical), and the following versions are vulnerable to Authentication Bypass.
- Versions 10.0.0 through 10.8.3
- Versions 11.0.0 through 11.3.0
The CrushFTP team has fixed the vulnerability in its latest version.
About CrushFTP
CrushFTP is an enterprise-grade file transfer software.CrushFTP supports various secure file transfer protocols, including FTP, SFTP, and FTPS, ensuring the secure transmission of files.
Authentication Bypass
This vulnerability enables authentication bypass using an incorrect password or no password at all. The bypass was possible due to an authentication flag (a boolean value) was used for password verification. After a successful authentication, the TA can create multiple backdoor accounts and deploy malware or ransomware for further exploitation.
CrushFTP Technical Analysis
According to the ProjectDiscovery analysis and code diff, the vulnerable code resides in the ServerSessionHTTP class.
The vulnerable code is in the loginCheckHeaderAuth() method, which checks for an “Authorization” header starting with the string “AWS4-HMAC”. If found, the username is extracted from the Authorization header. However, it fails to verify the presence of the user’s password.
Authorization: AWS4-HMAC-SHA256 Credential=crushadmin/
LoginCheckHeaderAuth Method Analysis
The method defines a boolean variable, set to true, which, based on its name, appears to be used for validating the user’s password.
boolean lookup_user_pass = true;
The method searches for the presence of ~ in the user name. If found the variable look_user_pass is set to False.
The method then calls the login_user_pass method with the following variables:
this.thisSession.login_user_pass(lookup_user_pass, false, user_name, lookup_user_pass ? "" : user_pass)
And that can be translated to:
login_user_pass(true, false, user_name, user_pass)
Please note that the user password (user_pass) is set to null, and this null value is passed as a parameter to the login_user_pass function.
Login_user_pass Method Analysis
boolean login_user_pass(final boolean anyPass, final boolean doAfterLogin, String user_name, String user_pass)
The look_user_pass variable’s value is assigned to the Anypass variable, setting Anypass to true.
The login_user_pass method attempts to verify the user’s password by invoking the verify_user method, with the return value stored in a boolean variable named verified.
The first IF statement in the below code block sets the otp_valid variable to True. The second IF statement also executes because:
- The verified variable is True.
- The condition (verified && otp_valid) evaluates to True.
verified = this.verify_user(user_name, verify_password, anyPass, doAfterLogin);
if (verified && this.user != null && this.user.getProperty("otp_auth", "").equals("true"))
{
SNIP
otp_valid = true;
SNIP
}
else if ((verified && (this.user == null || !this.user.getProperty("otp_auth", "").equals("true"))) || (verified && otp_valid)) {
The method evaluates other conditions , sets the user_logged_in variable as True and the following code block will return true.
this.uiPUT("user_logged_in", "true");
if (this.uiBG("user_logged_in")) {
this.active();
SNIP
return true;
Verify_user Method Analysis
The verify_user method has the following parameters. The Anypass variable is set to True as mentioned in the above section.
boolean verify_user(String theUser, String thePass, boolean anyPass, boolean doAfterLogin)
Based on how the Anypass variable is used, we can infer that the developer assumed it defaults to False. The method checks the password via the checkPassword method, and if the password matches, the Anypass variable is set to True.
if (UserTools.checkPassword(thePass))
anyPass = true;
However, the Anypass variable is always True, rendering the password check irrelevant.
The method invokes the verify_user method from the UserTools class with the following parameters:
this.user = UserTools.ut.verify_user(ServerStatus.thisObj, theUser, thePass, uiSG("listen_ip_port"), this, uiIG("user_number"), uiSG("user_ip"), uiIG("user_port"), this.server_item, loginReason, anyPass);
The following code block returns a user object since the Anypass variable is True.
if (anyPass && user.getProperty("username").equalsIgnoreCase(the_user))
return user;
The method evaluates other conditions and ultimately returns True.
In summary, both the verify_user and login_user_pass methods return True, triggering the execution of a code block in the ServerSessionHTTP class. This block retrieves the user password from the client module, and the user is authenticated.
if (this.thisSession.login_user_pass(lookup_user_pass, false, str2, lookup_user_pass ? "" : str1)) {
if (lookup_user_pass)
str1 = Common.encryptDecrypt(this.thisSession.user.getProperty("password"), false);
Proof Of Concept
To perform this attack, the attacker must know the admin account’s username. The CrushFTP software recommends creating a default user account named “crushadmin”. All the publicly available CrushFTP servers using the default “crushadmin” account are vulnerable to exploitation and attack.
We can exploit this vulnerability using the following steps:
- If the CrushFTP server accepts external connections, the attacker (TA) can authenticate without a password/ wrong password.
- Can use any forged cookie , but the c2f value must match with last four digit of cookie.
- The TA sends a custom GET request with an Authentication header containing the username.
- Once authenticated, the attacker gains admin access and no longer needs to include the Authentication header.
- Post-authentication, the attacker can retrieve user lists, account details, and other server data.
- The attacker can create backdoor accounts by sending a custom POST request with an Authentication header.
- Using the backdoor account, the attacker can log in and upload malicious files to the customer environment for further exploitation.
Scenarios Tested
Login Without an Active Session:
(1) Retrieve User Information
In my initial attempt, without signing into the CrushFTP console or having an active session, I sent a GET request with a forged cookie and successfully bypassed authentication.


(2) Add a backdoor account (requires an authentication header) (with wrong password)

Login with an Active Session
In this attempt, I logged into the CrushFTP console. Authentication can be bypassed using any forged cookie, as the cookie is not verified during the process.

Retrieve user information without an authentication header after an authentication bypass
After authentication, the TA can retrieve any kind of user details (ex: getuserlist, getuserInfo, getusername etc)

Add a backdoor account using a nonexistent user account after authentication bypass

Add a backdoor account using a brute force nonexistent user account

Weak Authentication in the Latest Version (10.8.4)
A single cookie generated after a successful login secures the entire session. The same cookie is reused for all other functions. If the attacker obtains this cookie, they can perform the above-listed exploits/POC and create a backdoor account with a nonexistent username.

