Redis Lua scripting: several security vulnerabilities fixed

Salvatore Sanfilippo

Redis Lua 脚本:多个安全漏洞已修复

原文由 Salvatore Sanfilippo 发布,订阅该博客

一个多月前,我收到了来自 Apple 信息安全团队的一封邮件。在一次审计中,Apple 团队在 Redis Lua 子系统中发现了一个安全问题,具体是在 cmsgpack 库中。这个库并非 Lua 本身的一部分,而是我自己编写的 MessagePack 实现。在合并一个用于增强功能的 pull request 的过程中,一个安全问题被引入了。随后,同一团队又在 Lua struct 库中发现了一个新问题,同样,这个库也不是我们所使用的 Lua 版本自带的:我们只是将它的源码嵌入到了自己的 Lua 实现中,以便为提供给 Redis 用户的 Lua 解释器增加一些功能。之后,我在同一个 struct 包中又发现了一个问题,再后来,阿里巴巴团队在 cmsgpack 以及其他使用 Lua API 的代码路径中发现了更多问题。短短时间内,我面前就堆积了一大堆与 Lua 相关的漏洞。

这些漏洞主要与在云上提供托管 Redis 服务这一特定场景相关,因为在没有直接访问 Redis 服务器的情况下,这些已发现的漏洞几乎不可能被利用:许多 Redis 用户根本不会使用 cmsgpack 或 struct 包,而即使使用,也极少会向它们传入不可信的输入。但对于云服务提供商来说情况就不同了:他们向订阅服务的用户暴露 Redis 实例,有时还是多租户架构。用户可以向这些 Redis 实例发送任意内容,从而触发漏洞、破坏内存、攻陷 Redis 进程,甚至完全接管 Redis 进程。

例如,这个简单的 Python 程序就能利用其中一个 cmsgpack 漏洞使 Redis 崩溃 [1]。

[1] https://gist.github.com/antirez/82445fcbea6d9b19f97014cc6cc79f8a

不过,对于能够控制发送到自身实例内容的普通 Redis 用户而言,风险仅限于在格式参数中选择了特别危险的解码格式“bc0”后,向 struct.unpack() 这类函数传入不可信数据的情况。

协调安全公告的发布

得益于 Apple 信息安全团队、我本人以及各 Redis 云服务提供商之间的合作与友好沟通,我在联系了市面上所有主要的 Redis 服务商后,尝试协调了此次漏洞的发布时间,以便他们能在漏洞公开前完成系统修补。我提供了一个统一的补丁,方便各服务商直接应用到自己的系统中。最后,在昨天到今天之间,我准备好了 Redis 3、4 和 5 的新补丁版本,其中已包含这些安全修复。如果你正在阅读这篇博文,说明它们都已发布。遗憾的是,我没能联系到一些规模较小或新成立的云服务商。仅是处理与 Redis Labs、Amazon、Alibaba、Microsoft、Google、Heroku、Open Redis 和 Redis Green 等公司的沟通,就已经耗费了巨大的精力,而如果将信息共享范围扩大到更多对象,泄露的风险也会更高(每家公司都有许多人参与处理流程)。如果你是一家今天才得知此漏洞的 Redis 服务商,对此我深表歉意,我已尽了最大努力。

我想感谢 Apple 信息安全团队以及所有其他服务商就此问题提供的提示和帮助。

Lua 带来的问题

坦白说,在设计 Redis Lua 引擎时,并没有考虑到客户与云服务提供商对抗的这种安全模型。当时的假设多少是,既然能操作你的 Redis 服务器的人就是可信的。因此,一般也没有对 Lua 库进行严格的安全审查。当时的想法是,反正如果你已经能访问 Redis API,你本来就能做更糟糕的事。

不过后来情况发生了变化,云服务提供商为了能够提供托管的 Redis 实例,对暴露给客户的 Redis API 进行了限制。然而,像 CONFIG 或 DEBUG 这类命令虽然可以被禁用,但 EVAL 和 EVALSHA 却几乎无法避免暴露。Redis Lua 脚本正是我们社区中使用率最高的功能之一。

于是渐渐地,在我几乎没有察觉的情况下,由于 Redis 向最终用户暴露和提供方式的变化,Lua 库也在本应由 Redis 来处理的安全模型中变成了攻击向量。如前所述,在这种模型下,受影响的与其说是 Redis 用户,不如说是提供托管 Redis 的“云”服务商,但无论如何,这都是一个必须解决的问题。

为了改善云服务提供商在 Lua 脚本这一具体问题上的安全现状,我们能做些什么?我想在未来几个月内做以下几件事。

  1. Lua 栈保护。看起来 Lua 可以通过某种编译方式来确保无法误用 Lua 栈 API,当然会带来一定的性能损失。说实话,我认为 Lua 对栈的假设有点过于简单,库开发者必须不断检查栈上是否有足够空间来推入新值。其他同等抽象层次的语言,其 C API 就没有这个问题。因此,我会尝试评估在 Lua 底层 C API 中加入更多保护措施所带来的性能下降是否可以接受,如果可以,就会着手实现。
  2. 安全审计与模糊测试。尽管时间有限,我已经对 Lua struct 库进行了一部分模糊测试。接下来我会继续开展工作,排查该领域的其他缺陷。我确信还有更多问题存在,我们之所以只发现了目前这批漏洞,仅仅是因为没有更多时间去深入研究脚本子系统。因此,这是一项将要执行的重要工作。同样,在工作结束后,我会与各 Redis 厂商协调,以便他们能及时打上补丁。
  3. 从 Redis 用户的角度来看,当有不可信数据被发送到 Lua 引擎时,使用 HMAC 来确保数据未被篡改非常重要。例如,有一种常见的模式是将用户的状态直接存储在用户 cookie 中,之后再进行解码。这类数据之后可能会被用作 Redis Lua 函数的输入。这就是一个绝对需要使用 HMAC 来确保我们读取的是之前所存储内容的例子。
  4. 更强的 Lua 沙箱隔离。关于这个话题应该有大量的文献和最佳实践。我们已经实现了一定程度的沙箱隔离,但以我过去做安全时的感觉来看,沙箱本质上永远是一场猫鼠游戏,永远无法做到完美。例如,对 CPU / 内存的滥用,对 Redis 的目标而言可能过于复杂而难以追踪。不过,我们至少应该确保违规行为只会导致“优雅”地中止,而不会引发任何内存破坏问题。
  5. 也许是时候升级 Lua 引擎了?我不确定新版本的 Lua 在安全性方面是否更先进,但我们面临一个巨大的问题:升级 Lua 可能会导致旧脚本无法正常运行。这对 Redis 社区来说是一个非常严重的问题,尤其考虑到 Redis 用户通常编写的脚本类型,更高版本的 Lua 带来的好处其实非常有限。

具体问题

已修复的问题如下列提交所示:

  • ce17f76b Security: 修复 redis-cli 缓冲区溢出。
  • e89086e0 Security: 修复 Lua struct 包的偏移处理。
  • 5ccb6f7a Security: 由 @soloestoy 提供的更多 cmsgpack 修复。
  • 1eb08bcd Security: 更新 Lua struct 包以修复安全问题。
  • 52a00201 Security: 修复 Lua cmsgpack 库的栈溢出。

第一个提交与此次修复工作无关,是一个仅在命令行中传入过长主机参数时才可被利用的 redis-cli 缓冲区溢出。其余问题都是我们在 cmsgpack 和 struct 包中发现的问题。

用于复现这些问题的两个脚本如下:

https://gist.github.com/antirez/82445fcbea6d9b19f97014cc6cc79f8a

以及

https://gist.github.com/antirez/bca0ad7a9c60c72e9600c7f720e9d035

两者均由 Apple 信息安全团队编写。不过第一个脚本经我修改,以使其能更稳定地触发崩溃。

受影响的版本

基本上所有带 Lua 脚本功能的 Redis 版本都受影响。

修复已通过以下 Github 标签提供:

  • 3.2.12
  • 4.0.10
  • 5.0-rc2

稳定版(4.0.10)也已按惯例在 http://download.redis.io 上提供。

各发布版本的 tarball 哈希可在以下地址查看:

https://github.com/antirez/redis-hashes

请注意,新发布的版本还包含了其他一些缺陷修复,因此建议同时阅读发布说明,了解升级到新版本还会涉及哪些变更。

希望未来能再发一篇博文,汇报计划中针对 Redis Lua 脚本子系统的安全审计结果。

本文章由 muse-spark-1.2-contributor 进行翻译

评论