TranslateProject/published/201501/20150104 Docker Image Insecurity.md
2015-02-01 11:42:08 +08:00

10 KiB
Raw Blame History

Docker的镜像并不安全

最近使用Docker下载“官方”容器镜像的时候我发现这样一句话

ubuntu:14.04: The image you are pulling has been verified (您所拉取的镜像已经经过验证)

起初我以为这条信息引自Docker大力推广的镜像签名系统因此也就没有继续跟进。后来研究加密摘要系统的时候——Docker用这套系统来对镜像进行安全加固——我才有机会更深入的发现逻辑上整个与镜像安全相关的部分具有一系列系统性问题。

Docker所报告的一个已下载的镜像经过“验证”它基于的仅仅是一个标记清单signed manifest)而Docker却从未据此清单对镜像的校验和进行验证。一名攻击者以此可以提供任意所谓具有标记清单的镜像。一系列严重漏洞的大门就此敞开。

镜像经由HTTPS服务器下载后通过一个未加密的管道流进入Docker守护进程

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

这条管道的性能没有问题但是却完全没有经过加密。不可信的输入在签名验证之前是不应当进入管道的。不幸的是Docker在上面处理镜像的三个步骤中都没有对校验和进行验证。

然而不论Docker如何声明实际上镜像的校验和Checksum从未经过校验。下面是Docker与镜像校验和的验证相关的代码片段,即使我提交了校验和不匹配的镜像,都无法触发警告信息。

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

不安全的处理管道

解压缩

Docker支持三种压缩算法gzip、bzip2和xz。前两种使用Go的标准库实现内存安全memory-safe)因此这里我预计的攻击类型应该是拒绝服务类的攻击包括CPU和内存使用上的当机或过载等等。

第三种压缩算法xz比较有意思。因为没有现成的Go实现Docker 通过执行(exec)xz二进制命令来实现解压缩。

xz二进制程序来自于XZ Utils项目,由大概2万行C代码生成而来。而C语言不是一门内存安全的语言。这意味着C程序的恶意输入在这里也就是Docker镜像的XZ Utils解包程序潜在地存在可能会执行任意代码的风险。

Docker以root权限运行 xz 命令,更加恶化了这一潜在威胁。这意味着如果在xz中出现了一个漏洞,对docker pull命令的调用就会导致用户整个系统的完全沦陷。

Tarsum

对tarsum的使用其出发点是好的但却是最大的败笔。为了得到任意一个加密tar文件的准确校验和Docker先对tar文件进行解密然后求出特定部分的哈希值同时排除剩余的部分而这些步骤的顺序都是固定的

由于其生成校验和的步骤固定,它解码不可信数据的过程就有可能被设计成攻破tarsum的代码。这里潜在的攻击既包括拒绝服务攻击,还有逻辑上的漏洞攻击,可能导致文件被感染、忽略、进程被篡改、植入等等,这一切攻击的同时,校验和可能都是不变的。

解包

解包的过程包括tar解码和生成硬盘上的文件。这一过程尤其危险因为在解包写入硬盘的过程中有另外三个已报告的漏洞

任何情形下未经验证的数据都不应当解包后直接写入硬盘。

libtrust

Docker的工具包libtrust,号称“通过一个分布式的信任图表进行认证和访问控制”。很不幸,对此官方没有任何具体的说明,看起来它好像是实现了一些javascript对象标记和加密规格以及其他一些未说明的算法。

使用libtrust下载一个清单经过签名和认证的镜像就可以触发下面这条不准确的信息说不准确是因为事实上它验证的只是清单并非真正的镜像

ubuntu:14.04: The image you are pulling has been verified(您所拉取的镜像已经经过验证)

目前只有Docker公司“官方”发布的镜像清单使用了这套签名系统但是上次我参加Docker管理咨询委员会的会议讨论时我所理解的是Docker公司正计划在未来扩大部署这套系统。他们的目标是以Docker公司为中心控制一个认证授权机构对镜像进行签名和客户认证。

我试图从Docker的代码中找到签名秘钥但是没找到。好像它并不像我们所期望的把密钥嵌在二进制代码中而是在每次镜像下载前由Docker守护进程通过HTTPS从CDN远程获取。这是一个多么糟糕的方案有无数种攻击手段可能会将可信密钥替换成恶意密钥。这些攻击包括但不限于CDN供应商出问题、CDN初始密钥出现问题、客户端下载时的中间人攻击等等。

补救

研究结束前,我报告了一些在tarsum系统中发现的问题但是截至目前我报告的这些问题仍然没有修复。

要改进Docker镜像下载系统的安全问题我认为应当有以下措施

摒弃tarsum并且真正对镜像本身进行验证

出于安全原因tarsum应当被摒弃同时镜像在完整下载后、其他步骤开始前就对镜像的加密签名进行验证。

添加权限隔离

镜像的处理过程中涉及到解压缩或解包的步骤必须在隔离的进程容器中进行即只给予其操作所需的最小权限。任何场景下都不应当使用root运行xz这样的解压缩工具。

替换 libtrust

应当用更新框架(The Update Framework)替换掉libtrust这是专门设计用来解决软件二进制签名此类实际问题的。其威胁模型非常全方位能够解决libtrust中未曾考虑到的诸多问题目前已经有了完整的说明文档。除了已有的Python实现我已经开始着手用Go语言实现的工作,也欢迎大家的贡献。

作为将更新框架加入Docker的一部分还应当加入一个本地密钥存储池将root密钥与registry的地址进行映射这样用户就可以拥有他们自己的签名密钥而不必使用Docker公司的了。

我注意到使用非Docker公司官方的第三方仓库往往会是一种非常糟糕的用户体验。Docker也会将第三方的仓库内容降为二等地位来看待即使不因为技术上的原因。这个问题不仅仅是生态问题还是一个终端用户的安全问题。针对第三方仓库的全方位、去中心化的安全模型既必须又迫切。我希望Docker公司在重新设计他们的安全模型和镜像认证系统时能采纳这一点。

结论

Docker用户应当意识到负责下载镜像的代码是非常不安全的。用户们应当只下载那些出处没有问题的镜像。目前这里的“没有问题”并包括Docker公司的“可信trusted”镜像例如官方的Ubuntu和其他基础镜像。

最好的选择就是在本地屏蔽 index.docker.io,然后使用docker load命令在导入Docker之前手动下载镜像并对其进行验证。Red Hat的安全博客有一篇很好的文章,大家可以看看。

感谢Lewis Marshall指出tarsum从未真正验证。

参考


via: https://titanous.com/posts/docker-insecurity

作者:titanous 译者:Mr小眼儿 校对:wxy

本文由 LCTT 原创翻译,Linux中国 荣誉推出