Clarifications on the Incapsula Redis security report

Salvatore Sanfilippo

关于 Incapsula Redis 安全报告的澄清

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

几天前,我一打开 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 实例本身就是一个关于“安全默认值”问题的典型案例,对安全圈来说也很有研究价值。

Incapsula 报告

先从 Incapsula 的报告说起。他们所做的是分析互联网上暴露的 Redis 实例。也就是那些互联网上任何人、任何地方都能访问的实例,因为它们在公网 IP 上监听连接,却没有任何密码保护。这就好像它们是 HTTP 服务器,只不过跑的是 Redis,而 Redis 本来就不是设计用来直接暴露在外的。

这并不是什么新鲜事。由于 Redis 非常流行,安装总量十分庞大,其中有一部分不可避免地会被暴露在外。从一开始就基本如此。人们在某个云服务商那里启动一台虚拟机,装上 Redis,发现连不上,于是就把虚拟机的端口对所有人开放,这时实例就处于完全无保护的运行状态了。唯一的变化是,过去大多数这类实例即便暴露在外,很多时候也相安无事。也许会有某个脚本小子连上来执行一下 “INFO” 或其他几个命令,看看里面有什么内容,大多数时候也就到此为止了。而现在,对加密货币挖矿的狂热给了攻击者一个非常好的理由去入侵他人的系统。实际上是最好的理由:赚钱。于是,同样那些暴露的实例现在会被攻破,用来安装挖矿软件,挖掘某种加密货币。Incapsula 统计的正是看起来已被入侵的实例所占的比例。

他们收集针对 Redis 实例的攻击类型的方式,是故意运行一组暴露在外的实例,来观察攻击者会如何对它们下手。许多攻击看起来都是基于我自己发布的示例攻击 [2] 的变种。

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

另外值得一提的是,扫描整个 IPv4 地址空间来寻找任何一种服务的暴露实例,是件轻而易举的事。比如你可以用 masscan。不过早在 20 年前,用我自己写的 hping 就能做到,我记得当时确实也成功做过。

一句话总结:互联网上存在开放的 Redis 实例是因为配置错误。攻击者通过安装挖矿软件等方式从中牟利。但事情还不止这些……请继续往下看。

保护模式

在我看来,安全是个很糟糕的领域。我曾在这个领域工作过,后来当它不再是地下圈子的事情时,我就离开了。人们对安全问题往往主见很强,但与此同时,却很少有人真正关心去对现实世界中的系统做安全审计(顺便说一句,Google 的 Project Zero 正在一定程度上改变这一点)。所以,在第 N 次收到用 PGP 加密的邮件,说 Redis 存在临时文件创建漏洞之后,我写了 [2] 那篇博客,只是想说:“也许你们根本没搞懂 Redis 安全的重点”。我基本上在几分钟的研究里就找到并公布了一个针对我自己系统的、相当严重的攻击。想传达的信息还是那句:Redis 不是设计用来暴露在外的。如果你真想扮黑客高手,有更有意思的事情可做。然而这样一来,我却把一件能攻破成千上万个到处开放的 Redis 实例的利器,亲手交到了脚本小子手里。

为了弥补我的过错,算是赎罪,我开始思考能做些什么来让情况变得简单一些。Redis 3.x 的一个问题是,它默认会监听所有 IP 地址,所以很容易配错:只要把端口开放,或者干脆没有任何防火墙,实例就暴露了。当时安全圈的 mantra 是“使用安全的默认值”。也就是说,对于 Redis 这样一个网络缓存来说,要提供一个默认配置,让它开箱即用就基本在大多数真实场景下无法正常工作。更糟的是,完全不懂的人为了解决连不上问题,会直接 “bind *”,于是又回到了最初的状态。所以我试图找一个更聪明一点的办法,一个被我命名为“保护模式(protected mode)”的功能,为此还让一群 386 处理器在我家门前抗议了好几天,大失所望。

保护模式的思路是,仍然监听所有地址,但如果连接不是来自本地,服务器就会返回一个错误,解释*为什么*它没有按预期工作、该怎么做,以及如果只是傻乎乎地关掉保护模式、把实例对所有人开放会有什么风险。这个功能是这样工作的,例如:

$ 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. 我们尽量避免用户在不了解风险的情况下就关掉保护模式,并让他们知道还有其他解决办法(比如设置密码或绑定到特定网卡)。

幻灭

所以我曾幻想,也许这会比安全的默认值更有用。但不幸的是,你无法改变这样一个事实:总有一部分用户根本不在乎,还有一些人甚至都不知道自己装了 Redis,它只是作为其他软件的依赖被顺带装上的,或是被某个脚本装上的,或是包含在你正在运行的虚拟机镜像里,等等。

起初通过在 Shodan 上搜索,看起来 Redis 4.0 的保护模式确实帮了大忙!我找到的开放实例大多都返回了“-DENIED”。这很棒。然而在 [1] 发表之后,我请 Shodan(顺便说一句,他们很棒,在 Twitter 上也非常热心)给我一份暴露的 Redis 实例的版本分布。结果是,仍然有大量的 Redis 4.0 实例暴露在外 [3]。

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

诚然,它们比 3.0 的实例要少,但数量依然很多。再深入调查后,我甚至被告知,有些虚拟机镜像的安装脚本默认就*移除*了 Redis 的保护模式,因为有些人觉得安全功能很烦。他们希望开箱即用就能通过网络直接连上,所以就这么干了,把这个“麻烦”去掉。然后这样的镜像流行开来,很多人便在完全不知情的情况下安装了它,当装上 Redis 4 时,保护模式是关闭的,实例也就暴露了。

我认为,这给我们的教训是,安全默认值如果能被用户轻易关掉,效果是有限的。它似乎能在某种程度上有所帮助,将事故数量降低一定的百分比,但并不能让这类事故变得罕见。

接下来怎么办?

Redis 安全模型中的一个根本问题是,服务器可以通过常规 API,使用像 CONFIG 这样的特殊命令来重新配置。这是 Redis 一个非常有价值的功能,但一旦有人获得了 Redis API 的访问权限,它也会让入侵变得简单得多,而正常使用 Redis 的应用其实并不需要这种级别的权限。

所以在 Redis 6 的开发过程中,我们将引入 ACL。同样,这个功能的引入方式会尽量让现有用户“无痛”升级。如果你不提供任何凭证就连接,Redis 会自动使用 “default” 用户为客户端登录,该用户可以执行应用通常需要的所有操作,但会拒绝所有管理类命令。当然,你也可以通过不同的配置来设置得更严格,创建只能对匹配特定模式的键执行特定命令的新用户,等等。

在 Redis 6 中,我们还计划合并对 SSL 连接的支持,虽然这不太可能对本文讨论的问题产生任何影响,因为默认情况下 Redis 仍将以非加密方式运行,该功能需要手动开启,不过 SSL 对于在某些环境中获得更安全的 Redis 体验来说,也是向前迈出的一步。

然而,我的希望还是寄托在 ACL 上,因为普通用户不太可能让默认账户拥有执行管理命令的权限。尤其是我们计划让来自本机的连接直接以 “admin” 用户身份登录。如果一切如我所愿,我们仍会看到有 Redis 6 实例暴露在外,因为这不可避免,但至少这些 Redis 6 实例应该会让攻破整个系统变得更难。至少理论上是这样:Redis 的 EVAL 命令允许执行 Lua 脚本,而这个功能默认应该是开放的,因为它是 Redis 的核心功能之一。我们试图提供一个近似沙盒化的 Lua 执行环境,但如果你关注 IT 安全有一段时间了,就会知道沙盒总是不完美的,与其说是一种封闭的解决方案,不如说是一场寻找如何逃逸的练习。不过这一次,我不会再为了证明观点而发布攻击方法了。

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

评论