前言
容器是系统虚拟化的实现技术,容器技术在操作系统层面实现了对计算机系统资源的虚拟化,通过对CPU、内存和文件系统等资源的隔离、划分和控制,实现进程间的资源使用,实现系统资源的共享。docker容器是使用最广、也是最具代表性的容器,这篇文章就来简单探讨docker容器的安全性,学习docker容器的风险与攻击面。
容器与虚拟化
虚拟化(Virtualization)和容器(Container)都是系统虚拟化的实现技术,可实现系统资源的“一虚多”共享。 容器技术是一种“轻量”的虚拟化方式,此处的“轻量”主要是相比于虚拟化技术而言的。例如,虚拟化通常在 Hypervisor 层实现对硬件资源的虚拟化,Hypervisor 为虚拟机提供了虚拟的运行平台,管理虚拟机的操作系统运行, 每个虚拟机都有自己的操作系统、系统库以及应用。而容器并没有 Hypervisor 层,每个容器是和主机 共享硬件 资源及操作系统 。容器技术在操作系统层面实现了对计算机系统资源的虚拟化,在操作系统中,通过对 CPU、内存和文件系统 等资源的隔离、划分和控制,实现进程之间透明的资源使用。
容器的安全机制与缺陷
容器因其轻量,不受运行环境的依赖、能很好的支持CI/CD(持续集成和持续部署)等特性,在快速发展的信息时代也备受开发者青睐。不同于虚拟机可以创建完全隔离的环境并实现完全虚拟化,容器共享宿主机内核和资源,在隔离性上存在一定安全风险,例如虚拟机逃逸风险,会导致对其他容器甚至是宿主机造成威胁。
容器主要通过Linux 内核的Cgroup和Namespace两大特性来实现资源限制和环境隔离。
- Cgroups
Cgroups 是 control groups 的缩写,是 Linux 内核提供的一种可以限制、记录、隔离进程组(process groups)所使用的物理资源(如:cpu,memory,IO等等)的机制。
- Namespace
Linux Namespace提供了一种内核级别隔离系统资源的方法,通过将系统的全局资源放在不同的Namespace中,来实现资源隔离的目的,提供了对 UTS、IPC、Mount、PID、Network、User 等的隔离机制,但Linux内核的namespace机制并不完善、不能实现完全的隔离:
- /proc、/sys等未完全隔离
- Top, free, iostat等命令展示的信息未隔离
- Root用户未隔离
- /dev设备未隔离
- 内核模块未隔离
- SELinux、time、syslog等所有现有Namespace之外的信息都未隔离
Docker容器安全风险攻击面
主要从承载容器运行的负载平台、容器自身、容器中运行的应用、容器编排工具 这几个维度来说明,当然也有别的维度。
负载平台
我们都知道,容器与宿主机共享操作系统内核,因此宿主机的配置对容器的运行安全有着至关重要的影响。
然而,随着云原生时代的到来,此处的『宿主机』不限于物理主机,可能是公有云、私有云、甚至混合云的环境,可以统称为『工作负载』;这种横跨物理机、公有云、私有云、混合云等环境时,可以参考云工作负载保护平台(CWPP)
常见的加固建议有:
- 最小化安装:禁止不必要的端口、不安装额外的服务与软件等;
- 为容器的存储安装单独的分区;
- 工作负载加固:主机安全加固、云工作负载平台加固,保证符合安全规范;
- 更新容器到最新版本;
- 守护进程的控制权限、行为审计
- docker相关的文件和目录进行审计
容器自身
网络安全风险
- 桥接模式(bridge)
在默认的bridge网络模式下,docker主机上的容器都会使用docker0这个网关,容易造成ARP欺骗、端口嗅探等,建议是自行给容器分配网络 或使用docker swarm、kubernetes等容器集群管理平台,创建集群间的overlay网络。
- 主机模式(host)
host模式会有更好的网络性能,因为其使用了主机的本地网络堆栈,共享网络命名空间(漏洞案例:host模式容器逃逸漏洞 cve-2020-15257);但此时 容器将共享主机的网络堆栈,容器能看到主机的所有端口,使容器有了访问主机本地系统服务的全部权限。
- none模式
在这种模式下,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。
- container模式
这个模式指定新创建的容器和已经存在的一个容器共享一个Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的,两个容器进程通过lookback网卡通信。
逃逸风险
容器逃逸攻击指的是容器利用漏洞,“逃逸”出了其自身所拥有的权限,实现了对宿主机和宿主机上其他容器的访问。
- Linux内核漏洞:
CVE-2016-5195 Dirty Cow
(脏牛漏洞)
该漏洞是Linux内核的内存子系统在处理写时拷贝(Copy-on-Write)时存在条件竞争漏洞,导致可以破坏私有只读内存映射,黑客可以获取低权限的本地用户后,利用此漏洞获取其他只读内存映射的写权限,进一步获取root权限。
Linux Kernel版本不低于2.6.22 的所有 Linux 系统(从 2007 年发布 2.6.22 版本开始,直到2016年10月18日为止,这中间发行的所有 Linux 系统都受影响),可自查Linux内核版本 uname -a
。
exp:git clone https://github.com/gbonacini/CVE-2016-5195.git
- docker软件漏洞
a. Shocker攻击
通过调用open_by_handle_at
函数(能够读取文件的函数)对宿主机文件系统进行暴力扫描,以获取宿主机的目标文件内容。由于Docker1.0之前版本对容器能力(Capability)使用黑名单策略进行管理,并没有限制CAP_DAC_READ_SEARCH
能力,赋予了shocker.c程序调用open_by_handle_at
函数的能力,导致容器逃逸的发生。
漏洞影响版本:Docker版本< 1.0, 存在于 Docker 1.0 之前的绝大多数版本。
exp:https://github.com/gabrtv/shocker
b. runC容器逃逸 CVE-2019-5736
runc:一个基于OCI (Open Container Initiative)标准的轻量级工具,用于创建和运行容器。
漏洞允许恶意容器(以最少的用户交互)覆盖host上的runc文件,从而在host上以root权限执行任意代码。所有使用到runc的容器服务和产品(如Docker、Kubernetes等)均受漏洞影响。
环境安装:curl https://gist.githubusercontent.com/thinkycx/e2c9090f035d7b09156077903d6afa51/raw -o install.sh && bash install.sh
POC: git clone https://github.com/Frichetten/CVE-2019-5736-PoC
c.dcker cp命令逃逸 CVE-2019-14271
当docker使用cp命令(实现容器和宿主机的文件拷贝)时,会调用在宿主机命名空间中的docker-tar,加载位于容器中的libnss_.so库,当容器被攻击者控制或存在恶意镜像时,攻击者可以通过替换libnss_.so库为恶意代码并将其注入到docker-tar中,实现docker逃逸、获得宿主机权限。
docker-tar的原理是chroot到容器中,归档其中请求的文件及目录,然后将生成的tar文件传回Docker守护进程,该进
程负责将文件提取到宿主机上的目标目录中。
docker-tar的原理其实是使用了chroot,其实就是设置了一个根目录,将要请求的文件与目录放到这个根目录中去,生成一个tar文件,然后再将这个tar文件交给docker的守护进程,由守护进程将文件复制到目标文件夹中去。
- 配置不当导致逃逸
Docker Remote API 未授权访问
Docker Remote API可以实现对容器的远程系统命令控制,相当于在dcoker界面执行指令;攻击者利用该未授权漏洞API能远程控制docker容器,实现容器逃逸、进而危害到宿主机,被广泛用于挖矿、蠕虫病毒等。
复现环境 https://github.com/vulhub/vulhub/tree/master/docker/unauthorized-rce
docker.sock 挂载到容器内部
是Docker守护进程(Docker daemon)默认监听的Unix域套接字(Unix domain socket),容器中的进程可以通过它与Docker守护进程进行通信,当其被挂载在容器内时,攻击者可利用 docker.sock 和docker deamon 通信,运行一个高权限的恶意容器,进而实现逃逸。
docker 高危启动参数
a. privileged 特权模式
当docker用特权模式启动时 ----privileged
,相当于放开了容器和宿主机对 Network,PID,IPC namespace命名空间
的限制,攻击者可通过–volume 将宿主机根目录挂载进容器中,直接实现逃逸。
b. 敏感目录挂载
例如将宿主机root目录挂载至容器,写入ssh密钥后获得宿主机权限
docker run -itd -v /root:/root ubuntu:18.04 /bin/bash
c. 相关启动参数的安全问题
docker通过linux namespace实现 主机名、用户权限、文件系统、网络、进程号、进程间通讯等资源的隔离,当启动时如果给予容器的权限过高就可能导致逃逸发生。
镜像安全
镜像是docker容器的静态表现形式,镜像的安全决定了容器运行时的安全,镜像的风险面主要分为如下几类:
- 镜像漏洞:如果镜像本来就存在cve、应用组件、语言包漏洞甚至是恶意上传的后门镜像等,开发者在从仓库(如 :docker hub 、阿里云等第三方镜像仓库)拉取镜像进行开发时就会直接引入安全风险,增大了容器的风险面。
针对容器镜像的安全扫描工具有:trivy、clair、Anchore、Quay等,可以嵌入CI流水线、和镜像仓库集成,实现在开发阶段就及时发现安全风险。
- 镜像仓库:用于存储容器镜像的仓库,安全风险主要包括仓库本身的安全风险和镜像拉取传输过程中的风险。
仓库本身的安全风险例如,将私有镜像仓库配置不当端口对外了、仓库存在越权漏洞可以创建管理员(CVE-2019-16097:Harbor任意管理员注册)。
镜像传输过程中风险包括,如何确保容器镜像从镜像仓库到用户端的完整性,不被中间人劫持、篡改。Docker已在其1.8版本后采用内容校验机制解决中间人攻击的问题,但非默认配置,需主动开启(export DOCKER_CONTENT_TRUST = 1)
。
- dockerfile
镜像构建的方式通常有两种:基于容器直接构建或基于dockerfile构建,基于dockerfile构建的镜像是“透明”的,所有指令都可以追溯,其中的安全风险在于正确使用镜像指令和敏感信息的处理:
**指令的正确选择:**在Dockerfile中没有指定USER,Docker将默认以root用户的身份运行该Dockerfile创建的容器,如果该容器遭到攻击,那么宿主机的root访问权限也可能会被获取;若需引入外部文件,在 Dockerfile 中能用COPY 指令就不要使用ADD 指令,因为COPY 指令只是将文件从本地主机复制到容器文件系统,ADD 指令却可以从远程 URL 下载文件并执行诸如解压缩等操作,这可能会带来从 URL 添加恶意文件的风险。
防止敏感信息泄露:如果在Dockerfile文件中存储了固定密码等敏感信息并对外进行发布,则可能导致数据泄露的风险。
最小必要原则:在编写指令时,只添加必要的应用和服务,ssh、telnet等高危服务若不使用就不必开启。
容器应用
微服务
服务软件从单一的应用程序迁移为基于容器的大量微服务,在带来许多好处的同时也改变了软件内部的通讯 模式。从网络和安全角度来看,最显著的变化是内部网络的东西向通信流量剧增,边界变得更加模糊。尽管每个 运行中的容器都可以被加固,也可限定有限的对外网络通信接口,但因为通信接口总量的激增,也给网络攻击者 探测和发现漏洞带来更多机会在这种动态变化的容器环境中,传统网络防火墙不仅难以看到容器之间的网络流量,而且随着容器的快速启 动和消失,它也无法适应这种持续的变化。
正如一位网络安全架构师所说:“在一个容器化的世界里,你无法手 动配置 Iptables 或手动更新防火墙规则。”
云原生的环境中,需要对应的云原生容器防火墙,它能够隔离和保护应用程序容器和服务。即使在容器动态扩展或缩小的情况下也会自动实现发现、跟随和保护。
微分段是比传统以网络地址为粒度的分段更细的隔离机制,例如可以是单个容器、同网段的容器集合或容器 应用等。微分段也是容器防火墙的基本功能之一,容器防火墙可感知第七层或者应用层,根据上层应用对连接进 行动态的控制,因而容器防火墙可实现面向业务的动态微分段,成为了保护东西向流量场景中容器免受恶意攻击 第一道防线。
容器防火墙主要是针对保护容器之间的网络会话,面向东西向场景,所以并不会取代部署在数据中心入口处 的防火墙等系统,比如 NGFW、IDS/IPS或WAF。相反,容器防火墙和传统防火墙协同合作,可以有效防止内部应用程序级别的攻击。
容器编排工具
docker的广泛用于也促进了整个云原生生态的发展,包括编排系统(常见的例如k8s)等