最近使用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 通过xz二进制命令来实现解压缩。
xz二进制程序来自于XZ Utils项目,由2万行C代码生成而来。而C语言不是一门内存安全的语言。这意味着C程序的恶意输入,在这里也就是Docker镜像的XZ Utils解包程序,潜在地存在可能会执行任意代码的风险。
Docker以root权限运行 xz 命令,更加恶化了这一潜在威胁。这意味着如果在xz中出现了一个漏洞,对docker pull命令的调用就会导致用户整个系统的完全沦陷。
Tarsum
对tarsum的使用,其出发点是好的,但却是最大的败笔。为了得到任意一个加密tar文件的准确校验和,Docker先对tar文件进行解密,然后求出特定部分的哈希值,同时排除剩余的部分,而这些步骤的顺序都是固定的。
由于其生成校验和的步骤固定,它解码不可信数据的过程就有可能被设计成。这里潜在的攻击既包括拒绝服务攻击,还有逻辑上的漏洞攻击,可能导致文件被感染、忽略、进程被篡改、植入等等,这一切攻击的同时,校验和可能都是不变的。
解包
解包的过程包括tar解码和生成硬盘上的文件。这一过程尤其危险,因为在解包写入硬盘的过程中有另外三个。
任何情形下未经验证的数据都不应当解包后直接写入硬盘。
libtrustDocker的工具包libtrust,号称“通过一个分布式的信任图表进行认证和访问控制”。很不幸,对此官方没有任何具体的说明,看起来它好像是实现了一些javascript对象标记和加密规格以及其他一些未说明的算法。