由于一个 GET 集合端点(collection endpoint)缺少安全检查,任何拥有基础 CoopCycle 账号的用户都可以获取共享实例中所有商店的完整地址簿,从而暴露无数客户的姓名、街道地址和邮政编码。该漏洞已在两天内得到修复,并敦促用户升级到最新发布的版本。

漏洞是如何发生的

CoopCycle —— 一款由食品配送合作社使用的开源物流平台 —— 使用 PHP 框架 API Platform 来定义其 API。在该框架中,每个操作(POST、GET 等)都必须配对一个安全表达式;如果省略了该表达式,框架就会在没有任何授权检查的情况下运行代码。

开发人员为创建或更新商店地址列表的 POST 请求设置了标准的表达式 is_granted('edit', object)。这之所以有效,是因为该请求针对的是单个商店实体,为框架提供了一个具体的“对象”来进行评估。

而读取相同资源的 GET 请求针对的是一个集合:/api/stores/{id}/addresses。集合没有单一的对象,因此无法应用相同的 is_granted('edit', object) 表达式。由于开发人员漏掉了安全检查行,框架会将地址数据提供给任何已认证的用户,而无需考虑租户隔离。

在共享的 CoopCycle 实例上,恶意用户只需遍历商店 ID,向该端点发送 GET 请求,即可抓取系统中存储的所有客户的家庭地址。除了普通账号外,不需要任何额外的权限。

为什么漏洞未能被发现

这个问题并非简单的疏忽。API Platform 的声明式安全模型缺乏一种直观的方式来表达“用户必须与集合中的每个对象属于同一个租户”。那行缺失的代码,恰好出现在框架使授权变得繁琐的地方。

更严重的是,该项目的测试套件实际上断言“包含所有地址的 GET 响应”是符合预期的行为。换句话说,自动化测试之所以通过,是因为测试中使用的测试数据(fixtures)允许跨租户访问,从而有效地掩盖了漏洞。在这种情况下,绿色的测试套件反而给人一种安全假象。

谁是赢家,谁是输家

  • 客户:他们的个人身份信息 (PII) —— 包括全名和家庭地址 —— 暴露给了平台上的任何人。尽管数据并未公开发布,但此次泄露损害了多个合作社的隐私。
  • 使用 CoopCycle 的合作社:对平台保护租户数据能力的信任受到了动摇。任何尚未升级的合作社都面临着持续暴露的风险。
  • CoopCycle 维护者:他们的快速响应 —— 在两天内发布补丁并增加了回归测试 —— 缩短了漏洞被利用的时间窗口,并展示了负责任的开源治理。然而,此次事件也凸显了加强安全审查流程的必要性,尤其是在涉及框架驱动的默认设置时。

开发人员和审计人员应注意什么

  • 操作不对称性:如果某个路径上的 POST(或任何变更操作)受到保护,而相应的 GET 请求却是开放的,这种差异就是一个警示信号。POST 请求体现了开发人员保护资源的意图。
  • 集合端点:任何返回列表而非单个项目的端点,往往会超出常规的安全模式。请务必验证是否为批量读取显式添加了授权检查。
  • 测试套件的真实性:确保测试数据能够反映真实的租户边界。一个验证了跨租户数据泄露且通过了的测试,是一个警告信号,而不是通行证。

修复方案及后续步骤

在漏洞报告后,CoopCycle 核心团队为 GET 集合操作添加了缺失的安全表达式,并引入了回归测试,以强制执行针对单个项目和集合端点的租户隔离。该补丁已在随后的软件版本中发布。

CoopCycle 用户应当:

  1. 确认正在运行最新版本的软件。
  2. 检查任何可能引入类似集合级漏洞的自定义扩展或插件。
  3. 重新运行安全扫描,重点关注所有 API 路由是否存在读写不对称性。

核心启示

使安全声明式的框架,如果开发者依赖于仅适用于单个对象的模式,可能会掩盖危险的安全漏洞。一个简单的检查——端点的读取端是否拥有与写入端相同的保护机制?——就能揭示一类跨租户数据泄漏问题,否则这些问题会被掩盖在通过测试的测试套件之下。