
This document presents a security analysis of vulnerability CVE-2025-2825 affecting the server component of CrushFTP — a commercial solution for file transfer and storage (FTP, SFTP, HTTP/S, S3-like interfaces, etc.).
The defect is classified as an authentication bypass, allowing a remote unauthenticated attacker to gain administrator privileges. Successful exploitation provides access with crushadmin privileges, the ability to view and modify files, manage user accounts, and perform administrative operations through the CrushFTP web interface and API.
Reported affected versions (according to public advisories and researcher reports):
⚠️ Note: Some publications contain duplicates and overlaps of CVE identifiers (e.g., CVE-2025-31161).
Step-by-step analysis of the vulnerability and demonstration of the full research cycle, including:
According to public reports, the vulnerability has a critical risk:
Instances with the following characteristics are critical:
CrushFTP (any edition with a web interface).CrushFTP implements support for an S3-like API. Authentication uses the Authorization header of the form:
Authorization: AWS4-HMAC-SHA256 Credential=<AccessKey>/<Date>/<Region>/s3/aws4_request, SignedHeaders=<Headers>, Signature=<Signature>
The server extracts AccessKey from Credential and should verify the signature. However, a mistake was made in the code when handling the lookup_user_pass flag.
// ServerSessionHTTP.java, method loginCheckHeaderAuth()
if (this.headerLookup.containsKey("AUTHORIZATION") &&
this.headerLookup.getProperty("AUTHORIZATION").trim().startsWith("AWS4-HMAC")) {
boolean lookup_user_pass = true; // ← critical error
if (s3_username3.indexOf("~") >= 0) {
user_pass = user_name.substring(user_name.indexOf("~") + 1);
user_name = user_name.substring(0, user_name.indexOf("~"));
lookup_user_pass = false;
}
if (this.thisSession.login_user_pass(
lookup_user_pass,
false,
user_name,
lookup_user_pass ? "" : user_pass)) {
// Successful authentication
}
}
The lookup_user_pass flag is directly passed as anyPass:
if (anyPass && user.getProperty("username").equalsIgnoreCase(the_user)) {
return user; // authentication without password verification
}
Thus:
~ character, the flag remains true.crushadmin).CrushAuth cookie and the c2f parameter, this allows bypassing authentication and gaining administrative access.In version 11.3.1 and newer, the developers:
s3_auth_lookup_password_supported parameter (default false), blocking the vulnerable scenario.~.lookup_user_pass → anyPass.s3_auth_lookup_password_supported disabled.Exploitation of CVE-2025-2825 is quite simple and does not require complex preparation. An attacker only needs to send a specially crafted HTTP request containing two key elements:
Authorization header in AWS S3 format, containing a valid existing username (Credential field with AccessKey/username).CrushAuth cookie in the expected format and the c2f parameter in the URL/request body, whose values logically correspond (the cookie format must match the structure expected by the server).If the server is vulnerable (version in the range 10.0.0—10.8.3 or 11.0.0—11.3.0 and no fixing patch applied), this combination forces the authentication handler to follow the vulnerable path, where the password lookup flag (lookup_user_pass) is interpreted as 'any password is acceptable', and the user is authenticated by name alone without password verification.
Important note:
Exploitation of this vulnerability typically requires sending two consecutive requests. The first, a so-called 'warm-up' request, triggers the vulnerable authentication process on the server. A characteristic sign that the server has entered the required state is receiving a
502 Bad Gatewayerror or simply a timeout. Immediately after, the second, main request is sent, which performs the useful action (e.g., creating a user) while the server remains in the vulnerable state.
GET /WebInterface/function/?command=getUserList&serverGroup=MainUsers&c2f=1111 HTTP/1.1
Host: target-server:8080
Cookie: CrushAuth=1743113839553_vD96EZ70ONL6xAd1DAJhXMZYMn1111
Authorization: AWS4-HMAC-SHA256 Credential=crushadmin/
For testing, I used the recently released HTB lab Soulmate, which requires exploitation of CrushFTP.

Thanks to this vulnerability, a new user with admin rights can be added using the setUserItem command. To do this, run new_user.py
python3 new_user.py --target_host http://ftp.soulmate.htb/ --port 80 --target_user crushadmin --new_user literide --password literide
Parsing the Authorization header: When the server sees an authorization header in S3-like format (AWS4-HMAC...), it extracts from the Credential field the client identifier (AccessKey / username). At this stage, the server obtains a string which it considers the username — this is the identification value used further in the authentication logic.
The lookup_user_pass flag and its role:
In the code there is a boolean flag lookup_user_pass, which should indicate where to get the password during verification:
Flag propagation along the call chain:
Approximate path: ServerSessionHTTP.loginCheckHeaderAuth() → Session.login_user_pass(...) → UserTools.ut.verify_user(...). At the input, the flag defines behavior, and in verify_user it leads to an early return of the found user object without password comparison, if the name matches. This gives the bypass of authentication — the server 'recognizes' the user by name and considers them authenticated.
Supporting elements (cookie / c2f):
In public analyses, it was noted that the handler expects the correct format of cookie/parameters to match the request with session/context. However, the key defect is precisely in the logical error of processing lookup_user_pass; other elements only help the request pass through the standard processing branches.
Key root cause
Overloading the meaning of the flag: a flag that was supposed to decide 'where to get the password' ended up being used as 'allow any password'. This, combined with simplified parsing of Credential, led to the situation where having a valid username was sufficient to obtain user credentials without password verification.
Passive template:
Since determining the exact version of CrushFTP is not possible in most web services, this template simply checks whether the service uses CrushFTP.
Best used in combination with the active template
Active template:
The active template checks if the getUserList command is possible.

Multithreaded script
The script works similarly to the active template, but much faster and supports checking multiple hosts simultaneously.
python3 scan.py -t http://ftp.soulmate.htb/ -p 80 -u crushadmin

To reduce risks associated with CVE-2025-31161, the following steps are recommended:
Immediate update:
Applying Workaround (if update is not possible):
s3_auth_lookup_password_supported parameter to false. This will disable the vulnerable authentication logic without updating the entire product.Risk compensation measures:
AWS4-HMAC-SHA256 in the Authorization header, especially if you are not using S3 integration.Authorization headers to endpoints not intended for the S3 API.