2018年,漏洞一个叫Felix Wilhelm的漏洞安全研究员在Docker的`docker cp`命令里挖出了一个漏洞。利用它,漏洞攻击者可以从容器内部把文件写到宿主机上任意路径——包括`/etc/cron.d/`。漏洞这意味着什么?漏洞容器里跑一个恶意进程,宿主机上就能拿到root shell。漏洞容器隔离,漏洞形同虚设。漏洞
这不是漏洞孤例。Docker的漏洞漏洞史几乎和它的流行史一样长,而且每一类漏洞背后都对应着一个设计层面的漏洞取舍。
容器逃逸是Docker漏洞里最致命的一类。容器的漏洞隔离靠的是Linux的namespace和cgroup,说白了就是漏洞内核提供的几把锁。锁本身没问题,漏洞但配锁的人经常出问题。
CVE-2019-5736是个典型。runc——Docker用来创建容器的底层工具——在处理`/proc/self/exe`时没有做好权限校验。攻击者可以在容器内覆写宿主机的runc二进制文件。等你下次执行`docker exec`,跑的就是攻击者的代码了。这个漏洞影响几乎所有容器运行时,因为大家都用runc。修复方式倒是干脆:runc 1.0-rc6直接改了文件描述符的打开方式,不给覆写留窗口。
另一个经典是CVE-2020-15257。containerd的shim进程监听在一个Abstract Unix Socket上,而这个socket在容器内部居然可以访问。攻击者连上去就能操作shim,进而影响宿主机。这个漏洞的根因是namespace隔离没覆盖到abstract socket——一个很多人以为“应该没问题”的地方。
还有CVE-2022-0492,cgroup v1的release_agent功能被滥用。容器内如果有权限写cgroup的release_agent文件,就能让宿主机执行任意命令。这个漏洞后来被Kubernetes和Docker同时修补,但根本解法是迁移到cgroup v2。
Docker官方文档反复警告不要用`--privileged`,但生产环境里这么干的不在少数。一旦容器以特权模式运行,它基本就拥有了宿主机的所有能力。不需要什么0day,`mount`一下宿主机的磁盘就完事了。
还有挂载Docker socket(`/var/run/docker.sock`)进容器的操作。很多CI/CD流水线这么干,方便在容器里构建镜像。但攻击者只要拿到这个socket,就能创建一个新容器,把宿主机根目录挂进去。这甚至不算漏洞——是用户自己把钥匙递出去的。
Docker Hub上拉下来的镜像,你确定里面只有你需要的东西?2018年,17个官方镜像被发现在特定版本中包含了挖矿恶意程序。攻击者通过盗取维护者账号,在镜像里埋了后门。这类攻击的隐蔽性极强——你`docker pull`下来的东西,构建层里多一个`RUN`指令,不仔细看根本发现不了。
镜像层缓存机制也带来过问题。CVE-2018-15664利用了`docker cp`在解析符号链接时的TOCTOU(检查时间与使用时间)竞争条件。攻击者在容器内不断切换符号链接指向,`docker cp`在检查时看到的是安全路径,实际操作时链接已经指向了宿主机敏感文件。这种竞争窗口通常只有微秒级,但攻击者可以反复尝试。
升级。Docker和runc的漏洞修复通常很快,但很多环境跑的是几年前的老版本。检查一下`docker version`,如果低于20.10,该动了。
别用root跑容器。`--user`指定一个非特权用户,能挡掉大量提权路径。`--read-only`挂载根文件系统,`--cap-drop=ALL`然后按需加回,这些都是基本操作。
seccomp和AppArmor别关。Docker默认的seccomp配置已经拦掉了几十个危险系统调用。有人为了跑某个程序图省事直接`--security-opt seccomp=unconfined`,等于把防弹衣脱了。
镜像用官方的,tag用具体的digest而不是`latest`。私有registry配好认证和扫描。Trivy、Clair这类工具定期扫一遍,能发现已知的CVE。
Docker的漏洞不会消失。它的攻击面太大了——内核、运行时、网络、存储、镜像分发,每一层都可能出问题。但大部分实际被利用的漏洞,修复方式都不复杂。复杂的是让人真的去修。