FreeScout:我们报告的 Polycast 漏洞
GHSA-vcgj-6qfr-7qr6:一个已登录的 FreeScout 用户可以撑大查看记录缓存,拖慢所有人的帮助台。我们发现了什么,以及为什么应升级到 1.8.248。
在 FreeScout 中打开一个工单时,如果有同事正在查看同一个会话,页面顶部会显示他的名字。这是个小而实用的功能:它能避免两个人在同一时间回复同一位客户。为了实现这一点,每个客服的浏览器会定期向服务器发送一个请求,大意是“我正在查看编号为 X 的会话”。服务器既不检查这个会话是否存在,也不检查用户是否有权查看它,只是照单全收并保存下来。
这就是安全公告 GHSA-vcgj-6qfr-7qr6 所描述的缺陷,该公告于 2026 年 10 月 17 日发布,问题已在 FreeScout 1.8.248 中修复。这个漏洞是我们报告的:发现者是 Webcracy 安全研究小组的 Liam Giannini(GitHub 上的 Liam-G)。感谢 FreeScout 的维护者,他们在下一个版本中就发布了修复。
“正在查看”提示背后发生了什么
FreeScout 是最受欢迎、使用最广泛的开源帮助台之一:自 2018 年开始开发,在 GitHub 上有超过 4,600 颗星,被大量企业用作 Help Scout 或 Zendesk 等服务的自托管替代方案。它用 PHP 编写,基于 Laravel。对于一个如此普及的项目,一个漏洞从来不只是少数人的问题。实时更新通过一个名为 Polycast 的组件完成,它在 POST /polycast/receive 端点上接收浏览器的周期性请求。
每个请求都会进入一个方法 processConvView(),它读取 data[conversation_id] 字段,并原样使用。不检查它是不是数字,不限制长度,也不校验权限。只有在某些情况下才会去数据库查找这个会话,而且即使查找失败,这个值也不会被丢弃。对于每个新的标识符,服务器都会创建一个专用的缓存条目,并把同一个标识符添加到一个共享数组 conv_view 中,这个数组保存着整个安装实例中所有活跃的查看记录。
让一个缺失的检查演变成可用性问题的,正是这个数组。每次请求都会把它完整读出、修改,再完整写回,有效期为二十分钟。如果有人往里面塞进成千上万个捏造的标识符,之后的每一次写入都会变得更重,其他客服的正常请求也一样,因为它们使用的是同一个对象。更糟的是,还有一个每分钟运行一次的计划命令 freescout:check-conv-viewers:它会遍历所有条目,对每个过期条目从缓存中删除一个键,并触发一个实时事件。虚假条目越多,数据库和广播系统的工作量就越大。此外,该端点也没有专门的频率限制。
结果是:单个客服账号,即使没有管理员权限,也能拖慢所有人的帮助台。请求变慢、worker 被占满、Redis 或所配置的缓存后端承压、计划任务延迟。这个问题不涉及访问他人数据,也不涉及篡改内容。GitHub 将其评为中等严重程度,CVSS 3.1 6.5(AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H),归入 CWE-400,即不受控制的资源消耗。
为什么“需要登录”还不够
一句“攻击者得有账号”就把这件事打发掉,是很容易的。但在帮助台里,账号很多,而且经常变动:季节性客服、外部协作者、被开放共享邮箱的供应商、在其他被攻破的服务中重复使用的凭据。Polycast 的访问控制只检查用户是否已登录、是否可以监听所请求的频道,对请求的内容却什么也没管。正确的边界不是“用户已通过认证”,而是“用户可以查看这个具体的会话”。
同一个端点上还有先例。7 月发布的 GHSA-gh3r-jh6q-wrvj(CVE-2026-62856)是一个高危漏洞,已在 1.8.226 中修复,它利用频道选择中 LIKE 查询的通配符来读取其他用户的通知。我们的情况不同:存在漏洞的字段是 data[conversation_id],影响的是资源,而不是机密性。但同一个入口在短短几个月内产生了两份安全公告,这本身就说明了一些问题。轮询端点,也就是浏览器每隔几秒就调用一次、却没人会去留意的那些端点,往往比用户看得见的界面得到更少的关注。
我们如何验证
这个问题是在审查 FreeScout 1.8.247(commit 9d11ff0)源代码时发现的,我们沿着数据的路径,从端点一直追到清理命令。在报告中,我们描述了如何在实验环境中复现:以普通客服身份登录,发送几个带有不存在标识符的请求,观察缓存以及 conv_view 对象的增长,然后让计划命令运行。几十个条目就足以看到这个现象:要证明一个帮助台可能被拖垮,并不需要真的把它拖垮。
1.8.248 中的修复
1.8.248 版本的发布说明提到了对 processConvView() 中标识符的清理。在修复后的代码中,只有当这个值是不超过十位的正整数时才会被接受,用户必须已通过认证,而且在写入缓存之前会先确认该用户确实可以查看这个会话。带有虚假标识符、或指向无权访问的会话的请求,不会再留下任何痕迹。
在报告中,我们还提出了其他措施:为 Polycast 端点设置专门的频率限制,为每个用户和每个会话的活跃查看数设置上限,最重要的是用按会话划分的结构取代全局数组,这样就不必在每次请求时重写全部内容。输入校验加上权限检查,已经堵住了安全公告中描述的攻击路径。其余措施属于纵深防御,对防范未来的滥用有用,是否采用仍是维护者的设计选择。
同一版本还修复了另外三份安全公告:GHSA-w3jr-fphw-whgv,涉及本应只能看到分配给自己的会话的用户却能看到之前的会话;GHSA-c8rg-2r2h-5vgc,涉及并行兑换同一邀请从而创建多个会话;以及 GHSA-cxf4-7mrp-vvpr,依赖库 league/flysystem 中的控制字符问题。光凭这一点,就不该推迟升级。
如果你在使用 FreeScout,该怎么做
如果你的安装版本早于 1.8.248,请升级。安全公告没有给出临时缓解方案,而禁用 Polycast 就意味着失去界面所依赖的实时更新。升级前,最好备份数据库和模块目录:FreeScout 升级后出现的很多问题,更多来自未同步更新的第三方模块,而不是核心本身。
如果你发现帮助台出现了无法解释的变慢,也值得看一下缓存中 conv_view 条目的大小。如果其中的条目数量远远多于客服实际打开的会话数,就是一个值得深究的信号。无论如何,升级之后,现有条目会在二十分钟内自行过期。
如果 FreeScout 运行在一台好几个月都没人检查过的服务器上,那么问题很可能不止这一份安全公告。帮助台里存放着客户邮件、附件,有时还有敏感的个人数据:它理应得到与公司网站同等的维护,甚至更多。
我们为什么研究开源软件
我们的许多客户使用自托管的开源工具,我们也经常推荐它们:没有按用户收取的费用,数据在自己的服务器上,代码可以阅读。但“可以阅读”并不意味着有人在读。花一部分时间审查我们所部署的项目,并以负责任的方式把发现报告给维护者,是我们所知道的回馈这些项目最实际的方式。也是真正了解我们投入生产的是什么的方式。
如果你希望对你的帮助台、业务管理软件或定制开发的应用做类似的检查,我们很乐意聊聊。我们的渗透测试工作正是从这里开始:阅读代码和配置,弄清楚哪里的数据没有经过检查,并准确地告诉你我们验证了什么、没有验证什么。