云安全 · 02 · 容器与 Docker 安全基础

这一篇要彻底搞懂:容器到底靠什么隔离?为什么它不像虚拟机那么安全?


一、容器是什么

容器是操作系统级别的虚拟化 :它不虚拟出一整套硬件,而是让多个进程共用同一个 Linux 内核,只是给每个进程组套上"隔离罩"。

1.1 容器 vs 虚拟机

对比项 虚拟机(VM) 容器
隔离层次 硬件级,各自有独立内核 进程级,共享宿主内核
启动速度 分钟级 毫秒~秒级
体积 GB 级 MB 级
隔离强度 强(Hypervisor 隔离) 较弱(内核出问题就一起完蛋)
逃逸难度 需攻破 Hypervisor 内核/配置有缺陷即可逃逸

核心安全结论 :容器共享内核,所以内核漏洞、危险配置都能导致逃逸。这也是第 03 篇存在的原因。


二、容器隔离的三大支柱

容器的"隔离罩"由三样东西组成,缺一不可:

2.1 Namespace(命名空间)------ 负责"看不见"

Namespace 让容器里的进程看不到宿主机的其他资源。Linux 有 8 种:

Namespace 隔离内容 看不到什么
mnt (Mount) 挂载点/文件系统 宿主机的目录树
pid (PID) 进程号 宿主机的其他进程
net (Network) 网卡、IP、端口、路由 宿主机的网络
ipc (IPC) 进程间通信(信号量、共享内存) 宿主机的 IPC
uts (UTS) 主机名和域名 宿主机 hostname
user (User) 用户和用户组 ID 宿主机的用户(容器内 root 可映射成宿主普通用户)
cgroup cgroup 视图 宿主机的 cgroup 树
time 系统时钟(较新) 宿主机的时钟偏移

实验:在容器里查看自己的 namespace

复制代码
docker run --rm alpine:3.19 ls -l /proc/self/ns

逐段解释:

  • docker run:启动一个容器。

  • --rm:容器退出后自动删除,避免堆积垃圾容器。

  • alpine:3.19:使用的镜像,Alpine 是一个只有几 MB 的极小 Linux。

  • ls -l /proc/self/ns/proc/self 是当前进程自己的信息目录,ns 子目录列出该进程所属的各种 namespace。

关键理解 :容器里每个 namespace 后面都带一个 [...] 编号(如 mnt:[4026532...])。如果和宿主机上 ls -l /proc/1/ns 的编号不同,就说明被隔离了。

2.2 Cgroups(控制组)------ 负责"用多少"

Namespace 管"看得见看不见",Cgroup 管"能用多少":CPU、内存、磁盘 IO、进程数上限。

为什么和安全有关 :cgroup 的某些功能(尤其 cgroup v1 的 release_agent)历史上被用作逃逸入口,第 03 篇会讲。

实验:确认你的环境是 cgroup v2

复制代码
stat -fc %T /sys/fs/cgroup
  • stat:查看文件/文件系统信息。

  • -f:显示文件系统信息。

  • -c %T:只输出文件系统类型。

  • 输出 cgroup2fs 表示 cgroup v2;输出 tmpfs 表示 cgroup v1。

可能踩坑 :本实验环境是 cgroup v2 (输出 cgroup2fs)。网上很多老教程的 release_agent 逃逸手法在 cgroup v2 上无效

2.3 联合文件系统(UnionFS / overlayfs)------ 负责"文件从哪来"

容器镜像是一层层叠加的只读层,最上面加一个可写层。容器运行时的写操作都落在可写层,删掉容器可写层就没了。

  • 好处:镜像分层复用,省空间、启动快。

  • 安全含义:镜像层是只读 的,但可写层和挂载点是攻击者重点利用的地方(如挂载宿主机目录)。

实验:查看存储驱动

复制代码
docker info | grep -i "storage driver"

输出 Storage Driver: overlayfs(旧版本叫 overlay2)。


三、Docker 架构与组件

理解组件关系,才能理解逃逸的"跳板"在哪里:

复制代码
docker 命令行
     │  (REST API,默认走 /var/run/docker.sock)
     ▼
dockerd(Docker 守护进程,接收 API 请求)
     │
     ▼
containerd(容器生命周期管理)
     │
     ▼
runc(真正调用内核 namespace/cgroup 创建容器的工具)
     │
     ▼
容器进程
组件 作用 安全关注点
docker CLI 客户端 -
dockerd 守护进程,监听 socket docker.sock 泄露 = 控制整台宿主机
containerd 运行时管理 containerd.sock 同理
runc 底层创建容器 runc 漏洞 = 容器逃逸(CVE-2019-5736)
OCI 容器标准规范 镜像/运行时遵循标准

实验:查看本机 Docker 组件

复制代码
docker info | grep -Ei "server version|cgroup|runtime|storage"

你会看到类似:

复制代码
 Server Version: 29.1.3
 Storage Driver: overlayfs
 Cgroup Driver: systemd
 Cgroup Version: 2
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc

逐行理解:

  • Server Version:dockerd 版本。

  • Storage Driver:文件系统驱动。

  • Cgroup Driver: systemd:cgroup 由 systemd 管理。

  • Cgroup Version: 2:内核 cgroup 版本。

  • Runtimes ... runc:底层运行时是 runc。


四、镜像与 Dockerfile 安全

4.1 镜像分层

复制代码
docker history alpine:3.19

逐层看镜像怎么构建的。安全含义:

  • 镜像层里删掉的文件仍在历史层 里,可能泄露密钥(例如构建时 COPY.envRUN rm,历史层里还在)。

  • 私有镜像仓库未授权可拉取,等于源码/凭证泄露。

4.2 Dockerfile 常见安全问题

问题 例子 风险
硬编码密钥 ENV AK=xxx 镜像里可提取
使用 root 运行 默认就是 root 逃逸后直接是宿主 root
基础镜像过旧 FROM ubuntu:16.04 含大量已知漏洞
安装不必要的包 apt install curl vim gcc 攻击者拿到后工具齐全
ADD 远程文件 ADD http://... / 不可控内容
未固定版本 FROM node:latest 供应链不确定性

最佳实践

  • USER nonroot 以非 root 运行。

  • 多阶段构建,最终镜像不含构建工具和密钥。

  • hadolinttrivy 扫描镜像漏洞和密钥。


五、危险配置项(逃逸的"温床")

这是本篇最重要的部分。下面每一条都是第 03 篇的伏笔。

配置 含义 危害等级
--privileged 给容器几乎全部能力 + 访问所有设备 ★★★★★
-v /var/run/docker.sock:/var/run/docker.sock 把 Docker 控制权给容器 ★★★★★
-v /:/host 挂载宿主机根目录 ★★★★★
--pid=host 共享宿主机 PID 命名空间 ★★★★
--network=host 共享宿主机网络 ★★★
--cap-add=SYS_ADMIN 增加系统管理能力 ★★★★
--cap-add=SYS_PTRACE 可调试/注入其他进程 ★★★
--user=0(默认) 容器内是 root ★★★
未启用 seccomp/AppArmor 缺少系统调用过滤 ★★

5.1 Capabilities(能力)是什么

Linux 把 root 的超级权限拆成了几十种"能力"(capability),容器默认丢弃 了大部分危险能力。--privileged 相当于把能力全加回来

实验:查看容器默认能力 vs 特权容器能力

复制代码
docker run --rm alpine:3.19 grep Cap /proc/self/status
docker run --rm --privileged alpine:3.19 grep Cap /proc/self/status

逐段解释:

  • grep Cap /proc/self/status:从进程状态文件里过滤能力字段。

  • 输出里 CapEff 表示当前生效的能力位图(十六进制)。

  • 对比两条命令的 CapEff,特权容器的值明显更大(本环境特权容器是 000001ffffffffff,即全部能力)。

可能踩坑CapEff 是十六进制位图,人眼看不出含义。可以用 capsh --decode=000001ffffffffff 解码(需安装 libcap2-bin)。

5.2 seccomp 与 AppArmor

  • seccomp :过滤容器能调用的系统调用。Docker 默认启用一个白名单,阻止如 mountreboot 等危险调用。

  • AppArmor / SELinux:强制访问控制,进一步限制容器能碰的文件。

--privileged关闭这些限制。


六、动手实验:感受隔离边界

以下命令都在远端执行。

1)容器共享宿主机内核

复制代码
docker run --rm alpine:3.19 uname -a

回显里的内核版本与宿主机 uname -a 完全一致,这就是"共享内核"的铁证。

2)容器默认看不到宿主机进程

复制代码
docker run --rm alpine:3.19 ps -ef

通常只看到容器内自己的几个进程(因为 PID namespace 隔离)。对比在宿主机执行 ps -ef,进程数量完全不同。

3)容器默认有独立网络

复制代码
docker run --rm alpine:3.19 ip addr

会看到容器自己的 eth0,IP 一般是 172.17.0.x(Docker 默认网桥网段)。宿主机上则有一个 docker0 网桥(本环境是 172.17.0.1)。

4)容器内是 root,但通常逃不出去

复制代码
docker run --rm alpine:3.19 id

输出 uid=0(root)------注意,这个 root 只是容器 namespace 内的 root ,默认情况下它没有权限动宿主机的核心资源。只有配置了特权/挂载等,才会真正危险。


七、防御要点(本篇版)

  1. 永远不要用 --privileged,除非你完全清楚在做什么。

  2. 不要把 docker.sock 挂进容器;必须用就通过受限代理(如 docker-socket-proxy)只开放必要 API。

  3. 容器以非 root 用户 运行:docker run --user 1000:1000 ...

  4. 按需授予能力,避免 --cap-add=SYS_ADMIN--cap-drop=ALL --cap-add=NET_BIND_SERVICE

  5. 不要 --pid=host / --network=host,除非确有需要。

  6. 启用 seccomp、AppArmor(Docker 默认已开,别关)。

  7. trivy / grype 扫描镜像漏洞,用 hadolint 检查 Dockerfile。

  8. 宿主机及时打内核补丁。


八、本篇小结

  1. 容器 = 共享内核 + namespace 隔离 + cgroup 限额 + 联合文件系统。

  2. namespace 管"看不见",cgroup 管"用多少",UnionFS 管"文件从哪来"。

  3. Docker 调用链:docker → dockerd → containerd → runc → 容器

  4. 危险配置是逃逸的根本原因:--privilegeddocker.sock-v /:/host--pid=host

  5. 本环境是 cgroup v2,部分老逃逸手法失效。

相关推荐
小小的木头人12 小时前
Docker 环境下迁移 containerd 数据目录:从 `/var/lib/containerd` 到 `/data/containerd`
linux·docker
Mr小林17 小时前
Docker 安装教程(在线 + 离线)
运维·docker·容器
java_logo17 小时前
Docker 部署 Milvus:轻松搭建高性能向量数据库平台
数据库·docker·私有化部署·milvus·向量数据库·rag·轩辕镜像
宁之明起(B服)18 小时前
struct2(vulhub靶场)指纹识别+沙箱绕过+反弹shell+webshell
web安全
三84420 小时前
Java 模板注入(FreeMarker)· 04 · 利用与绕过:payload 全集
java·web安全·freemarker·payload
wdfk_prog21 小时前
VMware Ubuntu 虚拟机从 124 GB 压缩到 33 GB
运维·缓存·docker·容器
三84421 小时前
云安全· 01 · 云计算与云安全基础
网络安全·云计算·云安全
闲云野鹤在人间1 天前
Docker入门|第3章 镜像详解
linux·网络·docker·容器·centos·云计算·php
典典分享指南1 天前
AI 配图的排版规范
macos·docker·github·vim·visual studio