0%

CrushFTP 认证绕过(CVE-2025-2825)

漏洞成因

crushftp.server.ServerSessionHTTP.handle_http_requests方法中对用户名有特殊处理。
当用户名中不包含~的时候 lookup_user_pass的值为true

1
2
3
4
5
6
boolean lookup_user_pass = true;
if (s3_username.indexOf("~") >= 0) {
user_pass = s3_username.substring(s3_username.indexOf("~") + 1);
user_name = s3_username.substring(0, s3_username.indexOf("~"));
lookup_user_pass = false;
}

后面在调用 login_user_pass 方法的时候传入的第一个参数就是lookup_user_pass

1
this.thisSession.login_user_pass(lookup_user_pass, false, user_name, user_pass))

login_user_pass方法最终会调用到crushftp.handlers.UserTools.verify_user(crushftp.server.ServerStatus, java.lang.String, java.lang.String, java.lang.String, crushftp.handlers.SessionCrush, int, java.lang.String, int, java.util.Properties, java.util.Properties, boolean)方法
anyPasstrue的时候只比较了用户名是否相等就直接返回了user对象没有对密码进行验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public Properties verify_user(ServerStatus server_status_frame, String the_user, String the_password, String serverGroup, SessionCrush thisSession, int user_number, String user_ip, int user_port, Properties server_item, Properties loginReason, boolean anyPass) {
if (the_user.indexOf("\\") >= 0) {
the_user = the_user.substring(the_user.indexOf("\\") + 1);
}

if (!the_password.startsWith("SHA:") && !the_password.startsWith("SHA512:") && !the_password.startsWith("SHA256:") && !the_password.startsWith("SHA3:") && !the_password.startsWith("MD5:") && !the_password.startsWith("MD5S2:") && !the_password.startsWith("CRYPT3:") && !the_password.startsWith("BCRYPT:") && !the_password.startsWith("MD5CRYPT:") && !the_password.startsWith("PBKDF2SHA256:") && !the_password.startsWith("SHA512CRYPT:") && !the_password.startsWith("ARGOND:")) {
String the_password2 = Common.url_decode(the_password);
if (!the_password2.startsWith("SHA:") && !the_password2.startsWith("SHA512:") && !the_password2.startsWith("SHA256:") && !the_password2.startsWith("SHA3:") && !the_password2.startsWith("MD5:") && !the_password2.startsWith("MD5S2:") && !the_password2.startsWith("CRYPT3:") && !the_password2.startsWith("BCRYPT:") && !the_password2.startsWith("MD5CRYPT:") && !the_password2.startsWith("PBKDF2SHA256:") && !the_password2.startsWith("SHA512CRYPT:") && !the_password2.startsWith("ARGOND:")) {
Properties user = null;
Log.log("USER_OBJ", 2, "Validating user " + the_user + " with password " + (the_password != null && !the_password.equals("")) + " ");
if (!ServerStatus.BG("blank_passwords") && the_password.trim().equals("") && !anyPass && !the_user.equalsIgnoreCase("ANONYMOUS")) {
return null;
} else {
try {
user = ut.getUser(serverGroup, the_user, true);
} catch (Exception var23) {
Log.log("USER_OBJ", 2, var23);
}

Log.log("USER_OBJ", 1, "Validating user " + the_user + " with local user file:" + (user != null ? String.valueOf(user.size()) : "no user.XML found!"));
if (user != null) {
loginReason.put("reason", "valid user");
ServerStatus var10000 = ServerStatus.thisObj;
if (ServerStatus.BG("secondary_login_via_email") && the_user.indexOf("@") >= 0 && user.getProperty("username").indexOf("@") < 0) {
the_user = user.getProperty("username");
}

if (anyPass && user.getProperty("username").equalsIgnoreCase(the_user)) {
return user;
}

修复方案

修改了this.thisSession.login_user_pass(false, false, user_name, user_pass))的调用逻辑,传入的第一个参数被固定设置为false

Buy me a coffee.

欢迎关注我的其它发布渠道