Docker 映像不安全

jopen 9年前

最近用Docker在下载一个官方镜像容器时我看到了这一行:

ubuntu:14.04: The image you are pulling has been verified

我以为这引用了Docker 大力推广的镜像签名系统,当时并没有深究。不久之后, 在研究Docker用以加密镜像的加密摘要系统时,有了继续研究的机会。我发现镜像安全相关的所有逻辑完全系统性的失效。

Docker 的报告称一个下载的镜像仅仅以一个签过名的清单为验证基础, 并且Docker从不校验清单上的镜像的总和校验码。攻击者可以提供任意带有签名清单的镜像。这为一系列严重的漏洞埋下了隐患。

镜像通过一个Docker守护进程里无安全的流处理管道从一个HTTPS服务器上下载

[decompress] -> [tarsum] -> [unpack]

这个管道高效但不安全。 非置信的输入不应在校验签名之前处理。 不幸的是Docker在总和校验码验证之前要处理镜像三次。

不管 Docker的声明 如何,镜像总和校验码从来没有实际校验过。这只是Docker校验镜像总和校验码相关的代码的section0 ,在呈现一个有错误总和校验码的镜像时我甚至无法打开警告触发器。

if img.Checksum != "" && img.Checksum != checksum {    log.Warnf("image layer checksum mismatch: computed %q,               expected %q", checksum, img.Checksum)  }

不安全的处理管道

解压

Docker支持三种压缩算法:gzip、bzip2和xz。前两个使用的是Go的标准库,它们是内存安全的,所以这里我想讨论的利用类型是拒绝服务攻击,比如崩溃、过多的CPU和内存使用。

第三个压缩算法xz更加有趣。因为没有原生的Go实现,所以Docker执行thexzbinary来解压缩。

Thexzbinary 来自于XZ Utils项目,构建自大约20000行的C代码。C并不是一个内存安全的语言。这意味着存在向C程序恶意输入的可能,这样的话Docker 镜像XZ Utils就被开箱了,可能会执行任意代码。
 

通过以管理员权限运行xz,Docker加剧了这个情况的发生。意味着只要在xz中存在一个漏洞,一个docker pull的调用就将导致整个系统的沦陷。

Tarsum

tarsum的使用原本是善意的但却完全错了。为得到一个编码任意的tar文件的真实校验和,Docker对tar解压然后取特定部分的哈希,以一个特定序列却不包含剩余部分。 

由于这个处理是为了生成校验和,它解压了设计用来生成tarsum码2非可信数据。这里会有DOS和逻辑缺陷攻击的风险,可能导致文件被注入,跳过,差异化地处理,修改,添加等等。而校验和不会变化。

解压

解压包括解压tar并将文件放置到磁盘上。这也是非常危险,在写本文时已有三个其他的解压阶段的漏洞报告2 。

当文件还没被校验就解压到磁盘上时还没有解决措施。

libtrust

libtrust是Docker的一个包,宣称提供“通过一个分布式的信任图来控制认证和访问。”但不幸的是不存在详细说明书,但它看起来像是实现了Javascript Object Signing and Encryption说明的一部分和其他未指明的算法。

下载一个使用libtrust签名和认证清单的镜像,会触发这个不安全的信息(仅仅检查了清单,而不是真实的镜像内容):

ubuntu:14.04: The image you are pulling has been verified

目前只有Docker,Inc发布的“官方”镜像使用这个系统签名,但从我最近参加的Docker Governance Advisory Board会议看,我的理解是Docker, Inc打算将来更广泛的部署这个系统。计划的目标是由Docker,Inc集中控制证书的认证,然后在镜像和/或客户端的证书上签名。

我在Docker的代码里找签名钥匙,但没能找到。结果证明这个钥匙并不像期望那样嵌入到了二进制程序中。相反,Docker后台程序在各个镜像下载前,使用HTTPS从CDN上获取。这个方法很糟糕,因为很多攻击可以导致信任的钥匙被恶意的替换。这些攻击包括但不限于:CDN主的妥协,CDN原有托管钥匙的妥协,以及客户端的中间人攻击来下载钥匙。

修补措施

在我完成本次研究前,我报告了我发现的tarsum系统的一些问题,但目前为止我还没发现它们被修复。我认为以下措施有助于提高Docker镜像下载系统的安全:

丢弃tarsum,真实验证镜像摘要

Tarsum不应该用于安全。相反,镜像必须被完整下载,它们的私密签名要在进一步处理发生前进行验证。

添加权限隔离

镜像的处理步骤,包括解压或展开,应该在隔离的仅有最少必要操作权限的进程(容器?)中运行。没有像xz这样的解压工具在root下运行的场景。

替换libtrust

libtrust应该被The Update Framework替代,它被设计出来明显就是为了解决软件二进制签名的问题。威胁模型非常好理解,也强调了许多在libtrust里面没有考虑到的事。它有完整的说明书,也有一个Python的参考实现,我已经开始用Go实现,欢迎对其贡献。

作为添加TUF到Docker中的一部分,应该添加一个本地key仓库,用来将root钥匙映射到注册的URL上,这样用户就可以有自己的签名钥匙,而不是被Docker,Inc管理。

我想强调的是,使用非Docker,Inc托管的登记一般来说是非常差的用户体验。Docker,Inc看起来把第三方登记降为第二等级的状态很满意,而并没有这样做的技术原因。这对于常见的生态系统和终端用户的安全都是一个问题。一个广泛的、去中心化的安全模型对与第三方登记来说,既是必要的也是值得的。我建议Docker,Inc在重新设计他们的安全模型和镜像认证系统时考虑下这个问题。

结论

Docker 用户要意识到下载镜像是非常不安全的。要只下载那些源没有问题的镜像。当前,这个问题没有影响到docker提供的信任的镜像, 包括官方的Ubuntu镜像和其他的基本镜像。

最好的选择是本地屏蔽掉‘index.docker.io’,并且在使用‘docker load’导入到docker之前,先手动下载、验证一下镜像文件。Red Hat的安全论坛上有很多好的相关的帖子(https://securityblog.redhat.com/2014/12/18/before-you-initiate-a-docker-pull/)。

感谢Lewis Marshall 指出了tarsums一直没有验证的问题。