从 Docker 安全机制探究 Docker 逃逸

2019-09-03 约 187 字 预计阅读 1 分钟

声明:本文 【从 Docker 安全机制探究 Docker 逃逸】 由作者 RTL 于 2019-09-03 09:05:00 首发 先知社区 曾经 浏览数 122 次

感谢 RTL 的辛苦付出!

背景

自2013年Docker发行开始,一直受到人们广泛关注,被认为成可能改变行业的一款软件。Docker的使用相信大家已经很熟悉,在这我也不一一介绍,今天我们探究的是 Docker 的实现问题。

Docker -> 容器

Docker 是一种容器,容器的官方定义是:容器就是将软件打包成标准化单元,以用于开发、交付和部署。容器的英文为 container,container 有一种意思为集装箱,集装箱的特点,在于其格式划一,并可以层层重叠。容器也是这样,我们平常在使用 Docker 过程中,其实就相当于将每一层的 Layer 拼接起来,去实现功能。在这说一个小问题,在构建Dockerfile 时,尽量把命令写到同一个 RUN 中,使用 && 拼接起来,因为 Docker 会将 Dockerfile 中不同的 RUN 封装到不同层中,导致整个 Docker 体积过大。

Docker vs 虚拟机

Docker 自出现后便经常与虚拟机做比较,有些人甚至认为 Docker 就是一种虚拟机。虚拟机总的来说是利用 Hypervisor 虚拟出内存、CPU等等。正如我上面所说,Docker 是一种容器,那么它跟虚拟机的区别在哪?
我们来看一张图:我们把图中的矩形看作一个计算机,内部的圆圈看作一个又一个的进程,它们使用着相同的计算机资源,并且相互之间可以看到。

Docker 做了什么事呢?Docker 给它们加了一层壳,使它们隔离开来,此时它们之间无法相互看到,但是它们仍然运行在刚刚的环境上,使用着与刚刚一样的资源。我们可以理解为,它们与加壳之前的区别就是无法相互交流了。需要说一句的是,这个壳我们可以将它看作一个单向的门,外部可以往内走,但是内部却不能往外走。这在计算机中的意思就是,外部进程可以看到内部进程,但是内部进程却不能看到外部进程。

这种机制是由 Linux namesapce 来实现的 namespace 是Linux 内核用来隔离内核资源的方式。其主要有Cgroup、IPC、Network、Mount、PID、User、UTS 七种,我刚刚所举的例子便是将进程进行了隔离。我们可以将命名空间看作一个集合,集合便存在父集以及子集,这便可以理解为我刚刚所说的单向门,父命名空间可以访问子命名空间,但子命名空间却不能访问父命名空间。Docker的隔离便是基于Linux namespace来实现的,其默认开启的隔离有ipc、network、mount、pid、uts。进程所属的命名空间可以在 /proc/$$/ns 里面查看。

由以上可知,Docker 并没有使用任何虚拟化技术,其就是一种隔离技术。如果你对 Linux 命令比较熟悉,甚至可以理解为 Docker 是一种高级的 chroot。

Docker安全机制

因为Docker所使用的是隔离技术,使用的仍然是宿主机的内核、cpu、内存,那会不会带来一些安全问题?答案是肯定的,那 Docker 是怎么防护的?
Docker的安全机制有很多种:Linux Capability、AppArmor、SELinux、Seccomp等等,本文主要讲述一下 Linux Capability
因为Docker默认对User Namespace未进行隔离,在Docker内部查看 /etc/passwd 可以看到 uid 为 0,也就是说,Docker内部的root就是宿主机的root。但是如果你使用一些命令,类似 iptables -L,会提示你权限不足。

这是由 Linux Capability 机制所实现的。
自Linux 内核 2.1 版本后,引入了 Capability 的概念,它打破了操作系统中超级用户/普通用户的概念,由普通用户也可以做只有超级用户可以完成的操作。
Linux Capability 一共有 38 种,分别对应着一些系统调用,Docker 默认只开启了 14种。
这样就避免了很多安全的问题。熟悉 Docker操作的人应该可以意识到,在开启 Docker 的时候可以加一个参数是 --privileged=true,这样就相当于开启了所有的 Capability。
使用 docker inspect {container.id} 在 CapAadd 项里可以看到添加的 capability

具体每个Capability所对应的功能可去Linux官方手册查询 Capability定义

Docker 逃逸

Docker 实现原理已知,下面我们就来探讨一下 Docker 逃逸的原理。
Docker 逃逸的目的是获取宿主机权限,目前的 Docker 逃逸的原因可以划分为三种:

  • 由内核漏洞引起
  • 由 Docker 软件设计引起
  • 由配置不当引起

对于由内核漏洞引起的漏洞,其实主要流程如下:

  1. 使用内核漏洞进入内核上下文
  2. 获取当前进程的task struct
  3. 回溯 task list 获取 pid = 1 的 task struct,复制其相关数据
  4. 切换当前 namespace
  5. 打开 root shell,完成逃逸

对于 Docker 软件设计引起的逃逸
爆出过的 runc (cve-2019-5736),就是因为 Docker 挂载了宿主机的 /proc 目录而引起的。

对于配置不当引起的逃逸
在这我举一个例子。比如我们开启了特权容器或者cap-add=SYS_ADMIN,此时 Docker 容器具有 mount 权限,查看 fdisk -l 将宿主机存储硬盘挂载到 Docker 内部目录上,这样就引起了逃逸。

关键词:[‘安全技术’, ‘技术讨论’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now