Docker 的镜像并不安全！
Docker 镜像 安全
最近使用Docker下载“官方”容器镜像的时候，我发现这样一句话：



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

```

起初我以为这条信息引自Docker[大力推广](https://blog.docker.com/2014/10/docker-1-3-signed-images-process-injection-security-options-mac-shared-directories/)的镜像签名系统，因此也就没有继续跟进。后来，研究加密摘要系统的时候——Docker用这套系统来对镜像进行安全加固——我才有机会更深入的发现，逻辑上整个与镜像安全相关的部分具有一系列系统性问题。


![](/data/attachment/album/201501/19/104004n7eort68zxjslt72.png)


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


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



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

```

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


然而，不论Docker如何[声明](https://blog.docker.com/2014/10/docker-1-3-signed-images-process-injection-security-options-mac-shared-directories/)，实际上镜像的校验和（Checksum）从未经过校验。下面是Docker与镜像校验和的验证相关的代码[片段](https://titanous.com/posts/docker-insecurity#fn:0)，即使我提交了校验和不匹配的镜像，都无法触发警告信息。



```
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)](https://en.wikipedia.org/wiki/Memory_safety)的，因此这里我预计的攻击类型应该是拒绝服务类的攻击，包括CPU和内存使用上的当机或过载等等。


第三种压缩算法，xz，比较有意思。因为没有现成的Go实现，Docker 通过[执行(exec)](https://github.com/docker/docker/blob/0874f9ab77a7957633cd835241a76ee4406196d8/pkg/archive/archive.go#L91-L95)xz二进制命令来实现解压缩。


xz二进制程序来自于[XZ Utils](http://tukaani.org/xz/)项目，由[大概](https://titanous.com/posts/docker-insecurity#fn:1)2万行C代码生成而来。而C语言不是一门内存安全的语言。这意味着C程序的恶意输入，在这里也就是Docker镜像的XZ Utils解包程序，潜在地存在可能会执行任意代码的风险。


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


**Tarsum**


对tarsum的使用，其出发点是好的，但却是最大的败笔。为了得到任意一个加密tar文件的准确校验和，Docker先对tar文件进行解密，然后求出特定部分的哈希值，同时排除剩余的部分，而这些步骤的[顺序都是固定的](https://github.com/docker/docker/blob/0874f9ab77a7957633cd835241a76ee4406196d8/pkg/tarsum/tarsum_spec.md)。


由于其生成校验和的步骤固定，它解码不可信数据的过程就有可能被设计成[攻破tarsum的代码](https://titanous.com/posts/docker-insecurity#fn:2)。这里潜在的攻击既包括拒绝服务攻击，还有逻辑上的漏洞攻击，可能导致文件被感染、忽略、进程被篡改、植入等等，这一切攻击的同时，校验和可能都是不变的。


**解包**


解包的过程包括tar解码和生成硬盘上的文件。这一过程尤其危险，因为在解包写入硬盘的过程中有另外三个[已报告的漏洞](https://titanous.com/posts/docker-insecurity#fn:3)。


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


### libtrust


Docker的工具包[libtrust](https://github.com/docker/libtrust)，号称“通过一个分布式的信任图表进行认证和访问控制”。很不幸，对此官方没有任何具体的说明，看起来它好像是实现了一些[javascript对象标记和加密](https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-11)规格以及其他一些未说明的算法。


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



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

```

目前只有Docker公司“官方”发布的镜像清单使用了这套签名系统，但是上次我参加Docker[管理咨询委员会](https://titanous.com/posts/docker-insecurity#fn:4)的会议讨论时，我所理解的是，Docker公司正计划在未来扩大部署这套系统。他们的目标是以Docker公司为中心，控制一个认证授权机构，对镜像进行签名和（或）客户认证。


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


### 补救


研究结束前，我[报告](https://github.com/docker/docker/issues/9719)了一些在tarsum系统中发现的问题，但是截至目前我报告的这些问题仍然没有修复。


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


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


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


**添加权限隔离**


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


**替换 libtrust**


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


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


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


### 结论


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


最好的选择就是在本地屏蔽 index.docker.io，然后使用docker load命令在导入Docker之前手动下载镜像并对其进行验证。Red Hat的安全博客有一篇[很好的文章](https://securityblog.redhat.com/2014/12/18/before-you-initiate-a-docker-pull/)，大家可以看看。


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


参考


* [校验和的代码](https://github.com/docker/docker/blob/0874f9ab77a7957633cd835241a76ee4406196d8/image/image.go#L114-L116)
* [cloc](http://cloc.sourceforge.net/)介绍了18141行没有空格没有注释的C代码，以及5900行的header代码，版本号为v5.2.0。
* [Android中也发现了](http://www.saurik.com/id/17)类似的bug，能够感染已签名包中的任意文件。同样出现问题的还有[Windows的Authenticode](http://blogs.technet.com/b/srd/archive/2013/12/10/ms13-098-update-to-enhance-the-security-of-authenticode.aspx)认证系统，二进制文件会被篡改。
* 特别的：[CVE-2014-6407](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-6407)、 [CVE-2014-9356](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-9356)以及 [CVE-2014-9357](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-9357)。目前已有两个Docker[安全发布](https://groups.google.com/d/topic/docker-user/nFAz-B-n4Bw/discussion)有了回应。
* 参见[2014-10-28 DGAB会议记录](https://docs.google.com/document/d/1JfWNzfwptsMgSx82QyWH_Aj0DRKyZKxYQ1aursxNorg/edit?pli=1)的第8页。




---


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


作者：[titanous](https://twitter.com/titanous) 译者：[Mr小眼儿](http://blog.csdn.net/tinyeyeser) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译，[Linux中国](http://linux.cn/) 荣誉推出


OK
[Docker Image Insecurity](/posts/docker-insecurity)

Recently while downloading an “official” container image with Docker I saw this line:

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


I assumed this referenced Docker’s [heavily
promoted](https://blog.docker.com/2014/10/docker-1-3-signed-images-process-injection-security-options-mac-shared-directories/)
image signing system and didn’t investigate further at the time. Later, while
researching the cryptographic digest system that Docker tries to secure images
with, I had the opportunity to explore further. What I found was a total
systemic failure of all logic related to image security.

Docker’s report that a downloaded image is “verified” is based solely on the presence of a signed manifest, and Docker never verifies the image checksum from the manifest. An attacker could provide any image alongside a signed manifest. This opens the door to a number of serious vulnerabilities.

Images are downloaded from an HTTPS server and go through an insecure streaming processing pipeline in the Docker daemon:

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


This pipeline is performant but completely insecure. Untrusted input should not be processed before verifying its signature. Unfortunately Docker processes images three times before checksum verification is supposed to occur.

However, despite [Docker’s
claims](https://blog.docker.com/2014/10/docker-1-3-signed-images-process-injection-security-options-mac-shared-directories/),
image checksums are never actually checked. This is the only section 0 of
Docker’s code related to verifying image checksums, and I was unable to trigger
the warning even when presenting images with mismatched checksums.

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


## Insecure processing pipeline

### Decompress

Docker supports three compression algorithms: gzip, bzip2, and xz. The first two
use the Go standard library implementations, which are
[memory-safe](https://en.wikipedia.org/wiki/Memory_safety), so the exploit
types I’d expect to see here are denial of service attacks like crashes and
excessive CPU and memory usage.

The third compression algorithm, xz, is more interesting. Since there is no
native Go implementation, Docker
[execs](https://github.com/docker/docker/blob/0874f9ab77a7957633cd835241a76ee4406196d8/pkg/archive/archive.go#L91-L95)
the `xz`

binary to do the decompression.

The `xz`

binary comes from the [XZ Utils](http://tukaani.org/xz/) project, and
is built from approximately 1 twenty thousand lines of C code. C is not
a memory-safe language. This means malicious input to a C program, in this case
the Docker image XZ Utils is unpacking, could potentially execute arbitrary
code.

Docker exacerbates this situation by *running xz as root*. This means that if
there is a single vulnerability in

`xz`

, a call to `docker pull`

could result in
the complete compromise of your entire system.### Tarsum

The use of tarsum is well-meaning but completely flawed. In order to get
a deterministic checksum of the contents of an arbitrarily encoded tar file,
Docker decodes the tar and then hashes specific portions, while excluding
others, in a [deterministic
order](https://github.com/docker/docker/blob/0874f9ab77a7957633cd835241a76ee4406196d8/pkg/tarsum/tarsum_spec.md).

Since this processing is done in order to generate the checksum, it is decoding
untrusted data which could be designed to exploit the tarsum code 2. Potential
exploits here are denial of service as well as logic flaws that could cause
files to be injected, skipped, processed differently, modified, appended to,
etc. without the checksum changing.

### Unpacking

Unpacking consists of decoding the tar and placing files on the disk. This is
extraordinarily dangerous as there have been three other vulnerabilities
reported 3 in the unpack stage at the time of writing.

**There is no situation where data that has not been verified should be unpacked
onto disk.**

## libtrust

[libtrust](https://github.com/docker/libtrust) is a Docker package that claims
to provide “authorization and access control through a distributed trust graph.”
Unfortunately no specification appears to exist, however it looks like it
implements some parts of the [Javascript Object Signing and
Encryption](https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-11)
specifications along with other unspecified algorithms.

Downloading an image with a manifest signed and verified using libtrust is what triggers this inaccurate message (only the manifest is checked, not the actual image contents):

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


Currently only “official” image manifests published by Docker, Inc are signed
using this system, but from discussions I participated in at the last Docker
Governance Advisory Board meeting 4, my understanding is that Docker, Inc is
planning on deploying this more widely in the future. The intended goal is
centralization with Docker, Inc controlling a Certificate Authority that then
signs images and/or client certificates.

I looked for the signing key in Docker’s code but was unable to find it. As it
turns out the key is not embedded in the binary as one would expect. Instead the
Docker daemon fetches it [over HTTPS from
a CDN](https://github.com/docker/docker/blob/0874f9ab77a7957633cd835241a76ee4406196d8/trust/trusts.go#L38)
before each image download. This is a terrible approach as a variety of attacks
could lead to trusted keys being replaced with malicious ones. These attacks
include but are not limited to: compromise of the CDN vendor, compromise of the
CDN origin serving the key, and man in the middle attacks on clients downloading
the keys.

## Remediation

I [reported](https://github.com/docker/docker/issues/9719) some of the issues
I found with the tarsum system before I finished this research, but so far
nothing I have reported has been fixed.

Some steps I believe should be taken to improve the security of the Docker image download system:

### Drop tarsum and actually verify image digests

Tarsum should not be used for security. Instead, images must be fully downloaded and their cryptographic signatures verified before any processing takes place.

### Add privilege isolation

Image processing steps that involve decompression or unpacking should be run in
isolated processes (containers?) that have only the bare minimum required
privileges to operate. There is no scenario where a decompression tool like `xz`

should be run as root.

### Replace libtrust

Libtrust should be replaced with [The Update
Framework](http://theupdateframework.com/) which is explicitly designed to solve
the real problems around signing software binaries. The threat model is very
comprehensive and addresses many things that have not been considered in
libtrust. There is a complete specification as well as a reference
implementation written in Python, and I have begun work on a [Go
implementation](https://github.com/flynn/go-tuf) and welcome contributions.

As part of adding TUF to Docker, a local keystore should be added that maps root keys to registry URLs so that users can have their own signing keys that are not managed by Docker, Inc.

I would like to note that using non-Docker, Inc hosted registries is a very poor user experience in general. Docker, Inc seems content with relegating third party registries to second class status when there is no technical reason to do so. This is a problem both for the ecosystem in general and the security of end users. A comprehensive, decentralized security model for third party registries is both necessary and desirable. I encourage Docker, Inc to take this into consideration when redesigning their security model and image verification system.

## Conclusion

Docker users should be aware that the code responsible for downloading images is
shockingly insecure. Users should only download images whose provenance is
without question. At present, this does *not* include “trusted” images hosted by
Docker, Inc including the official Ubuntu and other base images.

The best option is to block `index.docker.io`

locally, and download and verify
images manually before importing them into Docker using `docker load`

. Red Hat’s
security blog has [a good post about
this](https://securityblog.redhat.com/2014/12/18/before-you-initiate-a-docker-pull/).

*Thanks to Lewis Marshall for pointing out the tarsums are never verified.*

-
[cloc](http://cloc.sourceforge.net/)says 18,141 non-blank, non-comment lines of C and 5,900 lines of headers in v5.2.0.[↩](#fnref:1) -
Very similar bugs been

[found in Android](http://www.saurik.com/id/17), which allowed arbitrary files to be injected into signed packages, and[the Windows Authenticode](http://blogs.technet.com/b/srd/archive/2013/12/10/ms13-098-update-to-enhance-the-security-of-authenticode.aspx)signature system, which allowed binary modification.[↩](#fnref:2) -
Specifically:

[CVE-2014-6407](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-6407),[CVE-2014-9356](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-9356), and[CVE-2014-9357](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-9357). There were two Docker[security](https://groups.google.com/forum/#!msg/docker-user/IrjXTHA6jJc/ZToMGL2yG_AJ)[releases](https://groups.google.com/d/topic/docker-user/nFAz-B-n4Bw/discussion)in response.[↩](#fnref:3) -
See page 8 of the

[notes from the 2014-10-28 DGAB meeting](https://docs.google.com/document/d/1JfWNzfwptsMgSx82QyWH_Aj0DRKyZKxYQ1aursxNorg/edit?pli=1).[↩](#fnref:4)