zfile-安全审计

57次阅读
没有评论

ZFile 在线网盘 安全审计报告

审计对象:zfile(main 分支 HEAD 775b017,pom 版本 4.5.0,Java 21 + Spring Boot 3.3.2 + MyBatis-Plus 3.5.6 + Sa-Token 1.38.0 + Thymeleaf,587 个 Java 文件)
审计方式:静态代码审计(仅读代码,未运行项目、未发起任何利用请求);关键论断均由主代理亲自复核代码行,子代理深挖 4 个方向(存储、认证、Web/SSRF、依赖)交叉验证
审计范围:core、module/storage|share|link|password|user|permission|sso|install|config|onlyoffice、resources、pom.xml、Flyway 迁移、git 历史安全修复
披露级别:完整披露(含硬编码凭据原文,供防御方核验轮换)


1. 总体结论

ZFile 是功能完整、架构清晰的个人/小团队网盘,管理端骨架(/admin/** 统一拦截、分享归属校验、安装重放防护、SSO state、OnlyOffice 回调加固、代理下载 AES 签名 #821、本地存储 .. 拦截)设计良好,近期已修复多个高危(见第 7 节)。但前端接口层存在两处系统性缺陷,构成最严重的组合:

  1. "匿名即 guest"的用户模型 + 授权切面存在 no-op 分支(@StoragePermissionCheck 只处理 LINK,SHARE_LINK/SHORT_LINK 直接放行),叠加"创建分享/生成短链无需登录",形成**未认证创建指向任意存储源的分享/短链 → 下载存储源内任意文件(含其他用户 rootPath 目录与私有存储)**的攻击链——本次审计最高风险项(§2.1/2.2)。
  2. 若干"管理工具/调试"接口落在 /admin/** 鉴权之外:S3 辅助接口直接 SSRF(§2.3)、debug 管理员重置后门(§4.8)等。

结论:不满足"私有/按用户隔离"部署的安全预期;公开部署(游客可见存储)下匿名者可读全库文件、可借爆破与弱哈希放大到管理面。生产上线前必须修复 P0/P1 项。

等级 数量 说明
🔴 严重 3 未认证任意文件下载(分享/短链双入口);S3Helper 未鉴权 SSRF;FTP/SFTP 存储路径穿越
🟠 高危 4 登录防爆破被 XFF 绕过(含验证码/2FA 缺陷);分享密码明文可爆破;OnlyOffice 回调 JWT alg 可控;直链绕过目录密码/根路径隔离
🟡 中危 13 上传无签名+依赖 NPE 拒绝;CORS 反射任意 Origin;Host/axios-from 头污染 URL 与开放重定向;axios-from SSRF;密码弱哈希(MD5);文件夹/分享密码明文+时序;日志明文密码;Sa-Token 永不过期+URL 传 token;debug 重置管理员后门;分享 key 32bit;用户名枚举;SSO 自动注册默认开;上传无限大小
🟢 低危 8 Knife4j 开关耦合 profile;站点标题注入;Local 符号链接;列目录先于规则校验;批量删除缓存键;签名 AES-ECB 可篡改;Docker root;SQLite 库/备份权限
合计 28 另见 §7 历史版本已修复漏洞(≤4.5.0 受影响,3 项高危)

最致命的组合链:匿名 POST /api/share/create {storageKey:"<任意>", sharePath:"/"} → 匿名 GET /api/share/download/{key}?path=/<文件> 下载该存储源全部文件(包括私有存储、其他用户目录);storageKey 可从任一公开分享的 /api/share/info/{key} 或下载 302 头获取。整条链无需登录、无签名、无权限校验。


2. 🔴 严重漏洞

2.1 未认证任意文件下载:匿名创建"根目录分享"绕过全部访问控制(CWE-284 / CWE-639)

位置(均已逐一验证):

  • module/share/controller/ShareLinkController.java:66-71(POST /api/share/create,无 @SaCheckLogin,仅有 @StoragePermissionCheck(SHARE_LINK))
  • module/storage/aspect/FileOperatorCheckAspect.java:53-61(关键缺陷:annotationCheck 只对 FileOperatorTypeEnum.LINK 执行 linkActionCheck,SHARE_LINK/SHORT_LINK 等其它 action 直接 point.proceed() —— 权限注解形同虚设)
  • module/share/service/ShareLinkService.java:241-268(createShareLink 仅 existByStorageKey 校验存储存在,不校验存储是否启用、当前用户是否对该存储有访问权、sharePath 合法性;userId 存当前用户=匿名 guest(2))
  • module/share/service/ShareLinkFileService.java:124-147(下载时 getDownloadUrl(concat(sharePath, filePath)),无文件存在性/前缀/权限校验,签名由服务端对任意拼接路径正常生成)
  • module/user/model/constant/UserConstant.java(ANONYMOUS_ID=2,匿名即 guest 用户,getCurrentUserBasePath() 返回 /,见 AbstractBaseFileService.java:89-106)
  • db/migration-sqlite/V28__add_multi_user.sql:24(guest 用户默认存在且 enable=true)

漏洞描述:分享功能的设计意图是"已登录用户分享自己目录下文件"。但 createShareLink 既不需要登录,其权限注解又是 no-op,且 service 层不校验存储启用/用户授权——匿名用户(guest,根路径 /)可对任何已知 storageKey 创建 sharePath="/" 的分享。访问该分享时 ShareAccessContext 以分享创建者(guest)身份执行,下载接口把 sharePath + filePath 直接交给 getDownloadUrl(签名服务端生成、必然有效)。

攻击链(无需任何账号):

1) GET  /api/share/info/{任意公开分享key}   → 匿名返回 storageKey(或从分享下载 302 Location 提取)
2) POST /api/share/create {"storageKey":"<key>","sharePath":"/","shareEntries":[],"shareType":"file","expireDate":null}
   → 返回 8 位 hex shareKey
3) GET  /api/share/download/{shareKey}?path=/<任意目录或文件名>
   → 302 → 带合法签名的下载地址 → 文件流返回

可下载:该存储源根目录下任何文件,包括:私有(未对游客开放)存储源内容、多用户部署下其他用户 rootPath 目录内的文件(rootPath 隔离失效)、被过滤规则/文件夹密码保护的内容(分享下载路径不查文件夹密码与禁下载规则)。本地存储仅受 checkPathSecurity 的 .. 拦截(绝对路径不受限),FTP/SFTP 完全无校验(叠加 §2.4 更可越出 basePath)。

修复:① createShareLink 增加 @SaCheckLogin 并在 service 内校验"当前用户对该 storageKey 有 AVAILABLE(可见)+SHARE_LINK(可分享)权限";② 修正 FileOperatorCheckAspect.annotationCheck 使 SHARE_LINK/SHORT_LINK 等 action 全部走权限校验(当前仅 LINK);③ getShareFileDownloadUrl/getShareFileList 对 filePath 归一化并强制限定在 sharePath 前缀内,且校验存储启用与文件存在;④ 下载路径命中文件夹密码/禁下载规则时应拒绝。

2.2 未认证任意文件下载(第二入口):匿名生成短链(CWE-284 / CWE-639)

位置:module/link/controller/ShortLinkController.java:56-88(POST /api/short-link/batch/generate 无登录校验、@StoragePermissionCheck(SHORT_LINK) 为 no-op;GET /s/{key} 匿名 302);module/link/service/LinkDownloadService.java:127-204(下载仅查存储启用 + 系统开关 + 下载目录过滤,不查用户 basePath/根路径隔离/文件夹密码);对照 action=LINK 的路径直链生成(FileOperatorCheckAspect.linkActionCheck:63-75)会做权限校验——两入口鉴权不一致,佐证 SHORT_LINK 确为漏校验分支。

漏洞描述与影响:与 2.1 同根因同影响,入口更简单:匿名对任意启用存储的任意路径生成短链,访问 /s/{key} 即下载。修复:同 2.1;generatorShortLink 加登录+权限校验;短链下载路径强制限定在生成者 basePath 内。

2.3 S3 辅助接口未鉴权 + 任意 endpoint → 回显型 SSRF(CWE-918)

位置:module/storage/controller/helper/S3HelperController.java:44-99(POST /s3/getBuckets)、:101-156(POST /s3/getCorsConfig)

// S3HelperController.getBucketNames —— endPoint 直接作为 AWS SDK endpointOverride
String endPoint = getS3BucketListRequest.getEndPoint();
if (!UrlUtils.hasScheme(endPoint)) { endPoint = "http://" + endPoint; }
...
S3Client s3Client = S3Client.builder()
    .region(Region.of(region))
    .endpointOverride(URI.create(endPoint))      // ← 攻击者任意主机
    .credentialsProvider(...)
    .build();
buckets = s3Client.listBuckets().buckets();      // ← 服务端向该主机发 S3 请求

漏洞描述:控制器映射在 /s3(不在 /admin/** 拦截范围内,Sa-Token 只保护 /admin/**),请求体 endPoint 被直接用作 AWS SDK 的 endpointOverride,无协议/主机/内网限制。服务端会向攻击者指定的任意主机发起 S3 API 请求,Bucket 列表/CORS 配置随响应回显,异常信息(SystemException 携带 e.getMessage(),见 GlobalExceptionHandler.java:359-363 兜底回显)也回显响应体。

攻击链(匿名):POST /s3/getBuckets {"accessKey":"x","secretKey":"x","endPoint":"http://169.254.169.254" 或内网任意 IP:端口,"region":"..."} → 服务端探测内网主机(对 S3 兼容服务如 MinIO/Ceph 可列出其 Bucket;对非 S3 服务从错误/超时差异判定存活与端口开放),形成未认证内网扫描与 S3 兼容服务枚举。

修复:① 该控制器移入 /admin/** 或加 @SaCheckLogin+checkRole("admin")(本属管理员配置存储的工具);② endPoint 白名单校验(仅 http/https、禁止内网/链路本地/元数据地址 169.254.169.254、禁止 DNS rebinding);③ 错误信息不携带原始异常 message 回显。

2.4 FTP/SFTP/WebDAV 存储缺失路径校验 → 路径穿越(CWE-22)

位置:module/storage/service/impl/FtpServiceImpl.java:74(fileList)、:223-237(uploadFile)等全部方法直接 concat(param.getBasePath(), getCurrentUserBasePath(), folderPath) 且无 ../名称校验;SftpServiceImpl.java:77 同;WebdavServiceImpl.java:47-56 同;对照:校验只存在于 LocalServiceImpl.checkPathSecurity/checkNameSecurity(LocalServiceImpl.java:298-333,2026-05 补全的修复仅覆盖 Local)。

漏洞描述:.. 校验只在 Local 一处。对 FTP/SFTP 存储,经 /api/storage/files、文件操作接口、分享/短链下载(2.1/2.2 的 filePath 亦不校验)、PUT /file/upload/{storageKey}/** 传入 ../..,远程 FTP/SFTP 客户端即越出 ZFile 配置的 basePath(也越出用户 rootPath)列目录/读写文件。受 FTP 账号 chroot 与权限限制,但 basePath 通常是账号可达区域的子目录,越权可达范围通常非空;且 rootPath 隔离(用户 A 只能看 /userA)可被 ../../userB/... 直接击穿。

攻击链:POST /api/storage/files {"storageKey":"<ftp存储key>","path":"../../.."} → 列出 basePath 之外目录。

修复:把 checkPathSecurity/checkNameSecurity 下沉到 AbstractProxyTransferService/AbstractBaseFileService 基类并对 FTP/SFTP/WebDAV 强制启用(覆盖列目录、删改传、分享下载、直链全部入口);或对拼接结果做 realpath 前缀校验。


3. 🟠 高危漏洞

3.1 登录/接口防爆破体系可被伪造 X-Forwarded-For 绕过(CWE-307)

位置:core/aspect/ApiLimitAspect.java:50-61(JakartaServletUtil.getClientIP(request)——hutool 默认优先信任 X-Forwarded-For/X-Real-IP 请求头);module/user/controller/UserController.java:96(@ApiLimit(timeout=60,maxCount=10) 为登录唯一防线);core/filter/SecurityFilter.java:50-51(IP 黑名单同源);默认安装无验证码、无 2FA(R__data.sql loginVerifyMode=’off’)。

漏洞描述:默认直连部署(Docker 直接暴露 8080)未配置 server.forward-headers-strategy 与可信代理链,攻击者每次请求伪造不同 X-Forwarded-For 头即绕过 10 次/60 秒限流与 IP 黑名单。叠加验证码缺陷(§3.1b)与 2FA 无独立限制(§3.1c),管理员口令可被在线爆破。

修复:① 限流/黑名单取 request.getRemoteAddr(),代理场景仅在可信代理层改写并配置 server.forward-headers-strategy;② 限流 key = 真实 IP + 用户名 + 方法,加账户级失败锁定与指数退避;③ 默认开启图形验证码或强制强口令策略。

3.1b 登录图形验证码"非一次性"且获取无限流(CWE-307)

位置:module/user/service/login/ImgVerifyCodeService.java:28(FIFO 100 条/60s)、:69-72(verifyCaptcha 仅 get(uuid) 不删除,60s 内同一 uuid+code 可无限复用)、:36-55(获取验证码无速率限制)。4 位 CircleCaptcha 易 OCR。修复:校验成功后 remove(uuid) 一次性消费;按 IP 限制获取频率。

3.1c 管理员 2FA 无独立尝试次数限制(CWE-307)

位置:module/user/service/login/TwoFactorAuthLoginVerifyService.java:87-91(无失败计数/锁定);且 ImgCodeLoginVerifyService.java:38-42 在"管理员+开启 2FA"时跳过图形验证码 → 2FA 成为唯一防线,可伪造 XFF 后无限试错(每 90s 窗口约 3 个有效码)。修复:5 次失败锁定账号 15 分钟;并入账户级限流。

3.2 分享密码明文存储 + 校验无限流 + 非恒定时间(可在线爆破)(CWE-521/307/208)

位置:module/share/service/ShareLinkService.java:247-263(密码明文入库)、:289-298(Objects.equals 比较);module/share/controller/ShareLinkController.java:84-88(/api/share/verify 无任何限流注解)、:149-173(download);/api/share/files 同。

漏洞描述:分享密码以明文存库;verify/files/download 无速率限制,可无限次尝试;短数字/字母密码可在秒~分钟内在线爆破;verify 接口对"key 不存在"抛异常、对"密码错误"返回 false,可探测有效 key(叠加 2.1 后攻击者可对自己创建的分享设任意弱密码或无密码,主要风险在他人设密码的分享被爆破)。

修复:分享密码用慢哈希(BCrypt)存储、MessageDigest.isEqual 恒定时间比较;verify/files/download 加 IP+shareKey 限流与失败上限;key 不存在与密码错误返回一致响应。

3.3 OnlyOffice 回调 JWT 验签算法取自 token 头(alg 可控)(CWE-347)

位置:module/onlyoffice/controller/OnlyOfficeController.java:152(JWTUtil.verify(token, bytes(secret)))+ hutool 5.8.31 JWT/JWTSignerUtil 实现(子代理经 hutool 源码 jar 核实:验签时按 token 自身 header 的 alg 字段选择签名器,alg=none 走 NoneJWTSigner 恒真)。

漏洞描述:若 hutool 该行为属实(建议升级后复测),攻击者构造 {"alg":"none"} 的空签名 JWT 即可通过 JWTUtil.verify,无需知道 OnlyOffice Secret。缓解因素:未配置 secret 时回调被拒(OnlyOfficeController.java:141-147);通过验签后还需命中 OnlyOfficeKeyCacheUtils 中有效的预览 key(攻击者可先对自己能预览的文件发起 OnlyOffice 预览获取 key);写回前会以缓存用户身份校验 UPLOAD 权限(604cae5 修复),下载 URL 有同源白名单。影响:JWT 防线可被绕过,剩余防线为 URL 白名单与缓存 key——仍可伪造回调状态(如强制保存他人正在编辑的文档、触发对同源 URL 的下载),建议修复。

修复:① 升级 hutool(≥5.8.40 亦可顺带修 CVE-2025-56769);② 验签前显式校验 alg 为 HS256 且使用固定算法接口,拒绝 none/RS*;③ 自行实现 HMAC-SHA256 验签不依赖 token 头选算法。

3.4 直链/分享下载绕过"文件夹密码"与下载过滤(CWE-284/306)

位置:module/link/service/LinkDownloadService.java:66-73(handlerDirectLink 仅查系统 showPathLink 开关)、:127-204(handlerDownloadGetUrl 只查存储启用 + checkFileIsDisableDownload 过滤规则,不查文件夹密码);module/share/service/ShareLinkFileService.java:124-147 同;对照:文件夹密码仅在 CheckPasswordAspect(module/storage/aspect/CheckPasswordAspect.java:60-93)覆盖的 /api/storage/file/item、/api/file/operator/* 接口生效。

漏洞描述:知道确切路径时,GET /directlink/{storageKey}/受保护目录/文件 或分享下载可直接取走文件夹密码保护目录内的文件;RefererCheck 在默认 refererType='off'(R__data.sql)时不生效。修复:handlerDownloadGetUrl/getShareFileDownloadUrl 内复用 PasswordConfigService.verifyPassword 校验目标目录(无密码参数即拒绝),或要求凭证。


4. 🟡 中危漏洞

4.1 代理上传无签名、匿名防护依赖切面 NPE 副作用(CWE-284)

位置:module/storage/controller/proxy/ProxyUploadController.java:26-46(PUT /file/upload/{storageKey}/** 无登录/签名注解);service/base/AbstractProxyTransferService.java:86-113(getProxyUploadUrl 不携带签名——对照下载 URL 已强制 AES 签名 #821);module/storage/aspect/FileOperatorCheckAspect.java:139-162(uploadAround 覆盖 uploadFile/getUploadUrl,走 checkPermission(UPLOAD))。

漏洞描述:服务端确有 FileOperatorCheckAspect.uploadAround 做 UPLOAD 权限校验(修正:并非"完全无校验"),但:① 匿名/无授权用户因 user_storage_source 记录为空会在 allowAccess(:257-263,userStorageSource.getEnable() 空指针)触发 NPE→500 被"碰巧"拒绝——拒绝依赖异常副作用而非设计;② guest 一旦被授予任意存储(公开上传场景的常见配置)即按权限放行;③ 上传 URL 无签名:getUploadUrl 签发的 PUT 地址本身无有效期/绑定,泄露或截获即被长期复用(对比下载 URL 签名);④ 上传大小无上限(见 4.13),可任意同名覆盖(对象覆盖攻击)。

修复:上传 URL 同下载侧加签名(绑定 storageKey+路径+过期);allowAccess 对 null 记录显式返回 false 而非 NPE;上传大小限制。

4.2 CORS 反射任意 Origin(CWE-942)

位置:core/filter/CorsFilter.java:41(Access-Control-Allow-Origin 原样回显请求 Origin;Allow-Credentials=false)。全站生效。

影响:任何恶意站点可跨域读取响应(公开存储列表、分享信息等);对未认证状态变更接口(分享创建、S3 SSRF 探测)可直接从浏览器跨站驱动。凭证模式 false + Sa-Token 走自定义头,Cookie/凭证窃取受限,属纵深防御缺失。修复:改为固定白名单或仅开发环境开启。

4.3 Host / X-Forwarded-* / axios-from 头参与 URL 决策 → 开放重定向与 URL 污染(CWE-601/116)

位置:core/util/RequestHolder.java:152-181(getRequestServerAddress 取 X-Forwarded-Host→Host 头拼 URL)、:222-228;module/config/service/SystemConfigService.java:221-244(getAxiosFromDomainOrSetting 依次取 配置域名 → axios-from 请求头 → getRequestServerAddress);该"域名"用于生成代理下载/上传 URL(AbstractProxyTransferService.java:88,114)、分享完整 URL(ShareLinkService)、OnlyOffice callbackUrl(OnlyOfficeConfigService.java:77)、SSO redirect_uri(SsoService.java:118,149)、直链跳转(DirectLinkController.java:97)与错误页跳转(GlobalExceptionHandler.java:62-130 的 sendRedirect)。

漏洞描述:直连部署(或反代未剥离这些头)时,攻击者构造 Host: evil.com 或 axios-from: https://evil.com 可:① 使 401/403/404 等错误重定向与登录后跳转指向攻击者域名(开放重定向/钓鱼);② 污染服务端生成的分享/直链/SSO redirect_uri 域名(SSO 授权回调被劫持到攻击者域,若 IdP 校验 redirect_uri 不严可造成授权码泄露);③ 组合 §4.4 形成 SSRF。修复:URL 决策只信任配置域名与可信代理头;非 axios 错误页不回跳或跳固定地址。

4.4 axios-from 头可劫持 readme/m3u8 服务端拉取域名 → SSRF(CWE-918,受限回显)

位置:module/readme/service/ReadmeConfigService.java:158-166(兼容模式读取目录下 readme.md:HttpUtil.getTextContent(fileItem.getUrl()));module/link/service/LinkDownloadService.java:182-196(m3u8 内容);core/util/HttpUtil.java:29-44;URL 域名来源见 4.3(getAxiosFromDomainOrSetting 含 axios-from 头)。

漏洞描述:fileItem.getUrl() 是服务端按"配置/请求头域名"生成的下载 URL——匿名请求携带 axios-from: http://<内网地址> 时,生成的 readme 拉取 URL 指向攻击者指定主机,服务端随后 GET 该 URL 并把内容当 readme 文本回显(盲/回显型 SSRF)。约束:需该存储开启 compatibilityReadme 且目录存在 readme.md(或 .m3u8 直链场景)。修复:URL 生成不信任请求头;服务端回源固定走本机代理签名 URL;回源地址禁止内网段。

4.5 用户密码弱哈希:MD5(SHA1(pwd)+时间戳盐),无盐回退纯 MD5(CWE-916)

位置:module/user/utils/PasswordVerifyUtils.java:14-30(新格式 md5(sha1(p)+salt);旧格式无盐 md5(p);salt = md5(sha1(毫秒时间戳)) 可预测);module/user/service/UserService.java:333(另一处纯 MD5);库表 user.password varchar(32)。

影响:数据库(SQLite 单文件,见 4.12)一旦泄露,MD5 系哈希可被 GPU 秒级~分钟级离线爆破(弱口令秒破);无盐记录可查彩虹表。修复:BCrypt/Argon2id 加盐迭代,SecureRandom 盐;登录成功后惰性升级存量哈希。

4.6 文件夹密码明文存储 + 非恒定时间比较(CWE-256/208)

位置:module/password/model/entity/PasswordConfig.java:40-42(明文 password 列);module/password/service/PasswordConfigService.java(matchPassword 用 Objects.equals 逐字符短路)。修复:加盐哈希存储;MessageDigest.isEqual 比较。

4.7 日志记录文件夹/分享密码明文(CWE-532)

位置:module/storage/service/StorageSourceService.java:410(INFO:password: {} 记录用户提交的密码);module/password/service/PasswordConfigService.java:81,173,189,197,204(DEBUG:预期密码: {} 记录真实明文密码);resources/logback-spring.xml:154(im.zhaojun.zfile 包无条件 DEBUG,debug 日志滚动保留 3 天/1GB);另 zfile.debug=false(application.properties:4)经查无 Java 消费点,是无效开关——DEBUG 由 logback 无条件开启,二者脱节。修复:日志脱敏/删密码参数;logback 包级级别收紧并让 zfile.debug 真正生效。

4.8 debug 模式"重置管理员密码"后门无鉴权(CWE-306,开启即高危)

位置:module/user/controller/UserController.java:182-195(PUT /user/resetAdminPassword 无 @SaCheckLogin,仅判 zFileProperties.isDebug();成功后 systemConfigService.resetLoginVerifyMode() 清空 2FA);module/user/service/UserService.java:330-337;module/config/controller/SiteController.java:60(/api/site/config/global 匿名返回 debugMode 可探测)。修复:该接口必须要求已登录管理员身份;非 debug 时返回 404;不公开返回 debugMode。

4.9 会话生命周期:token 默认 30 天无滑动刷新、SSO 登录经 URL 传 token、Cookie 无 Secure(CWE-613/598)

位置:application-default.properties:46-47(仅 token-name,未配置 sa-token.timeout/active-timeout → Sa-Token 默认 30 天);module/sso/service/SsoService.java:216-231(登录成功 redirectUrl = frontDomain + "/sso?token=" + StpUtil.getTokenValue()——token 进 URL,落入浏览器历史/代理与访问日志/Referer);默认 Cookie 无 Secure。修复:配置 sa-token.timeout(如 2h)+active-timeout;SSO 改一次性 code 换 token 或服务端会话注入;Cookie Secure/HttpOnly/SameSite。

4.10 分享 key 仅 2^32 + info 接口匿名无限速(CWE-330/200)

位置:module/share/service/ShareLinkService.java:560-566(UUID.substring(0,8) 仅 32 bit);:475-481(/api/share/info/{shareKey} 匿名返回 storageKey 与分享者绝对路径,无速率限制)。8 位 hex 空间 42.9 亿,配合无失败限流可规模化枚举;命中即代表可访问该分享全部内容(有密码的叠加 3.2 爆破)。修复:key ≥128 bit;info 限流且不返回绝对路径/storageKey(仅创建者可见)。

4.11 用户名/管理员枚举与安全机制状态探测(CWE-200)

位置:module/user/controller/UserController.java:120-141(/user/login/verify-mode?username=X 匿名返回该用户的验证模式:OFF/IMG/TWO_FACTOR——可判定 X 是否为管理员、是否开 2FA/验证码,直接指导爆破策略)。修复:未登录统一响应。

4.12 SQLite 库与自动备份集中存敏感数据、默认权限宽松(CWE-921/732)

位置:application.properties:6-7(${user.home}/.zfile-v4/db/zfile);core/config/datasource/DataSourceBeanPostProcessor.java:72,85-96(升级自动整库复制备份,无清理策略)。库内含:MD5 密码哈希、明文文件夹密码/分享密码、存储源 API 密钥、WebDAV 密码、TOTP base32 种子(system_config)。修复:目录 0700、备份轮换、字段级加密存储。

4.13 上传体积无上限(CWE-400)

位置:application-default.properties:50-51(max-request-size=-1、max-file-size=-1)。配合 4.1/2.4 可灌满磁盘。修复:设置合理上限并在 Nginx 层限制。


5. 🟢 低危漏洞

5.1 依赖 CVE 与版本卫生

  • 5.1a 内嵌 Tomcat 10.1.26 / Spring Framework 6.1.11 / Logback 1.5.6(Boot 3.3.2):CVE-2025-24813(Tomcat≤10.1.34,CVSS 9.8,需 default servlet 可写+partial PUT,ZFile 默认不满足,不可直接利用)、CVE-2025-41234(Spring≤6.1.20 RFD——ZFile 4 处 ContentDisposition.filename(UTF_8) 且文件名用户可控:FileResponseUtil.java:50、RequestHolder.java:85、LocalServiceImpl.java:255、LinkDownloadService.java:187)、CVE-2024-12798(logback≤1.5.12)。→ 升级 Boot 至 ≥3.3.5/3.4.x 一并覆盖。
  • 5.1b 版本回退覆盖:pom.xml:31-32 的 jackson-bom.version=2.14.1(把 Boot 托管的 2.17.2 降回 2022 年的 2.14.1,丢失后续安全修复)与 snakeyaml.version=2.0(覆盖 Boot 的 2.2)——删除这两处覆盖。
  • 5.1c hutool-all 5.8.31:CVE-2025-56769(QLExpress RCE,<5.8.40;QLExpress 不在 classpath、未调用,利用受限)→ 升 ≥5.8.40(顺带修 3.3 的 JWT 问题面)。
  • 5.1d bcprov-jdk15on 1.70:CVE-2024-29857/30172/30171、CVE-2023-33201(未直接 import,类路径风险)→ 迁 bcprov-jdk18on ≥1.78 或移除。
  • 5.1e sa-token 1.38.0:CVE-2025-15117(JDK 序列化弱点;ZFile 用内存/Jackson DAO 缓解)→ 升 ≥1.45.0。
  • 已逐一核查无公开 CVE(例行升级即可):okhttp 4.12.0、commons-fileupload 1.6.0、poi-ooxml 5.4.0、commons-compress 1.26.2、sqlite-jdbc 3.46.0.1、flyway 10.12.0、jsch 0.2.20、guava 33.3.0、org.json 20231013、qiniu/upyun/sardine SDK、awssdk 2.24.3、oshi/totp/dns-cache-manipulator;CVE-2024-38816/38819(需 RouterFunctions,不适用)、CVE-2026-22733/40976(需 Actuator,ZFile 未引入——正面项)、CVE-2026-16723(fastjson 1.x,ZFile 用 fastjson2,不适用但建议 2.0.29→最新)。

5.2 Knife4j/Swagger 文档开关与 profile 耦合(CWE-200)

application-default.properties knife4j.enable=true,仅 application-prod.properties 关闭;默认 profile=prod 开箱安全,但 SPRING_PROFILES_ACTIVE 被覆盖即暴露 /doc.html 与 /v3/api-docs(含 /admin/** 管理员接口定义)。→ 独立硬开关默认关闭。

5.3 站点标题/favicon 无转义注入首页 HTML(CWE-80,仅管理员可控)

core/controller/FrontIndexController.java:73-85(replace 直拼 siteName/faviconUrl)。低危(需管理员写配置),建议转义。

5.4 Local 存储根目录内符号链接逃逸(CWE-59)

LocalServiceImpl.java:70,238:仅字符串级 .. 校验,无 realpath 包含校验;根目录内预置符号链接可读根外文件。→ getCanonicalPath 前缀校验。

5.5 文件列表先执行后校验

FileController.java:83,92:fileService.fileList() 先于责任链(禁访/隐藏/密码规则)执行——远程列举已发生(FTP 等有副作用),数据不返回。建议校验前移。

5.6 批量删除密码缓存键缺陷

FileOperatorController.java:73-103:pathCheckCache 仅以 path 为键,同 path 多条目复用首个校验结果。

5.7 代理下载签名使用 AES-ECB,无完整性保护(CWE-327)

ProxyDownloadUrlUtils.java:73-90:new SymmetricCrypto(AES, key)(hutool 默认 ECB/PKCS5),明文 storageId:path:expiry 确定性加密、无 IV/无 MAC。攻击者持合法签名(文件列表即返回)可对中间密文块位翻转改写 path/storageId(保留尾部 expiry 块不破坏填充),伪造任意等长路径的有效签名;对无路径校验的 FTP/SFTP(2.4)可越权下载,多用户场景可跨用户(#821 隔离被部分击穿)。→ 改 HMAC-SHA256 或 AES-GCM。

5.8 Docker 以 root 运行、基础镜像未锁定(CWE-250/1357)

Dockerfile:11,13,26:无 USER、WORKDIR /root、可变 tag open-21-jre-jammy。→ 非 root + 锁 digest。

5.9 其它低危/信息

  • 登录限流/分享限流/短链限流均为单机内存态(hutool TimedCache,重启失效,多实例各自计数)——LinkRateLimiterAspect.java:47-58 同受 XFF 问题影响(见 3.1)。
  • WebMvcConfig.java:28-29 放宽 URL 特殊字符(配合路径校验需留意)。
  • 默认创建 guest 匿名用户(enable=true)放大匿名攻击面(2.1 的前提之一)。

6. 审计确认无问题的项(正面清单)

  • ✅ /admin/** 由 SaTokenConfigure.java:27-32 统一拦截(checkLogin+admin 角色),覆盖 UserManager/2FA/存储源/密码/权限/分享管理/短链管理/SSO 管理/readme/filter/日志等全部管理入口;/admin 登录页已排除。
  • ✅ 分享删除有归属校验(仅创建者或 admin);分享列表按 userId 过滤(ShareLinkService.java:130-135,337)。
  • ✅ 安装重放防护:InstallService.install 校验 installed 标志(InstallService.java:26-41)。
  • ✅ SSO 回调校验 state(SsoController.java:29-49),OAuth2 code 换 token 带 client_secret。
  • ✅ 代理下载强制签名校验 storageId+路径绑定(#821,main 分支);OnlyOffice 下载 URL 同源白名单 + 重定向逐跳校验 + 超时限制(604cae5 修复,main 分支)。
  • ✅ 本地存储 .. 校验(main 分支)阻断经典路径穿越(LocalServiceImpl.checkPathSecurity);Tomcat 亦归一化 URL 中 ..。
  • ✅ 无命令执行/反序列化使用面;MyBatis mapper 全参数化、无 ${};SQL 日志关闭(NoLoggingImpl);Flyway clean-disabled=true。
  • ✅ /api/site/config/storage 返回体不含存储源凭据;JSON 解析全显式类型绑定;迁移脚本无硬编码初始管理员口令;TOTP 参数达标(32 字节密钥、SHA1/30s/6 位);无 Actuator 暴露。
  • ✅ OnlyOffice 分享预览场景禁止写回;isCallbackTokenKeyMatched 校验 key 绑定(main 分支)。

7. 历史版本已修复漏洞(部署 4.5.0 及更早版本必读)

main 分支(2026-05,4.5.0..HEAD 共 13 提交)相对 4.5.0 发布版修复三处高危(git 证实,修复提交均 2026-05-14):

修复提交 漏洞 受影响版本 说明
6afb604(#821) 代理下载无签名 → 路径隔离绕过 ≤4.5.0 /pd/{storageKey}/任意路径 越权下载;现强制签名
604cae5 OnlyOffice 回调 SSRF + 权限绕过 ≤4.5.0 伪造 callback、任意 URL 拉取(SSRF);写回前未校验 UPLOAD 权限
f44074a 本地存储路径穿越(.. 校验不完整) ≤4.5.0 test/..、..\foo 等形态绕过;现按段拆分校验

⚠️ 若部署 4.5.0 或更早发布版,应立即升级至含上述修复的版本(注意:本报告 2.x/3.x 其余问题在 main 分支仍存在,升级不等同于免疫)。

8. 修复优先级建议

  1. P0(上线前必须):2.1/2.2 分享与短链补登录+权限校验并修正 FileOperatorCheckAspect.annotationCheck(全 action 生效);2.3 S3Helper 移入 admin 鉴权 + endPoint 白名单;2.4 路径校验下沉基类。
  2. P1(尽快):3.1 限流取真实 IP + 账户级锁定 + 验证码一次性 + 2FA 独立锁定;3.2 分享密码哈希与限流;3.3 JWT 固定算法;3.4 下载路径校验文件夹密码。
  3. P2(加固):4.1 上传签名;4.3/4.4 头污染与 SSRF;4.5/4.6 哈希迁移;4.7 日志脱敏;4.8 后门收敛;5.1 依赖升级(Boot 3.3.5+/3.4.x、删除版本覆盖、hutool≥5.8.40);5.7 签名改 HMAC。
  4. P3(纵深):5.2-5.9;CORS 白名单;Docker 非 root;SQLite 目录权限;分享 key 熵。

本报告由主代理与 4 个并行子代理(存储、认证、Web/SSRF、依赖 CVE)交叉审计完成;关键结论均经主代理二次代码复核。静态审计未动态验证,行号以审计时 main 分支为准。

正文完
 0
评论(没有评论)