有人说Docker Hub上三成的镜像包含漏洞?是吗?

2015-06-15 14:55


【编者的话】到底Docker Hub上是否三成的镜像存在漏洞?通过漏洞计算发现确实有高比例漏洞,对于官方镜像遵循Docker的安全指南,如若是自创建镜像,可找源仓库或自行处理。但我们发现,这些漏洞中大部分是老镜像。面对漏洞镜像,我们可以采取本地措施,还可用Web安全审查进行检查,如果想让Docker更加安全,建议用dockerbench来评估。文中额外阐述了容器究竟有什么用。

这个数字太神奇了!并不是因为这个比例过高或者过低,而是因为居然存在这个比例。既然存在(相对容易地)计算出这个比例的可能性,也就意味着存在(相对容易地)改善这个比例的可能性。

声明:我是Docker官方人员,然而我写这篇文章并未受到公司批准,所以最好带着批判的思维来读本文。

这个数字来源于BanyanOps的博客

漏洞的计算

首先,来看看怎么才能计算出原文中的数据。这过程比较简单:

  • 获取Docker注册表的一个列表
  • 下载列表中的镜像
  • 检查镜像中的漏洞

这步骤看起来似乎太简单,我们稍微深入下细节。

列举镜像

列举官方的镜像是容易的。这些镜像是基于一个叫bashbrew的自动化系统构建而来的,使用的都是公布于众的方法。顺便一提,这意味着如果你想重建官方的镜像,做起来也会很容易。(记住那些方法中涉及一些blobs或者tar包,是在启动的时候用到的;因而有时你要多费一点力,重建这些blobs或者tarbal)。

构建官方镜像的方法在Github上的docker-library仓库可以找到。

要列出其他的镜像(即属非官方的用户和机构所有的镜像)要困难点。Docker Hub目前没有提供什么方法来列举他们,所以一个暂时可行的方法是搜索一个十分宽泛的关键字,然后对其结果进行提取。当然,这需要一些抓取的工作;抓取到的结果可能会漏掉一些用户的数据,但是你拿到的结果已经会十分接近了。(虽然这么做肯定可行, 我也听说新的注册表接口有一些十分好的特性,可以让这一步完成起来容易点)。

下载镜像

下载镜像是一个繁琐的任务。如果你想安安静静的做这件事情,运行一个docker的守护者进程,然后执行docker pull username/imagename:tag即可。

如果你想拿到容器的文件系统的一个tar包,也很容易:只需要运行docker export username/imagename:tag就行。(记得把标准输出重定向到其他地方,否则你的终端会抓狂的)

如果你不十分相信Docker的守护者进程,你可以检查registry的接口(v1v2)并且通过接口来下载层,然后把层文件重组成镜像文件。一些细节我想留给你自己去做,但是层文件只是普通的tar包,你只需要在彼此之上解包(需要保持正确的顺序)就可以重组成镜像文件。没有什么特别难的步骤;唯一需要留心的地方就是“留白”(whiteout)。“留白”是特殊的标记文件,用来表明“此处曾经有文件存在于此,但目前没有了”。换句话说,如果一个层包含文件/etc/foo.conf,但是之上的层把其删除了,上一层就会包含一个/etc/.wh.foo.conf文件,并且foo.conf不会出现在容器里面。这个文件相当于被留白文件加上了蒙板。

我后来发现,了不起的Tianon已经为此写好了一个脚本,如果感兴趣你可以去看看。

检查镜像

在这个阶段你有几件事情需要做。细节繁琐,本文无法全部叙述;但是在一个全面的安全检查中,下面这些步骤你可能需要做:

  • 运行yum-security或者类似的命令,保证在此刻没有安全更新;
  • 或者更好的做法是:列举出所有安装的包及其版本,然后检查软件包在该版本是否包含漏洞;
  • 计算系统中的每一个文件的hash值,然后去跟已知的有漏洞的文件的hash集合去做比较;
  • 执行自动化工具(如chkrootkit)寻找可疑的文件;
  • 运行一定数量的专门为某些漏洞而打造的漏洞测试。这些测试的目标是尝试利用某些漏洞,然后告诉你,“你的系统有漏洞,因为我已经设法利用了这个漏洞”或者“我无法利用这个漏洞,所以你的系统很可能没有此漏洞”。

事情到了容器的环境中变得很有趣,因为用Docker来自动化这些步骤容易且便捷。例如,你可以将你的漏洞分析工具包放在/tmp/toolkit中,然后对于每一个镜像$I,执行docker run -v /tmp/toolkit:/toolkit $I /toolkit/runall.sh

(注意:这里假设你的工具包是静态链接并且/或者是自包含的,如不依赖你容器镜像中的任何地方,因为这又可能让你的工具包收到蒙骗。我这里主要想说的是,如果你想用一系列的测试来检查你的容器镜像,你可以用容器让这个步骤变得很简单,并且整个过程会更加的快速,因为对于每一个测试你不需要单独做一个检查的机器的拷贝)

提升指标

那么在我们运行完这些测试,然后发现出奇高比例的镜像包含有漏洞的包。我们怎么来改善这个指标呢?

对于官方的镜像,最容易的方法是遵循Docker的安全指南。以后随着官方镜像数的增长,Docker也会改善这个机制,达到自动提示官方镜像的上游安全列表的效果。

对于非官方的镜像,你可以检查镜像中的Author字段:

$ docker inspect --format '{{.Author}}' bin/ngrep
Jerome Petazzoni <jerome@docker.com>

如果该镜像是自动构建出来的,你可以找到其来源的仓库,并且直接联系他们。

如果你直接受到了漏洞的影响,想事情进展的更加快速,你可以自己重建镜像,并且/或者研究怎么才能修复这个漏洞,然后提交一个包含相应修复的PR。这里的意图不是把安全的责任推卸到镜像的使用者身上,而是让有意愿和有能力修复这些漏洞的人能对Docker镜像的安全做贡献。

在将来,这些步骤会完善,并且流式化。会有自动化的过程构建出来减少需要联系相关机构的烦琐,然后尽量降低发布包含漏洞补丁版本的时间。

但是30%还是太高了,对不对?

30%的“有漏洞的镜像”可能听起来非常多。我第一次听到也是这么想。但是如果你细看,你会发现其中一大部分的镜像是老的镜像,它们是__故意不被更新__的。

什么?__故意不被更新__?

是的,并且对此有一些好的解释。第一个是(其中的一部分)照顾其他的媒介。有的发行版想XYZ想在CD/DVD,网络安装,VM镜像,和容器都保持一致。第二个原因是(这也解释了第一个原因)可重复的构建。

设想你有一个服务器跑着12.04,但是你用一个新的Ubuntu 12.04的版本(更别提14.04)却重现不出来。在更加深入的研究后发现,这个问题只存在于那些在某特定时间安装的机器,其版本是12.04.02。如果一个容器镜像有12.04.02的版本,你可以重现出这个bug;否则,你得从其他地方得到这个特定版本。这就是为什么Docker Hub有很多老的镜像,它们保持着发行时的状态 - 同时包含当时发行时的安全问题。尽管这么说,我们已经放置了安全警示说“历史的镜像 - 保持远离”,所以我们比较希望这些镜像在计算出这些安全的指标的时候不应该被包含进去。

让我们希望下次有人在计算安全指标的时候他们能意识到这一点。

在本地采取措施

我们可能跑着有漏洞的镜像!求救!怎么办,怎么办?

事情没有其看起来那么糟。当你(或者其他人)做完对于这些镜像(官方的,公开的,私有的)的审查,结果是一个镜像的列表(以一个唯一的hash串),并且包含“PASS”或者“FAIL”的状态。(对于“FAIL”的镜像,你可能想知道一些其为何不通过的细节,如:“好像有ShellShock/CVE-2014-7187和其他漏洞”或者“包含软件包OpenSSL 1.0.1c / CVE-2014-0160”。)

Web规模的安全审查

你可以拿着这个列表,然后与你本地的镜像做比较。这里就是事情变得有趣的地方。根据本地镜像和这个列表做一个简单而廉价的匹配结果,你就马上能知道你是否运行着有漏洞的镜像。这可以很容易的扩展到成千上百万的主机。

这也意味着事情可以很好的解耦:你的安全审查员不需要访问你的生产环境(甚至不需要访问你的开发环境中的系统)。他们甚至不需要要知道你运行着什么镜像:他们只需大面积的对镜像做分析,然后得出结果给你。你甚至可以从几个安全公司那里拿到结果然后比较他们的结果。

我的镜像在创建后修改过怎么办?

对于新手,你不应该这么做。如果你像更新容器中的某些东西,你应该创建一个新的镜像然后运行该新的镜像。好吧,但是我已经这么做了该怎么办?

那真是什么都说不准,但是至少我们能知道你这么做了。安全审查的一部分,你可以在运行的容器上运行docker diff来知道是否他们被修改了。(通常docker diff是没有结果的。注意你已经用shell启动了一个容器,或者在容器内执行过docker exec,你可能会看到少许的更改。但是生产环境的容器不应该出现任何的更改结果。)

专业的Tip:你甚至可以防止更改,通过在容器中使用--read-only的标记来达到这一点。这会让容器的文件系统只读,保证docker diff的结果为空。

如果你想用一条命令来检查所有的容器,可以执行:

docker ps -q | xargs -I {} dockr diff {}

(感谢@diogomonica提供命令!)

我已经构建了自定义的容器怎么办

如果你构建了自己的容器,我建议你把他们上传到一个仓库里面。如果这是一个公共的,我们就会到最初讨论的情形。如果这是一个私有的,让我们看下一个部分!

私有的镜像和注册表该怎么办?

如果你上传的是私有的镜像怎么办?如果你上传的地方是一个本地的注册表,或者Docker Hub的企业版怎么办?

事情显然变得更加复杂了。你可能会看见有人告诉你“设想ABC有CVE-XYZ的漏洞”如果他们从来没有看到过镜像ABC。

这里是一些可能会发生的事情:

  • 安全提供商可以提供镜像的扫描器,你可以用在自己的镜像上;
  • 安全提供商可以更进一步,将其集成到Docker的注册表中。这可以通过分配读权限(访问Docker Hub上的私有镜像)或者部署前置(on-prem)的安全扫描器(对于Docker Hub企业版的情形)来实现。在两个情形中,都能达到一旦镜像上传就会被自动扫描的效果,并且立即报告任何可能包含的漏洞。

结论

有两点我想强调,因为我相信这能在安全领域产生好的结果。

  1. 得出数字是好的。一旦我们得到了量化的数据,我们可以提升他们。Docker对于安全问题十分延严肃,你可以很肯定我们会和社区及镜像的维护者一期来改善这些量化数据。
  2. 有类似这样的围绕着Docker和Docker Hub的生态环境和社区,让他们成为一个树立标准的地方。正如Soloman在一些keynote里面指出的,Docker里面最重要的一点不是技术,而是让人在某事上达成一致。

后一点意味着Docker现在有足够的批评群体来校正横向的工具(包括安全审查)来让这个生态环境受益。其结果会是更加完善的安全机制,让每一个人受益。

Docker注重安全

如果有Docker公司不在乎安全的印象,那真相离你就太远了。正如上面指出的,我们有一个负责的披露安全的政策,并且我们总是很快的解决我们意识到的问题。没有哪个软件是没有bug的。Docker也是由人类编写出来的,即时他们有些人十分的了优秀,但是还是会犯错。重要的是我们对待安全报告的严肃态度如何,我们解决这些问题的速度如何;我想我们在这些方面都一直做的很好。

如果你想让你的Docker的环境更加安全,我推荐你看下dockerbench。我参与了其编写,该软件包含了一个自动化的评估工具可以用来评估Docker的主机,使用的是CIS Docker 1.6 Benchmark。它会检查很多事情(如是否SELinux和AppArmor是启用了的)然后生成一个报告。

这是Docker会推出或者参与的一大批软件的第一批,目的是让你可以在没有Docker容器安全方面的Ph.d的证书的情形下,或者在没有聘请一个Taylor Swift的情形下,安全地运行Docker。

并且,我们鼓励公开的讨论,安全的担忧也不例外。Docker Library的仓库里面有一个十分有趣的有关于这个话题的讨论

额外的提示

有人问我阐释下容器究竟为什么有用,如果我们不反复的检查我们运行的所有的东西的来源。这里是一些例子:

  • 容器让我们可以在沙箱环境下测试一些危险的操作(如著名的curl ... | sh),并且可以查看到底能产生结果,这得益与docker diff
  • 容器让我们可以在沙箱环境下测试一些危险的操作(比如一个商用软件的install.sh),并且可以查看到底能产生结果,这得益与docker diff
  • 容器让我们可以在沙箱环境下测试一些危险的操作(如安装一个npm,pip,gem等等的包,但是我们不清楚其来源),并且可以查看到底能产生结果,这得益与docker diff
  • 容器让我们可以在沙箱环境下测试一些危险的操作(如安装一个deb,rpm或者其他的发行版的包),并且可以查看到底能产生结果,这得益与docker diff
  • 容器让我们可以在沙箱环境下测试一些危险的操作(如安装一个危险的squid包),并且可以查看到底能产生结果,这得益与docker diff

我猜想你能看出这个模式。仅仅因为事情以一种熟悉的形式呈现给你并不代表它们是安全的。但是我们可以使用Docker来提升安全性。