Clarifications on the Incapsula Redis security report

Salvatore Sanfilippo

澄清 Incapsula 的 Redis 安全报告

几天前,我打开 Twitter 信息流开始一天的工作,看到满屏的文章都在说类似这样的话:“75% 的 Redis 服务器感染了恶意软件。”这句明显被误引的话,源自 Incapsula 的一项研究:他们发现,在互联网上开放、没有任何保护、使用公共 IP 地址的 Redis 实例中,有 75% 已被感染 [1]。

[1] https://www.incapsula.com/blog/report-75-of-open-redis-servers-are-infected.html

很多人不需要对此做任何澄清,因为如果你对计算机安全和 Redis 的工作方式有一定了解,就能毫不费力地把这些信息放到正确的语境中。不过我写这篇博文有两个原因。显而易见的一个原因是,它可以帮助媒体以及其他不太了解安全和/或 Redis 的用户理解究竟发生了什么。第二个原因是,暴露的 Redis 实例是一个关于安全默认配置(safe defaults)的案例研究,对安全领域的人来说应该很有意思。

Incapsula 的报告

我们先从 Incapsula 的报告说起。他们做的是分析互联网上暴露的 Redis 实例。这些实例之所以任何地方的任何人都能访问,是因为它们在公共 IP 地址上监听连接,而且没有密码保护。它们就像 HTTP 服务器一样,只不过这里是 Redis;而 Redis 并不是为暴露在互联网上而设计的。

这远不是一个新故事。由于 Redis 很受欢迎,Redis 安装总量非常庞大,其中一部分安装被暴露在外。这种情况基本从一开始就存在。人们在某个云服务商那里启动一台虚拟机,安装 Redis,发现自己无法访问它,于是把虚拟机的端口对所有人开放;此时这个实例就处于无保护状态了。唯一发生变化的是,过去这些实例大多只是运行着而未受到影响。也许某个脚本小子(script kiddie)会连接上去,调用“INFO”或其他几个命令,看看里面有什么,但大多数时候也就到此为止了。如今,对加密货币挖矿的新狂热给了攻击者一个非常充分的理由去入侵他人的系统。实际上,这是最充分的理由:金钱。因此,同样那些暴露的实例如今会被攻破,以便安装某种软件来挖掘某种加密货币。Incapsula 检查了看起来已遭入侵的实例所占的比例。

他们收集针对 Redis 实例所使用的攻击类型的方法,是故意运行一组暴露的实例,以监控攻击者会如何瞄准它们。其中许多攻击看起来都像是我自己的示例攻击 [2] 的变体。

[2] http://antirez.com/news/96

另外值得注意的是,要扫描 IPv4 地址空间来寻找暴露的任何类型的服务,非常简单。例如,你可以使用 masscan。不过 20 年前你就可以用我自己的 hping 做到这一点,而且我记得当时确实成功地做过。

TLDR:互联网上存在开放的 Redis 实例,是因为它们配置错误。攻击者通过在其中安装某种挖矿软件,或出于其他原因,从这些实例中获利。但事情还不止这些……继续读下去。

保护模式

在我看来,安全是一个糟糕的领域。我曾在这个领域工作,但当它不再是地下活动后,我决定离开。人们对安全问题都有非常强烈的看法,但与此同时,对现实世界系统进行安全审计(security auditing)之类的事情却很少真正上心(顺便说一句,Google Project Zero 正在一定程度上改变这一点)。所以,在不知第多少次收到声称 Redis 易受临时文件创建攻击的 PGP 加密邮件后,我写了 [2] 中的那篇博文,只是想说明:“也许你没有抓住 Redis 安全的重点。”我基本上公开了一个针对我自己系统的攻击;我只花了几分钟研究就找到了它,而且它非常严重。我要传达的信息再次是:Redis 并不是为暴露在互联网上而设计的。如果你真的想摆出一副黑客(hack3rzzz)的样子,那么还有更有意思的事情可做。不过这么做也把一件很厉害的武器交到了脚本小子手里,让他们能够入侵各处开放的成千上万个 Redis 实例。

为了弥补自己的过错,试图赎清自己的罪过,我开始思考能做些什么来让情况变得更简单。Redis 3.x 的一个问题是,默认情况下它会监听所有 IP 地址,因此很容易配置错误:只要开放端口,或者根本没有防火墙,这个实例就会暴露出去。关于这一点,安全领域的箴言是“使用安全默认配置”。也就是说,对于 Redis 这个联网缓存来说,要采用一种默认配置,使它基本上无法开箱即用地满足大多数现实用户的需求。更糟糕的是,不懂这些的人会直接设置“bind *”来解决问题,于是我们又回到了最初的状态。因此我试图找到更聪明一些的办法,一个我称为“protected mode”的功能;这让一群 386 处理器非常失望,它们在我家门前示威了好几天。

protected mode 的理念是监听每个地址,但如果连接不是本地连接,服务器就返回一条错误消息,解释它为何没有按预期工作、应该怎么做,以及仅仅通过禁用 protected mode 就把实例开放给所有人所涉及的风险。下面是该功能工作方式的一个示例:

$ nc 192.168.1.194 6379
-DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting the protected mode option to 'no', and then restarting the server. 3) If you started the server manually just for testing, restart it with the '--protected-mode no' option. 4) Setup a bind address or an authentication password. NOTE: You only need to do one of the above things in order for the server to start accepting connections from the outside.

这样做的优势在于:

  1. 用户知道该怎么做才能解决这种情况。这比“拒绝连接”好得多。
  2. 我们会尽量避免用户在不了解相关风险、也不知道还有其他解决方案(设置密码或绑定到特定接口)的情况下禁用 protected mode。

幻灭

所以我原本以为,它或许会比安全默认配置更有帮助。但遗憾的是,你无法解决这样一个事实:有一部分用户就是不在乎;有些人甚至不知道自己安装了 Redis,它只是作为其他东西的依赖项、由脚本安装,或者包含在你正在运行的 ABI 镜像中,等等。

最初通过在 Shodan 上使用 grep 查询时,看起来 Redis 4.0 的 protected mode 确实帮了很大的忙!我能找到的开放实例大多回复“-DENIED”。这很棒。然而在 [1] 发布后,我向 Shodan 询问(顺便说一句,他们很棒,在 Twitter 上也非常乐于提供帮助),能否给我一份暴露 Redis 实例的版本分布。结果是,仍然有大量 Redis 4.0 实例暴露在外 [3]。

[3] https://asciinema.org/a/8heQvivQkFmUrisbb9FQLfMBj

确实,它们比 3.0 实例少,但数量仍然很多。进一步调查后我甚至得知,有些虚拟机镜像的安装脚本默认会移除 Redis 的 protected mode,因为有时人们会嫌安全功能碍事。他们希望东西开箱即用地通过网络运行,所以他们就这么做了——移除这个麻烦。随后这样的镜像变得流行起来,许多人在不了解实际情况的情况下安装它;于是安装 Redis 4 时,protected mode 处于关闭状态,实例也就暴露了。

我认为,这说明可以被用户轻易禁用的安全默认配置存在局限。它们似乎确实能在某种程度上提供帮助,但只能将事件数量降低一定比例,而无法让事件变成罕见的例外。

接下来会怎样?

Redis 安全模型的一个根本问题是,服务器可以通过普通 API,使用 CONFIG 之类的特殊命令重新配置。这是 Redis 非常有价值的功能,但一旦能够访问 Redis API,它也会让入侵实例变得简单得多,而使用 Redis 的普通应用并不需要这么高的访问权限。

因此,在 Redis 6 的开发过程中,我们将引入 ACLs(访问控制列表)。同样,这项功能的引入方式会尽量让现有用户获得“无痛”的体验。如果你在没有任何凭据的情况下连接,Redis 会自动使用“default”用户登录客户端;这个用户可以执行应用通常会执行的所有操作,但会拒绝所有管理命令。当然,也可以通过不同的配置让它更加严格:创建只能执行特定命令、且只能操作匹配给定模式的键的新用户,等等。

在 Redis 6 的开发过程中,我们还计划合并对 SSL 连接的支持。虽然这不太可能对这里讨论的问题产生任何影响,因为默认情况下 Redis 仍会以未加密方式运行,而且该功能需要主动选择启用;不过在某些环境中,SSL 也是让 Redis 使用体验更加安全的一步。

不过我把希望寄托在 ACLs 上,因为普通用户似乎不太可能让默认账户能够执行管理命令。尤其是因为我们计划直接将源自本地主机的连接记录为“admin”用户。如果一切如我所愿,我们仍会看到暴露在外的 Redis 6 实例,因为这不可避免;但至少这些 Redis 6 实例应该会让整个系统更难被攻破。至少理论上如此:Redis 的 EVAL 命令允许执行 Lua 脚本,而由于这是 Redis 的一项基础功能,默认情况下应该允许使用它。我们会尽力提供一种近似沙箱化(sandboxed)的 Lua 执行环境,但如果你关注 IT 安全已有一段时间,就会知道沙箱总是不完善的,与其说是严密的封闭方案,不如说更像是寻找逃逸方法的练习。不过这一次,我会避免为了证明观点而公开攻击方法。