目录
十、ServiceAccount、Token 与 RBAC:权限如何沿节点扩散
六、案例三:特权启动参数带来的磁盘可见性
第三个案例改变的是容器启动权限,而不是 daemon 的访问路径。实验使用特权模式启动容器,再比较普通模式和特权模式下的 capability 值。原稿记录的观察是:普通容器的能力集合与特权容器不同,特权模式下能够看到原本应由隔离机制隐藏的设备和磁盘信息。

图 72:特权容器、磁盘识别和 mount 挂载验证

图 73:特权容器、磁盘识别和 mount 挂载验证

图 74:特权容器、磁盘识别和 mount 挂载验证

图 75:特权容器、磁盘识别和 mount 挂载验证

图 76:特权容器、磁盘识别和 mount 挂载验证

图 77:特权容器、磁盘识别和 mount 挂载验证
特权模式实验先做能力差异对照,再做设备挂载。普通模式下,容器无法直接看到宿主机块设备;特权模式下,能力集合被大幅放宽,fdisk、mount 等工具能够观察或使用设备。原稿一度忘记具体检查命令,随后回到 capability 输出重新确认,这说明命令记忆不可靠时应依赖系统输出,而不是凭印象继续推导。
块设备名称没有被写成固定前提。实验先用 df -h 找到根文件系统对应的设备,再决定是否挂载 /dev/sda 或其他实际设备。设备名写错会导致 mount 失败,但失败并不代表特权模式没有风险;它只说明当前选择的对象不匹配。正确顺序是先确认设备,再确认挂载点,最后检查挂载点中的宿主机文件。
为了确认挂载对象,实验先用 df -h 观察当前系统的磁盘布局,再选择对应的块设备。由于不同环境的设备名称可能不是同一个,原稿没有把 /dev/sda1 当作固定答案,而是强调先看实际输出。随后在容器内使用 mount 将宿主机磁盘挂入容器目录。磁盘挂载成功后,宿主机根目录自然包含在该文件系统中,写入任务计划、公钥或其他文件都不再依赖单独的根目录挂载。

图 78:特权容器、磁盘识别和 mount 挂载验证
讲解者认为,这一案例在现实环境中的前提非常苛刻,因为正常部署不应给业务容器增加特权参数。也正因此,特权逃逸更像是“高风险配置导致的直接后果”,而不是默认 Docker 行为。课程把它与前两个案例并列,是为了让排查者建立分类:一类是接口暴露,一类是危险挂载,一类是启动能力过高。
七、前三类逃逸的现实性与配置取舍
在前三个案例完成后,原稿没有直接把它们描述成高概率漏洞,而是回到成功条件。2375 需要 daemon 监听外部地址且缺乏认证;docker.sock 需要宿主机主动把套接字挂进业务容器;特权模式需要管理员在启动时明确给出高权限。缺少其中任一条件,演示链路就无法按原样成立。课程据此给出较低的总体成功率判断,并指出真正遇到这些问题时,常见根因往往是内网隔离不足、运维疏忽或“内网环境可以放宽限制”的错误假设。

图 79:逃逸成功率、版本条件和云原生靶场说明

图 80:逃逸成功率、版本条件和云原生靶场说明

图 81:逃逸成功率、版本条件和云原生靶场说明

图 82:逃逸成功率、版本条件和云原生靶场说明
原稿还提到若干真实环境中的观察:某些校园网或机场内网没有做好网络分区,使得拥有普通网络入口的人员可以继续访问内部服务。在这种背景下,原本被认为“只在内网开放”的 2375 端口就不再是局部风险。安全控制与运维便利之间存在张力,限制接口可能增加工作成本,但长期拖延修复会把局部配置问题变成大范围暴露。
经验判断:安全控制不是单纯追求“最严格”,而是要明确哪些管理接口只能本机访问、哪些容器绝不能挂载宿主机资源,并让网络隔离、认证和最小权限同时成立。
八、案例四:旧版 runC 的运行时设计缺陷
第四类逃逸来自运行时软件缺陷。原稿先区分了两个版本问题:非常早期的 Docker 版本中存在的旧问题,现实环境已经很少满足;而 runC 相关漏洞在满足版本条件时仍有复现价值。课程引用的条件是 Docker 18.09.2 之前、使用低于 1.0 的 runC 版本,攻击者可能借助容器操作改写宿主机上的 runC 二进制。
这里的关键推理是组件位置。runC 不是只存在于容器文件系统中的普通命令,而是安装在宿主机上、由 containerd 调用的低级运行时。上层 daemon 下发创建或执行请求,containerd 调度任务,最终由宿主机上的 runC 完成。若运行时二进制被替换,后续任何会触发运行时的 Docker 操作都可能执行被植入的逻辑。

图 83:Kubernetes 控制平面、tcd 与节点关系示意

图 84:Kubernetes 控制平面、tcd 与节点关系示意

图 85:Kubernetes 控制平面、tcd 与节点关系示意

图 86:Kubernetes 控制平面、tcd 与节点关系示意

图 87:Kubernetes 控制平面、tcd 与节点关系示意

图 88:Kubernetes 控制平面、tcd 与节点关系示意

图 89:Kubernetes 控制平面、tcd 与节点关系示意

图 90:Kubernetes 控制平面、tcd 与节点关系示意
旧版 runC 的复盘重点在于“谁在宿主机上执行”。Docker daemon 和 containerd 负责管理,但最终启动容器的二进制位于宿主机。漏洞利用因此不是在容器内部单纯执行一条命令,而是要影响运行时文件,再等待下一次正常容器操作触发恶意逻辑。
课程没有把旧版本条件简化为“Docker 低版本都有漏洞”。复现前至少要核对发行版打包的实际二进制版本、Docker daemon 报告的运行时版本以及靶场脚本假设的版本。只看 Docker 主版本而忽略发行版补丁,可能会把已修复环境误判为脆弱环境。
靶场安装失败的过程也被完整保留。安装脚本要求 Ubuntu,网络恢复后仍需要加入软件源并刷新索引;当前机器上已经存在另一个 Docker 版本,脚本提示先卸载,但快照恢复后的状态与提示不一致。随后下载过程卡在镜像源,最终返回没有候选版本。由于运行时版本没有真正切换,课程只能讲清原理,不能把脚本输出当作成功证据。
原稿把该漏洞称为“被动触发”:攻击者先把恶意 runC 放到宿主机上,真正的执行发生在后续的 docker ps 或其他容器操作中。命令仍然沿着 client、daemon、containerd、runC 的原有链路走,但最底层的可执行文件已被替换,因此它会以宿主机运行时权限执行额外动作。这个过程与前三类不同,它不依赖 2375 监听或 docker.sock 挂载,而依赖组件版本和写入运行时文件的能力。

图 91:ServiceAccount、Token 与 RBAC 权限验证

图 92:ServiceAccount、Token 与 RBAC 权限验证

图 93:ServiceAccount、Token 与 RBAC 权限验证

图 94:ServiceAccount、Token 与 RBAC 权限验证

图 95:ServiceAccount、Token 与 RBAC 权限验证

图 96:ServiceAccount、Token 与 RBAC 权限验证
课程随后尝试使用云原生攻防靶场复现该问题。靶场的价值在于把不同 Docker 版本和对应漏洞集中到一个环境中,避免反复寻找历史镜像。安装过程遇到网络无法访问 Docker 源、快照恢复后网络配置丢失、依赖版本不匹配等问题。脚本提示需要卸载当前 Docker,但实验环境的状态与提示不完全一致,最终未能顺利完成目标版本部署。

图 97:ServiceAccount、Token 与 RBAC 权限验证

图 98:ServiceAccount、Token 与 RBAC 权限验证

图 99:ServiceAccount、Token 与 RBAC 权限验证

图 100:ServiceAccount、Token 与 RBAC 权限验证
由于版本环境没有准备完成,原稿没有把 runC 案例写成“已成功弹回”。讲解者明确说明自己曾经在其他环境复现过,但本次演示因版本和网络问题中止。这个失败结果本身具有复盘价值:漏洞原理可以理解,利用脚本也可以阅读,但没有满足版本、运行时和网络条件,就不能声称实验已经验证。
九、Kubernetes 架构:从控制平面到工作节点
在 Docker 案例之后,课程切换到 Kubernetes。讲解先用“控制平面负责决策、节点负责执行”建立最小模型。控制平面以前常被称为 master,工作节点称为 worker 或 node。控制平面通过 API Server 接收和下发指令,节点上的 kubelet 接收这些指令并管理本机 Pod。Pod 是 Kubernetes 的最小调度单位,里面运行一个或多个容器,节点则是一台真实服务器。

图 101:节点逃逸后读取凭据及组件过度授权分析

图 102:节点逃逸后读取凭据及组件过度授权分析

图 103:节点逃逸后读取凭据及组件过度授权分析

图 104:节点逃逸后读取凭据及组件过度授权分析

图 105:节点逃逸后读取凭据及组件过度授权分析
控制平面中的 etcd 被反复强调,因为它保存集群状态和大量敏感对象。原稿列举了 Node、Pod、Deployment、Service、Secret、ConfigMap、RBAC 等对象,并指出 Secret、证书、服务账号相关记录尤其敏感。若 etcd 被未授权读写,攻击者拿到的不是单个容器,而是集群的状态存储和权限依据,后续可能影响多个命名空间与节点。

图 106:节点逃逸后读取凭据及组件过度授权分析

图 107:节点逃逸后读取凭据及组件过度授权分析

图 108:节点逃逸后读取凭据及组件过度授权分析

图 109:节点逃逸后读取凭据及组件过度授权分析

图 110:节点逃逸后读取凭据及组件过度授权分析
Kubernetes 的控制链还包括调度器和 Controller Manager。调度器根据节点资源、已有 Pod 数量和规则决定新 Pod 放在哪个 node;Controller Manager 持续比较期望状态与实际状态,例如 Deployment 要求三个 Nginx Pod,而当前只有两个时,它会补齐缺失的 Pod。原稿用“货运清单”和“自动补货”作类比,实质上是在解释声明式控制与自愈。

图 111:节点逃逸后读取凭据及组件过度授权分析

图 112:节点逃逸后读取凭据及组件过度授权分析

图 113:节点逃逸后读取凭据及组件过度授权分析

图 114:节点逃逸后读取凭据及组件过度授权分析

图 115:节点逃逸后读取凭据及组件过度授权分析

图 116:调度器、控制器和节点状态管理示意

图 117:调度器、控制器和节点状态管理示意

图 118:调度器、控制器和节点状态管理示意
网络组件负责让节点上的 Pod 能够被访问。原稿没有深入展开网络插件实现,而是保留了一个直接判断:如果容器能够逃逸到 node,攻击者面对的就不再是单个 Pod,而是该节点上的运行时、配置文件和其他 Pod。由此,Docker 逃逸和 Kubernetes 权限链被连接起来。
十、ServiceAccount、Token 与 RBAC:权限如何沿节点扩散
课程把 ServiceAccount 区分为两类身份:真实用户使用的 user 身份,以及 Pod 内进程使用的 ServiceAccount 身份。Pod 通过挂载的 Token 向 API Server 证明“自己是谁”,API Server 再依据 RBAC 规则决定允许读取、创建、修改或删除哪些资源。默认 ServiceAccount 通常权限很低,拿到默认 Token 并不等于控制集群。

图 119: unC 靶场安装、网络失败与复现准备

图 120: unC 靶场安装、网络失败与复现准备

图 121: unC 靶场安装、网络失败与复现准备

图 122: unC 靶场安装、网络失败与复现准备

图 123: unC 靶场安装、网络失败与复现准备

图 124: unC 靶场安装、网络失败与复现准备

图 125: unC 靶场安装、网络失败与复现准备
原稿重点比较了 Kubernetes 1.24 前后 Token 的生成方式。早期版本会为 ServiceAccount 自动生成长期有效的 Secret,并把相关内容存储在 etcd;1.24 以后改用 TokenRequest API 生成短期、可轮换的 Token,Pod 仍然能够读取当前挂载目录中的 Token、证书和命名空间文件,但 Token 不再以同样方式长期存在。这个变化降低了长期凭证风险,却没有消除“容器内进程可以读取当前身份凭证”的事实。

图 126: unC 靶场安装、网络失败与复现准备

图 127: unC 靶场安装、网络失败与复现准备

图 128:云原生攻防工具、 unC 原理和脚本检查

图 129:云原生攻防工具、 unC 原理和脚本检查

图 130:云原生攻防工具、 unC 原理和脚本检查
为了说明 RBAC 的影响,课程使用 Prometheus 作为例子。若 Prometheus 的 ServiceAccount 绑定的是只读 view 角色,那么拿到 Token 后可以查询一部分资源,但不能删除对象;若某个监控、日志、网络或 CI/CD 组件被错误绑定到 cluster-admin,同样的 Token 就可能拥有跨命名空间读写能力。风险不在 Token 这个字符串本身,而在 Token 代表的身份与绑定的角色。

图 131:云原生攻防工具、 unC 原理和脚本检查

图 132:云原生攻防工具、 unC 原理和脚本检查

图 133:云原生攻防工具、 unC 原理和脚本检查

图 134:云原生攻防工具、 unC 原理和脚本检查

图 135:云原生攻防工具、 unC 原理和脚本检查
原稿随后提出一个逐层扩散的推理:若某个 Pod 先被拿下,再通过容器逃逸取得 node 的 root 权限,就可以读取该 node 上其他 Pod 的挂载目录。每个 Pod 的 Token 路径由独立 UID 区分,读取范围首先受限于该 node;如果其中某个组件使用了高权限 ServiceAccount,攻击者就可能从单节点权限继续调用 API Server,扩大到集群对象。

图 136:云原生攻防工具、 unC 原理和脚本检查

图 137:云原生攻防工具、 unC 原理和脚本检查

图 138:Kubernetes 配置、组件功能和弹性伸缩说明

图 139:Kubernetes 配置、组件功能和弹性伸缩说明

图 140:Kubernetes 配置、组件功能和弹性伸缩说明
讲解者对“任何 Pod 都可能拿到 cluster-admin”的说法保持怀疑。普通业务 Pod 通常不会需要最高权限,真正更容易出现过度授权的是监控、日志、网络插件和 CI/CD 等集群组件,因为它们需要跨命名空间创建或读取资源。这个判断保留了原稿中的谨慎态度:高权限绑定确实存在,但不能把一个可能性直接写成所有集群的默认状态。

图 141:Kubernetes 配置、组件功能和弹性伸缩说明

图 142:Kubernetes 配置、组件功能和弹性伸缩说明
如果逃逸发生在控制平面节点,风险会进一步上升,因为该节点可能直接存放 etcd 数据或控制面凭证。不过原稿同时指出,普通 Pod 通常不会被调度到控制平面节点,因此这条路径的成立仍然依赖具体集群的调度和污点配置。即使不读 Token,node root 也可能接触 kubeconfig、容器运行时套接字和本机容器数据,风险边界需要逐项确认。

图 143:Kubernetes 配置、组件功能和弹性伸缩说明

图 144:Kubernetes 配置、组件功能和弹性伸缩说明

图 145:Kubernetes 配置、组件功能和弹性伸缩说明

图 146:Kubernetes 配置、组件功能和弹性伸缩说明
十一、ConfigMap、Secret 与其他组件的区别
课程最后补充了 ConfigMap。ConfigMap 是以键值形式保存非机密配置的 API 对象,用于把环境变量或配置文件与 Pod 解耦。原稿对它的评价较为克制:其中可能包含端口、线程池等运行参数,但通常不等同于数据库密码、云 AK/SK 或 TLS 私钥,因此其敏感度低于 Secret。这个区分避免了把所有 Kubernetes 对象都当成同等级别的凭证。

图 147:课程资料、风险归类和面试复盘要点

图 148:课程资料、风险归类和面试复盘要点

图 149:课程资料、风险归类和面试复盘要点

图 150:课程资料、风险归类和面试复盘要点
Secret 的影响则更直接。原稿指出,Secret 中可能保存数据库连接信息、云平台密钥、证书或其他访问凭据;在未启用额外保护时,内容可能只是 Base64 编码,编码不等于加密。拿到这些值后,攻击者可能直接访问数据库或云资源,不必再继续依赖容器逃逸。
十二、工具使用、失败复盘与验证边界
课程使用官方 Docker 文档、云原生攻防靶场和已有复现脚本进行辅助。官方文档用于确认架构和参数;靶场用于准备特定版本;脚本用于检查接口、危险挂载和 runC 条件。原稿对工具也有明确评价:工具能够降低环境搭建成本,但不能替代对原理、版本、权限和实际输出的判断。
多个步骤出现了失败或无回显:镜像拉取受网络影响,代理配置需要反复调整;Docker 重启因配置冲突失败;任务计划文件写入后没有立即触发;runC 靶场因依赖版本和源不可达而未完成。每个失败都被保留为条件判断,而不是用“最终成功”概括。只有在看到文件、端口、进程、挂载或 API 返回值后,结论才被推进一步。
调试方法:先确认当前环境和权限,再确认输入是否真正到达目标组件,随后检查组件返回的状态,最后把“写入成功、服务重启成功、命令执行成功、权限链成立”分别验证。任何一步缺少证据,都只能写成待确认。
十二、补充排查记录:如何避免把现象写成结论
原稿中的多个暂停点说明,云安全实验最容易出错的地方不是命令本身,而是把不同层级的证据混在一起。例如,看到 .dockerenv 只能说明当前文件系统呈现出容器特征;看到少量进程只能说明当前 PID 命名空间可见范围较小;看到 docker.sock 只能说明套接字存在。只有当这些现象与容器 ID、挂载点、daemon 返回值和宿主机文件差异相互印证时,才可以把判断推进到“当前环境确实位于容器内”。
同样的原则适用于接口暴露。端口扫描看到 2375 处于监听状态,不等于已经能够创建容器;还需要确认 API 是否接受请求、daemon 是否有权限执行、测试容器是否真的启动以及挂载是否出现在新容器中。原稿先遇到查询无结果,再发现容器已经停止,正说明端口可达、API 可用和目标容器运行是三个不同状态。复盘文章必须分别记录,否则读者无法知道链路断在哪一步。

Docker 配置的失败也有层次。写入配置文件失败可能是目录不存在、用户权限不足或 JSON 结构错误;服务重启失败可能是字段冲突、systemd override 语法或其他启动条件;服务启动但端口不通则要回到监听地址、防火墙和网络路径。原稿中对 host 配置冲突的修正,就是从“文件已写入”继续追踪到“服务实际读取并接受配置”。
宿主机写入任务计划时,路径选择和触发机制不能合并。容器里看到的 /etc 可能是镜像自己的 /etc,也可能是宿主机根目录挂载后的 /etc;.dockerenv 是否存在、目录内容是否与宿主机一致,可以帮助确认当前视图。文件写到宿主机后,还要确认任务计划守护进程读取的是哪一个文件、权限是否符合要求、解释器是否存在,以及日志是否出现执行记录。原稿没有把这些步骤压成“写入后反弹成功”,而是保留了未触发的结果。
Kubernetes 凭据排查同样需要逐层缩小范围。读取一个 Pod 的 Token,只能证明该 Pod 的进程身份被取得;调用 API 返回列表,只能证明该身份拥有相应读取权限;如果删除请求被拒绝,则说明 RBAC 仍在生效。只有发现某个 ServiceAccount 被绑定到宽泛角色,才可以继续推断可能跨命名空间操作。这个过程把“凭据泄露”和“集群沦陷”明确区分开。
原稿对工具的态度也属于复盘结论的一部分。云原生靶场把多个历史版本放在一起,能节省准备时间,但它不能替实验者完成版本核对;自动化脚本能够创建容器、检测接口或写入文件,但脚本输出仍需要用手工命令和日志验证;官方文档可以解释架构和参数,但文档中的默认值不一定与当前发行版打包结果一致。工具适合辅助证据收集,不能替代证据本身。
十二点五、从实验现象到技术结论
原稿中的多个暂停点说明,云安全实验最容易出错的地方不是命令本身,而是把不同层级的证据混在一起。看到 .dockerenv 只能说明当前文件系统呈现出容器特征;看到少量进程只能说明当前 PID 命名空间可见范围较小;看到 docker.sock 只能说明套接字存在。只有当这些现象与容器 ID、挂载点、daemon 返回值和宿主机文件差异相互印证时,才可以把判断推进到“当前环境确实位于容器内”。
端口扫描看到 2375 处于监听状态,也不等于已经能够创建容器。还需要确认 API 是否接受请求、daemon 是否有权限执行、测试容器是否真的启动以及挂载是否出现在新容器中。原稿先遇到查询无结果,再发现容器已经停止,正说明端口可达、API 可用和目标容器运行是三个不同状态。复盘文章分别记录这些状态,读者才能知道链路断在哪一步。
Docker 配置的失败也有层次。写入配置文件失败可能是目录不存在、用户权限不足或 JSON 结构错误;服务重启失败可能是字段冲突、systemd override 语法或其他启动条件;服务启动但端口不通则要回到监听地址、防火墙和网络路径。原稿中对 host 配置冲突的修正,就是从“文件已写入”继续追踪到“服务实际读取并接受配置”。
宿主机写入任务计划时,路径选择和触发机制不能合并。容器里看到的 /etc 可能是镜像自己的 /etc,也可能是宿主机根目录挂载后的 /etc;.dockerenv 是否存在、目录内容是否与宿主机一致,可以帮助确认当前视图。文件写到宿主机后,还要确认任务计划守护进程读取的是哪一个文件、权限是否符合要求、解释器是否存在,以及日志是否出现执行记录。原稿没有把这些步骤压成“写入后反弹成功”,而是保留了未触发的结果。
Kubernetes 凭据排查同样需要逐层缩小范围。读取一个 Pod 的 Token,只能证明该 Pod 的进程身份被取得;调用 API 返回列表,只能证明该身份拥有相应读取权限;如果删除请求被拒绝,则说明 RBAC 仍在生效。只有发现某个 ServiceAccount 被绑定到宽泛角色,才可以继续推断可能跨命名空间操作。这个过程把“凭据泄露”和“集群沦陷”明确区分开。
工具的作用也需要放回证据链。云原生靶场把多个历史版本放在一起,能节省准备时间,但不能替实验者完成版本核对;自动化脚本能够创建容器、检测接口或写入文件,但输出仍需要用手工命令和日志验证;官方文档可以解释架构和参数,但文档中的默认值不一定与当前发行版打包结果一致。工具适合辅助证据收集,不能替代证据本身。
运行时链路的逐步复原
课程在讨论 Docker 架构时,先从用户实际输入的 docker run 开始,而不是从源码或内核细节开始。客户端收到命令后,将请求送入 daemon;daemon 不直接创建所有资源,而是把容器管理任务交给 containerd;containerd 再根据容器 ID、bundle 目录和运行时路径建立任务,最后调用 runC。这种从可见命令反推内部组件的方式,让初学者能够把终端输出与后台进程对应起来。
当容器正常运行时,状态信息沿着相同的组件链回传。用户执行 docker ps,客户端请求 daemon 查询状态,daemon 向 containerd 获取任务信息,containerd 依据 runC 返回的运行状态整理结果。容器退出后,退出信号从 runC 传给 containerd,再传给 daemon,用户最终看到 Exited。因此,状态字段既是运维信息,也是判断哪一层已经完成动作的线索。
原稿把 containerd 描述成“中间人”并不是说它没有实际作用,而是强调它处在管理和执行之间。它向上提供接口,使 Docker daemon 可以管理容器;向下组织运行时任务,使不同容器的创建、销毁和状态回收能够并发进行。若只记住 Docker daemon 和镜像、容器,会遗漏后续案例中真正承载漏洞条件的运行时组件。
runC 的位置决定了它与普通容器命令的不同。容器内的 ls、ps 或 echo 只在容器的文件系统和命名空间中工作;宿主机上的 runC 则负责为容器准备命名空间、挂载和进程。课程讨论旧版 runC 时,始终把“恶意文件位于宿主机,触发动作来自后续 Docker 命令”作为区分点。
隔离机制的观察方法
namespace 的作用在课程中被拆成多个可观察对象。PID namespace 让容器内看到的进程数量变少;network namespace 让容器只看到自己的接口和地址;mount namespace 让容器只看到自己的文件系统;用户 namespace 则影响身份映射。讲解虽然用“一个新系统”帮助理解,但同时通过宿主机对照提醒:隔离的是可见范围,不是把宿主机物理复制了一份。
cgroup 的讨论集中在资源分配,而非权限提升。所有 Docker 容器最终共享宿主机的 CPU、内存和磁盘,cgroup 用来给每个容器划定使用额度。若一个容器消耗过多,其他容器会受到影响,因此 Docker 需要对资源做切分。课程将它与 network namespace、PID namespace 并列,是为了说明资源边界和观察边界是两套不同机制。
bridge、host 和 none 三种网络模式也对应三种不同的排查起点。bridge 模式下,应检查 docker0、容器网卡和端口映射;host 模式下,应直接审计宿主机端口和进程,因为网络隔离已取消;none 模式下,外部访问失败可能是预期行为,而不是服务故障。原稿没有把三种模式混成一个概念,而是让读者先识别模式再解释现象。
识别容器的证据组合
.dockerenv 是课程演示中最直观的线索。进入 Nginx 容器后,根目录能看到该文件;回到普通服务器,文件消失。这个对照帮助建立初步判断,但原稿随后马上补充进程和命令证据,说明单一标志不足以完成结论。文件可能不存在、被删除或因镜像差异而变化,识别过程应保留不确定性。
进程数量的对照来自同一台环境中的两个视角。容器内只看到少量进程,包含主进程和少数子进程;宿主机即使没有部署很多服务,也能看到更多系统进程。讲解还区分了 Nginx 的 master 和 worker,避免把每一个进程都当成独立服务。进程数量是命名空间隔离的结果,必须结合 PID 关系和命令路径观察。
命令缺失是精简镜像的另一个特征。容器中可能没有 vim、磁盘工具或完整 shell,导致操作直接报“命令不存在”。原稿没有因为命令缺失就停止,而是改用已有的 echo、ps 或文件查看操作继续验证。这个过程体现了排查时应区分工具缺失、权限拒绝和目标对象不存在三种不同失败。
2375 案例的配置推进
守护进程配置检查从“文件是否存在”开始。原稿发现配置文件有时不存在,有时存在但内容为空,于是先创建文件,再加入监听参数。监听参数的目标是让 daemon 在 0.0.0.0:2375 接收请求,之后通过重启服务让配置被读取。配置写入完成只是准备动作,端口检查和 API 请求才是验证动作。
第一次重启失败后,讲解者没有立刻归因于 Docker 本身,而是回到配置语法和字段冲突。由于 host 既可能在默认配置中出现,也可能在 override 中出现,重复定义会造成启动行为异常。删除旧字段并将新设置移动到 containerd 相关的 override 目录后,才重新尝试启动。这个过程展示了“先排参数,再排服务”的顺序。
镜像拉取过程中的网络问题与 daemon 配置问题被分开处理。代理端口调整只是为了让镜像源可访问,不能替代 Docker 服务重启;镜像拉取成功也不能证明 2375 已监听。原稿在一度卡住后选择使用已经存在的镜像继续测试,避免把实验目标变成单纯的网络调试。
Nginx 容器启动后,课程再次进入容器检查。docker run -itd 让容器在后台运行,-p 8080:80 建立宿主机到容器的端口映射,--name nginx-test 便于后续查询。参数逐项出现后,原稿通过 docker ps、进入容器、检查 .dockerenv 和进程列表验证结果,没有把命令返回成功当作容器已经稳定运行。
远端 API 查询的第一次无结果来自容器状态,而不是访问失败。容器停止后,按默认过滤条件的列表接口不会展示它;重新启动后,容器名称、镜像、ID、宿主机端口和容器端口均可见。这个失败分支说明 API 验证需要明确查询范围、容器状态和操作时间。
根目录挂载与任务计划排错
创建带挂载的容器后,实验把宿主机根目录映射到容器中的 host 目录。进入该目录时,文件列表不再包含容器标志文件,而是出现宿主机的目录结构。课程随后用 chroot 或切换目录确认当前视图,目的不是改变系统,而是证明挂载确实指向宿主机根路径。
任务计划写入阶段出现了多个不确定点。原稿先考虑路径是否写错,再检查权限是否为 644,之后才考虑 Ubuntu 的 shell 行为。不同于“写入成功即完成”的简化叙述,文章保留了文件已经出现在宿主机、但任务没有触发的状态。只有这样,读者才能看到攻击链中的实际断点。
把权限改为 600 后仍然没有立即执行,促使讲解回到解释器问题。Ubuntu 可能使用 dash,脚本却按 bash 方式组织;即使任务计划成功调用脚本,解释器不匹配仍会导致失败。改用 sh -c 是下一次尝试,但原稿没有声称它必然解决问题,而是继续要求查看日志。
日志选择也经历了纠正。容器中看到的日志不一定记录宿主机任务计划,挂载根目录后应回到宿主机的日志路径检查。原稿先尝试一个不相关的登录日志,再切换到任务计划相关位置,仍未得到执行结果。这个过程提醒读者,日志排查要先确认组件、主机和时间范围。
docker.sock 案例的验证
套接字挂载实验从两个文件开始:宿主机 Docker 二进制和 Docker 套接字。二进制让容器具备客户端命令,套接字让命令能够到达宿主机 daemon。只挂载二进制时,客户端可能只能访问本地或报连接失败;只挂载套接字时,还需要其他客户端访问方式。原稿把两者并列展示,是为了明确“工具”和“控制入口”的区别。
执行 docker info 后,返回的是宿主机的存储驱动、镜像和容器信息。这个结果比单纯看到套接字文件更有说服力,因为它同时验证了文件权限、连接路径、API 解析和 daemon 响应。随后创建内层容器、改变挂载点并检查 ID,进一步确认了控制面已经跨过外层容器。
内外两层容器的退出过程被作为验证方法保留。第一次退出只离开新创建的内层容器,第二次退出才离开最初容器。若只执行一次 exit 就认为已经回到宿主机,容易把内层 shell 和外层 shell 混淆。原稿通过提示符、容器 ID 和目录内容反复确认当前层级。
特权模式与设备挂载
特权模式实验首先比较 capability 输出。原稿提到普通模式与特权模式的值不同,随后通过设备列表和磁盘信息确认可见性变化。这个顺序避免了“加上 privileged 就一定能挂载任何磁盘”的误解:特权只改变能力边界,设备名称、挂载权限和目标目录仍需逐项确认。
使用 df -h 选择设备是为了避免照抄示例中的 /dev/sda1。不同虚拟机和云服务器可能使用不同块设备,错误设备会导致挂载失败。课程在实验中先观察实际根文件系统,再决定挂载对象;这个细节体现了技术复盘不能把环境相关值写成通用固定值。
磁盘挂载成功后,任务计划和公钥写入都可以在文件系统中进行,但原稿没有再次扩展为新的攻击步骤,而是把它归为“宿主机根目录已经可见”的验证结果。特权模式、设备可见和根目录挂载是连续的三步,任何一步失败都不能直接进入下一步。
runC 案例的版本与失败
runC 漏洞案例先讲版本,再讲原理。原稿提到 Docker 18.09.2 之前以及 runC 小于 1.0 的条件,并指出现代 Docker 已经远高于这个版本。讲解者因此区分“历史上存在的漏洞”和“今天仍可能遇到的现实配置”,避免把版本过低的案例写成当前 Docker 的普遍问题。
靶场安装过程中,Ubuntu 作为推荐环境;安装脚本需要访问 Docker 软件源,快照恢复后网络一度不可用,恢复连接后又遇到源和候选版本问题。脚本还询问是否卸载当前 Docker,但环境中是否真的安装过目标版本并不清晰。最终安装没有完成,因此后续只保留工具说明、漏洞条件和原理解释。
原稿还提到某些复现项目提供链接和文档,另一个项目缺少完整说明。讲解者建议在没有链接时查找现成 payload,或使用绿盟提供的构建和利用工具;但同时指出工具的检查逻辑并不总是完善。成稿因此把这些内容写成工具选择和评价,不把没有完成的脚本执行写成成功案例。
Kubernetes 的组件和状态
控制平面、node、Pod 三者的层级关系是后半段的基础。控制平面下发指令,node 是承载 Pod 的真实服务器,Pod 是调度单位,容器运行在 Pod 内。一个 node 可以运行多个 Pod,一个 Pod 通常承载一个主要业务容器。这个模型解释了为什么从容器逃逸到 node 后,风险范围会扩大到同一节点上的其他工作负载。
etcd 存储集群状态,API Server 接收控制请求,调度器决定 Pod 放置位置,Controller Manager 负责状态收敛,kubelet 在 node 上执行。原稿还提到网络组件负责让 Pod 能被外部访问。每个组件的职责都对应一个风险面:API Server 是入口,etcd 是状态库,调度器决定落点,控制器决定副本,kubelet 负责节点执行。
Deployment 的三个副本示例体现了自愈机制。用户声明需要三个 Nginx Pod,Controller Manager 持续对比期望和实际数量;一个 Pod 退出后,实际数量变成两个,控制器创建新的 Pod。这个自动补齐动作不是人工脚本,而是控制器持续工作产生的结果。
弹性伸缩在原稿中分为水平、垂直和节点伸缩。水平伸缩改变 Pod 数量,垂直伸缩改变容器资源请求与限制,节点伸缩则根据整个集群是否还有可用 node 决定是否增加节点。课程用 CPU 负载、容器资源不足和所有节点已满三个例子区分它们,避免把所有自动扩容都称为同一种机制。
ServiceAccount 和 RBAC 的权限链
Pod 内的 ServiceAccount 通过挂载文件获得身份。Token、证书和命名空间文件通常在容器目录中出现,Web 进程如果可以读取它们,就可以向 API Server 发出以该身份签名的请求。原稿强调,这只证明当前 Pod 的身份被拿到,真正权限要由 RBAC 规则决定。
Prometheus 的例子展示了只读角色的边界。绑定 view 后,Token 可以列出或读取允许范围内的对象,但删除操作失败。这个“能读、不能删”的结果比“接口有响应”更能说明 RBAC 生效。若组件被绑定到 cluster-admin,权限范围则可能跨越命名空间,但这种绑定需要从具体配置中验证。
容器逃逸到 node 后读取 Token 的范围首先受 node 限制。一个 node 上多个 Pod 的运行目录可能包含各自的 Token,root 可以遍历这些目录;其他 node 的 Token 不会因为当前 node root 自动出现。课程把高权限监控、日志、网络和 CI/CD 组件作为现实中更值得审计的对象,因为它们确实可能需要跨命名空间权限。
控制面节点路径需要额外前提。普通业务 Pod 通常不应被安排到控制面节点,但如果调度规则、污点或容忍度允许,逃逸后的权限可能接触控制面文件。原稿对此保持谨慎,没有把它写成默认场景,而是要求检查具体集群的调度配置。
复盘方法的最终落点
整场课程虽然展示了多个漏洞名称,但讲解者最终把它们归并为几类边界失效:管理接口暴露、宿主机套接字挂载、启动能力过高、运行时组件缺陷,以及 Kubernetes 身份和权限过宽。归类后,排查对象从“所有漏洞列表”变成监听地址、挂载清单、启动参数、运行时版本和 RBAC 绑定。
面试表达也沿用同一顺序:先说架构,再说入口,再说权限,最后说验证结果。若被问到 Kubernetes,应能说清 API Server、etcd、调度器、Controller Manager、kubelet 和 Pod 的作用;若被问到容器逃逸,应能说清 2375、docker.sock、特权和 runC 四条路径的前置条件与不同根因。
原稿最后提醒,资料中的漏洞数量可能达到二三十个,但其中一些只是不同名字下的重复危险挂载或重复接口问题。学习重点不是把每一个编号背下来,而是理解它们如何破坏隔离、如何获得宿主机视图、如何从 node 权限继续读取身份凭证,以及哪些步骤只是理论可能、哪些步骤已经在授权环境中观察到。
本次 runC 实验未能在当前环境完成,是复盘中必须保留的事实。之前环境中曾经使用过类似复现,但这次因为版本、网络和 Docker 包状态未满足条件,不能把历史成功代替当前证据。技术文章若明确写出“原理已解释、当前复现未完成”,比声称所有案例都成功更符合真实排查过程。
十四、交付前的内容边界
本文只整理附件中的授课内容,未把课堂中的口头指令扩展成面向未知目标的操作指南。所有接口、挂载、任务计划、Token 和 RBAC 内容都保留在授权实验、靶场或技术教学语境中。图片均来自附件并按正文引用顺序放置,图片标题采用重新组织后的技术说明。
文章没有把原稿中未完成的 runC 部署改写为成功,也没有把讲师对某些高权限绑定的怀疑改写成确定事实。对于 etcd、ServiceAccount、Secret、ConfigMap 和 Token 的描述,均沿用原稿出现的对象、版本和权限条件。
十五、结语
从 Docker 的 namespace、网络和 cgroup,到 daemon、containerd、runC 的运行时链路,再到 Kubernetes 的控制平面、节点和 ServiceAccount,整场复盘始终围绕同一问题展开:隔离边界由哪些组件维护,哪些配置或版本会让边界失效,以及每一步如何用实际输出验证。把这些判断过程保留下来,才是这次授课对技术复盘最有价值的部分。
十六、交付自检前的范围核对
正文中的章节顺序与原稿一致:先解释 Docker 基础和运行时,再处理三个配置型逃逸,随后讨论 runC,转入 Kubernetes 架构、etcd、ServiceAccount、RBAC 和 ConfigMap,最后保留工具评价、面试建议与未完成复现。图片引用没有集中移动到文末,而是跟随对应案例步骤出现。
十七、复盘文章的证据观
原稿提供的最重要方法不是某个命令,而是证据观。看到监听端口要继续确认 API,看到接口返回要继续确认容器状态,看到挂载目录要继续确认文件归属,看到 Token 要继续确认 RBAC,看到漏洞编号要继续确认版本。每个结论都由下一次观察推动,任何没有观察支撑的部分都应明确写成假设或待验证。
十八、按原讲解顺序补充的实验记录
课程开场并没有直接进入命令操作,而是先把早间渗透案例中的“未授权访问”重新放回云安全语境。原稿先提出一个问题:拿到 WebShell 后如果发现自己位于 Docker 容器,如何判断能否到达宿主机。这个问题被拆成三个连续动作:先确认容器身份,再确认隔离机制,再观察是否存在破坏隔离的接口或配置。文章保留这个开场顺序,是因为后面每个案例都在回答其中一个动作。
讲解者先说明容器逃逸并不是单纯的“进入另一个目录”。容器中的进程、网络、用户和挂载点都可能与宿主机隔离,只有当某个能力或管理通道突破边界时,才可以把容器内的操作作用到宿主机。这个区分贯穿四个案例:接口泄露让请求到达宿主机 daemon,套接字挂载让本地命令到达宿主机 daemon,特权模式让容器看到宿主机设备,runC 缺陷让宿主机运行时被替换。
在解释 Docker 架构时,原稿专门指出官方图“画得简略”。这不是对官方资料的否定,而是提醒学习者知道图的抽象层级。入门图只要说明 client、daemon、image 和 container 的关系即可;进行逃逸分析时,还要补上 containerd 和 runC。复盘文章把两种图放在同一条叙述中,先建立简图,再解释为什么简图不足以支撑漏洞分析。
Docker 客户端暴露了 docker ps、docker rm、docker run、docker pull 等管理命令,但这些命令的执行对象仍是 daemon。课程用这一点解释远程 API 的危险:当 2375 开放时,远端主体并不是“模拟输入终端”,而是在直接调用宿主机管理面。权限边界因此从容器内进程提升到了宿主机 Docker 的管理权限。
容器运行时链路中的线程和进程数量并非固定值。原稿曾用“三个线程”作示意,随后补充实际数量可能是一个、两个或多个,取决于实现和任务。复盘中保留这个限定,避免把示意图中的数量误写成协议要求。重要的是分层关系和请求方向,而不是某一次环境中显示的具体进程数。
网络模式的解释还连接到容器识别。bridge 模式下,容器通常看到 172.17 一类地址和自己的网卡;host 模式下,容器可能直接看到宿主机网络;none 模式下,许多网络检查结果为空。若没有先确定模式,看到某个 IP 或网卡时就无法判断它属于容器还是宿主机。
安装 Docker 的章节虽然基础,却为后续版本问题埋下了条件。通过发行版包安装时,containerd 和 runC 会作为依赖被一起安装;通过官网脚本安装时,版本可能不同。原稿在 docker info 中观察到这些组件,正是为了说明 Docker 架构图中未展示的运行时并非额外外挂,而是实际安装的一部分。
镜像源和代理的失败还影响了实验节奏。原稿在无法访问 get.docker 时考虑网络原因,随后尝试代理;代理仍不通时选择暂时跳过。后面部署 runC 靶场再次遇到类似的源问题,说明网络条件是贯穿整堂课的环境限制。成稿将这些失败分散放在对应案例中,而不是在文末统一归因,保持原始推进顺序。
进入 Nginx 容器后,课程通过“普通服务器对照”来增强判断。普通服务器没有 .dockerenv,能够看到更多系统进程,命令也更完整;容器中相反。对照实验比单独查看一个环境更有说服力,因为它把差异具体化为文件存在、进程数量和工具集合。
原稿还注意到 Nginx 的 master 和 worker 进程会让进程计数看起来比预期多。分析时必须区分主进程、worker 和容器管理进程,不能看到三个 PID 就直接断言有三个独立服务。这个细节体现了“先理解进程关系,再使用数量作为证据”的排查习惯。
在 2375 案例中,容器列表接口返回的信息比容器内 ps 丰富,包括镜像、镜像 ID、容器名、宿主机端口和容器端口。原稿通过这些字段确认远程调用已经进入 daemon 的管理面。若只返回空列表,仍需区分没有容器、容器已退出、接口过滤和访问失败四种情况。
根目录挂载后,原稿反复寻找 .dockerenv 作为边界标志。外层容器有该文件,挂载后的宿主机根目录没有该文件;这一次差异用于说明当前目录已经切换到宿主机视图。这个验证没有依赖反弹 shell,因为文件系统视图本身已经证明了挂载效果。
任务计划的脚本内容在原稿中涉及 shell、网络连接和 root 权限,但讲解重点是写入位置与宿主机关系。由于任务计划写入的是宿主机文件,后续即使在容器外看到 shell,也应归因于宿主机任务计划被触发,而不是容器自身拥有了宿主机进程。文章保留这种因果关系,不把不同阶段混成一次命令执行。
第二个案例中,套接字和二进制文件挂载的位置被分别检查。宿主机二进制挂入容器后,容器内才出现 docker 命令;宿主机套接字挂入后,命令才能找到 daemon。原稿用 ls 查看挂载目录,再用 docker info 验证连接,先验证文件、后验证能力。
内层容器创建后,宿主机根目录被挂入测试目录。课程比较两个容器的 ID,确认它们不是同一个实例;随后检查目录内容,确认挂载来源是宿主机。这个过程说明“容器里创建容器”只是现象,真正形成逃逸效果的是对宿主机 daemon 的控制和危险挂载。
特权模式案例中,原稿提到普通模式和特权模式的 capability 数值不同,并尝试通过 fdisk 与 mount 查看设备。即使某个工具在镜像中不存在,也不能把工具缺失误解成设备不可见;应该更换观察命令或通过宿主机对照确认。课程在此处多次停顿,正好说明实验环境差异会影响演示。
前三类案例完成后,讲解者对成功率作了个人判断,认为现实中更可能是内网疏忽和错误的安全假设,而非默认配置。原稿举出校园网和机场内网的观察,指出“内网可达”并不等于“可信”。文章保留这一经验评价,但没有把它扩展成普遍统计结论。
安全与便利的冲突也被纳入复盘。开发和运维人员希望镜像源、管理端口和跨节点操作更方便,安全限制会增加工作量,因此修复可能被拖延。课程通过密码、代理和内网暴露的例子说明便利的短期收益可能换来更大的长期风险,但没有把这些类比写成新的技术事实。
runC 章节中,讲解者首先把早期 Docker 1.0 前后的漏洞与较新的 runC 问题分开。旧问题因为版本过低,现代环境很难遇到;runC 问题虽然也有严格版本条件,但在满足条件的历史环境中有真实复现价值。这样的版本分层让读者知道哪些内容应作为历史背景,哪些内容值得在靶场中继续验证。
靶场工具的三个功能在原稿中被概括为信息收集、利用和修复。信息收集用于查看容器中的逃逸条件,利用模块根据条件执行对应检查或动作,修复模块用于处理风险。课程当前选择利用模块,是因为已知目标漏洞;但由于环境安装未完成,最终没有把利用结果写成成功。
有些工具模块有详细文档,有些只有名称或脚本。讲解者认为没有文档的模块更难直接使用,需要回到源码、参数和实际输出。这个评价属于工具使用经验,文章保留为学习建议:当自动化工具没有解释原理时,应先理解手工链路,再判断脚本是否真的对应当前环境。
Kubernetes 章节从集群而不是单机开始,因为“集群”意味着至少有控制平面和节点。控制平面可以有多个实例,节点也可以有多个;控制平面负责决策和状态,节点负责执行。原稿用 master、worker 等旧称帮助理解,随后逐步替换为 control plane 和 node 的职责说明。
API Server 是控制面唯一的主要入口,etcd 保存集群状态,Controller Manager 维持资源数量,调度器决定 Pod 的落点,kubelet 在 node 上执行。讲解者要求面试时不能只说组件名称,还要说出每个组件“做什么”。文章将每个名称都放在对应操作和风险中,避免形成名词列表。
etcd 的敏感性来自对象范围。原稿列出 Node、Pod、Deployment、Service、Secret、ConfigMap 和 RBAC 等对象,说明它不仅存应用配置,也存权限和集群状态。若 etcd 被直接读写,影响就可能超出单个工作负载;但课程同时承认 etcd 未授权并不容易遇到,因此把高危后果与入口概率分开。
Secret 与证书的讨论强调“拿到凭证后可以绕过后续容器步骤”。如果数据库连接密码、云 AK/SK 或 TLS 私钥已经泄露,继续进行容器逃逸并不是必要条件。这个观点让风险评估从“是否能反弹 shell”扩大到“是否已经获得业务或云资源的直接凭证”。
ServiceAccount 的默认权限很低,是原稿中反复强调的限制。读取默认 Token 后,API Server 可能只允许查看有限资源;真正值得重点关注的是被绑定到 view、更高角色或跨命名空间角色的组件身份。文章把“拿到 Token”和“控制集群”分成两个段落,保持这种条件关系。
Token 的存储位置在课程中出现了前后讨论。容器内有挂载目录,node 上也可能存在按 Pod UID 区分的短期文件。讲解者通过询问和确认来修正自己的记忆,最终保留“容器内可读、node 上可能短期落盘、长期 Secret 的行为在 1.24 前后不同”的结论。文章不把不确定的具体路径写成绝对事实。
从 node root 读取多个 Pod Token 的推理也经过范围收缩。最初问题是能否读取所有 Pod,后来明确为“该 node 上、确实挂载 Token 的 Pod”。如果某个 Token 对应默认 ServiceAccount,扩散能力有限;如果对应高权限组件,才可能继续调用 API Server。这个修正过程是原稿中重要的思维转折。
控制面节点的风险讨论还涉及 kubeconfig 和运行时 socket。即使没有直接读取 etcd,node root 也可能读取本机控制面凭证或管理本机容器。课程没有把这些对象自动等同为集群管理员权限,而是要求继续检查文件内容、凭证作用域和 API 返回结果。
ConfigMap 的评价相对克制。它保存非机密配置,可能包括端口和线程池等参数,通常不包含数据库密码和云密钥。原稿认为这类信息有辅助价值但优先级较低,因此文章将 ConfigMap 放在 Secret 之后,并明确两者的敏感度不同。
Kubernetes 弹性伸缩的三个例子还体现了声明式系统的特点。水平伸缩改变 Pod 副本数,垂直伸缩改变单个容器资源,节点伸缩改变可承载 Pod 的服务器数量。课程通过负载过高、容器资源不足和所有节点满载的现象分别说明三者,避免把“系统自动处理”视为单一动作。
面试建议并不是脱离技术的额外主题。原稿要求回答者至少能讲架构、普通 Docker 逃逸形式和 Kubernetes 风险点,因为面试官可能从组件问到权限,再问到逃逸链。文章保留这一建议,并把它放到技术结论后,确保读者先理解内容再准备表达。
课程最后承认时间有限,未把所有云安全资料全部讲完,后续内容需要自行阅读。这个限制不能被成稿隐藏,因为它解释了为什么文章重点集中在四类逃逸、Kubernetes 架构和凭据风险,而没有凭空增加其他案例。
十九、顺序与完整性复核
文章已按原稿从 Docker 基础到容器识别、三类配置型逃逸、runC、Kubernetes 架构、凭据与 RBAC、工具和面试建议的顺序组织。每个案例都保留了背景、判断、操作、结果、失败原因和下一步推理;未完成的 runC 靶场仍标记为未完成。
图片引用保持与正文步骤相邻,并保留原稿中重复出现的图片引用。图标题说明对应架构、命令、输出、目录、进程或验证结果,不把图片集中到文末。
二十一、案例之间的共同推理框架
四类 Docker 逃逸虽然入口不同,但可以用同一套问题框架复盘。第一步是确认当前主体是否在容器内;第二步是找出容器能够访问的管理面、设备或文件;第三步是验证这个入口是否真的能改变宿主机状态;第四步是确认权限范围和持续性。2375 在第二步表现为网络 API,docker.sock 表现为 Unix 套接字,特权模式表现为设备访问,runC 表现为宿主机运行时文件。
这个框架也解释了为什么原稿没有把“发现接口”直接写成“完成逃逸”。接口暴露只回答入口存在,必须继续验证 daemon 是否接受请求;套接字文件只回答通信路径存在,必须继续验证 docker info;特权参数只回答 capability 可能扩大,必须继续验证设备与挂载;旧版 runC 只回答版本可能受影响,必须继续验证运行时是否被替换。
入口、权限与结果的分层
入口是攻击链的起点,权限决定入口能做什么,结果则是实际观察到的宿主机影响。以 2375 为例,端口监听是入口,daemon 运行身份和 API 权限是中间层,创建挂载容器并看到宿主机根目录才是结果。以 ServiceAccount 为例,Token 文件是入口,RBAC 绑定是中间层,API Server 接受创建或删除请求才是结果。
原稿中的失败大多发生在三层之间。任务计划文件存在但没有触发,说明入口和写入权限成立,调度执行层失败;runC 工具下载失败,说明利用脚本存在,但环境准备层失败;API 列表无结果后发现容器停止,说明入口可达,但目标状态层不满足。将失败定位到层级,比简单记录“命令没成功”更有复盘价值。
证据优先级
直接输出通常比口头推断更可靠。.dockerenv、ps、docker info、docker ps、df -h、挂载目录和日志都属于原稿中实际查看过的证据。讲解者多次在记忆不确定时回到屏幕输出,说明技术复盘应优先引用可重复观察的结果,而不是依赖“以前遇到过”或“应该是这样”的经验。
对于没有回显的操作,文章只记录已知状态。例如任务计划文件已经出现在宿主机目录,但日志没有显示执行结果,因此结论停留在“写入成功,触发未确认”;靶场安装没有候选版本,因此结论停留在“版本复现未完成”;某个高权限绑定是否常见,讲解者明确表示怀疑,因此文章保留为待核实判断。
组件图与实际命令的对应
Docker 架构图中的 client 对应终端输入,daemon 对应管理进程,containerd 对应生命周期管理,runC 对应底层容器任务。Kubernetes 图中的 API Server 对应控制请求入口,etcd 对应状态存储,调度器对应 Pod 落点,Controller Manager 对应期望状态,kubelet 对应 node 执行。把图中名词映射到实际动作,能避免面试回答只剩下组件背诵。
从 Docker 到 Kubernetes 的自然过渡
课程从 Docker 转入 Kubernetes 并不是换了主题,而是扩大了容器边界问题。Docker 案例回答“容器能否触达宿主机”;Kubernetes 案例继续追问“如果拿到 node,能否读取其他 Pod 的身份,能否调用 API Server,能否接触 etcd 中的状态”。因此,node、Pod、ServiceAccount 和 RBAC 是 Docker 逃逸后续风险的延伸。
Kubernetes 的自愈和伸缩功能也会影响排查。容器退出后 Controller Manager 可能补出新 Pod,Pod 位置由调度器决定,Service 可能继续把流量转到新的 Pod。若只观察单次进程或容器 ID,可能把自动重建误判为攻击者动作。原稿通过 Deployment 三副本示例说明,状态变化需要结合控制器行为理解。
etcd、Secret 与 ConfigMap 的区别
etcd 是状态数据库,Secret 和 ConfigMap 是其中保存的对象类型。Secret 可能包含凭证,ConfigMap 主要保存非机密配置,RBAC 对象描述权限关系。三者在风险链中位置不同:etcd 失守影响存储本身,Secret 泄露直接暴露敏感值,ConfigMap 泄露更多是辅助理解环境。文章按原稿顺序分别讨论,而没有把它们都称为“密码文件”。
原稿对 Secret 的强调来自后果而非名称。数据库密码、云 AK/SK、TLS 私钥或 ServiceAccount 相关凭证一旦可用,攻击者可能直接访问业务或云资源;是否需要继续容器逃逸,要看已拿到的权限。反过来,ConfigMap 中的端口和线程池参数通常不能直接完成认证,因此优先级相对低。
Token 的时间和空间范围
Token 的时间范围由版本和轮换方式决定。1.24 以前可能长期存在,1.24 以后通常短期有效并自动更新。Token 的空间范围由挂载的 Pod 和 ServiceAccount 决定,读取一个 Pod 的文件不等于读取所有 Pod。node root 扩大的是同一节点的文件访问范围,API Server 和 RBAC 决定的是对象访问范围。
这两个范围必须同时考虑。一个短期 Token 如果绑定高权限,短时间内仍然危险;一个长期 Token 如果只拥有默认低权限,影响可能有限。原稿通过 Prometheus 的 view 角色和高权限组件的对比,说明凭证寿命与权限范围是两个独立维度。
现实配置与理论漏洞
讲解者多次区分理论存在和现实可遇到。高权限模式、外部 2375、套接字挂载都需要人为配置;旧版 runC 需要历史版本;控制面 Pod 需要特殊调度条件;高权限 ServiceAccount 需要过度授权。文章保留这些限定,避免把所有理论链路都写成默认风险。
同时,原稿也没有因为前提苛刻就忽略风险。校园网和机场内网的案例说明,内网隔离不足会让“只在内部开放”的接口变得可达;监控和 CI/CD 组件的过度授权说明,业务需要可能推动权限扩大;旧版本环境虽然少见,但仍可能存在于遗留系统和靶场。
面试建议中的准确性
面试表达需要同时做到完整和克制。完整是能说出控制平面组件、四类逃逸和 Token/RBAC 关系;克制是说明版本条件、权限前提和本次实验的失败结果。讲师建议至少能回答一部分,而不是面对 Kubernetes 或容器逃逸完全无话可说;文章将这条建议放在结论中,不把面试建议混进实验事实。
如果被问到 Docker 逃逸,可以按入口分类:远程 API 未授权、套接字挂载、特权启动和 runC 设计缺陷;如果被问到 Kubernetes,可以按控制面、节点、Pod、Token、RBAC 和 Secret 解释;如果被追问利用条件,则说明需要授权实验、具体版本、实际挂载和权限绑定。这样的回答既覆盖原稿重点,也保留了边界条件。
对工具和文档的使用建议
官方文档适合确认 Docker 的隔离、网络模式和组件定义,靶场适合准备指定历史版本,利用工具适合缩短重复操作。原稿同时强调,工具不能替代理解:工具可能没有文档、检查逻辑可能不完整、网络和依赖可能导致安装失败。阅读官方文档时应围绕当前问题定位章节,而不是盲目从头读完所有内容。
课程中“先手工理解,再使用工具”的顺序也值得保留。2375 和 docker.sock 先手工观察接口和挂载,再看工具如何自动化;runC 先理解宿主机运行时替换,再尝试靶场脚本;Kubernetes 先理解 Token 和 RBAC,再分析工具收集的凭证。这样即使工具没有输出,也能知道应检查哪一层。
未完成内容的表达方式
“未完成”不是一句笼统的失败,而应说明卡在哪里。原稿的 runC 复现卡在 Docker 版本依赖、软件源访问和候选版本;任务计划验证卡在触发日志、权限和 shell;镜像拉取卡在代理与网络。文章把这些条件分别写出,读者可以据此判断下一次实验需要先修复环境,还是先换验证方法。
同样,讲师对某些判断的犹豫也属于有效信息。例如高权限 ServiceAccount 在现实中是否常见、某个 node 是否绑定 admin、具体设备名称是什么,原稿都出现了“需要确认”的停顿。成稿不把这种犹豫删除,因为它体现了安全分析应区分事实、推测和经验。
文章最终要保留的技术主线
从客户端输入到宿主机文件,从容器目录到 node 目录,从 ServiceAccount Token 到 API Server,所有案例都沿着“谁接收请求、谁执行动作、谁返回结果”的方向推进。Docker 组件链解释执行者,隔离机制解释边界,配置和版本解释突破条件,Kubernetes 权限解释后续扩散。
这条主线也解释了文章标题中的“从隔离边界到权限链”。前半段关注容器能否看见宿主机,后半段关注 node 上的身份凭证能否影响集群。两部分并非并列知识点,而是从一次容器入侵继续推演到云平台控制面的完整复盘。
二十二、最终复核说明
正文没有把附件中的课堂口头问答、停顿和重复确认直接搬入成稿,讲师观点则通过引用块、经验判断和失败复盘保留。命令、参数、路径、配置项、协议、文件名和组件名均使用反引号,关键限制、失败原因和最终判断以加粗或段落结论突出。
文章继续保留原稿的授权实验语境,未把 Docker 管理接口、宿主机挂载、任务计划、Token 或 RBAC 操作扩展成针对未知目标的攻击指南。所有案例都被描述为本地、靶场或教学环境中的观察与推理。
对失败分支的再检查
镜像拉取失败、服务重启失败和任务计划无回显看起来属于不同问题,但原稿的排查方式相同:先确认输入是否写入,再确认目标进程是否读取,再确认输出是否返回。镜像源失败时,输入是拉取请求,目标是外部仓库,输出是镜像层;服务重启失败时,输入是配置文件,目标是 daemon,输出是服务状态;任务计划无回显时,输入是宿主机文件,目标是 cron 或相应调度器,输出是日志和进程。
这种“输入、执行者、输出”的三段式判断,也适用于 Kubernetes。Token 文件是输入,API Server 是执行者,RBAC 决定输出是否允许;Deployment 副本数是输入,Controller Manager 是执行者,Pod 数量是输出;调度约束是输入,调度器是执行者,Pod 所在 node 是输出。把对象和执行者对应起来,能减少因组件名称相似而产生的误判。
运行时风险的现实前提
课程对 Docker 逃逸成功率的个人评价较低,原因并不是这些链路不存在,而是每条链路都依赖额外配置。外部 2375 需要错误监听和缺少认证,docker.sock 需要主动挂载,特权模式需要启动参数,runC 需要旧版本和写入条件。现实审计应该先检查这些前提是否成立,再决定是否进行授权验证。
内网并不自动等同于安全边界。原稿提到校园网和机场环境中的网络隔离不足,说明拥有普通网络入口的人员可能继续访问管理端口。这个经验判断并不改变 2375 案例的技术条件,但会改变暴露面的评估:若内网能够被低门槛进入,接口的实际可达性就不能按“只有本机可访问”处理。
权限扩散的现实前提
Kubernetes 的权限扩散同样不是自动发生。容器逃逸取得 node root 后,首先只能读取该 node 的工作负载文件;这些工作负载是否挂载 Token,Token 是否有效,ServiceAccount 是否绑定高权限,API Server 是否允许相应动作,都要分别验证。原稿通过默认 ServiceAccount、Prometheus 的 view 和可能的高权限组件进行对照,说明权限扩散需要一串条件同时成立。
即便某个组件需要跨命名空间权限,也不意味着它一定绑定 cluster-admin。监控、日志、网络和 CI/CD 组件可能有较宽权限,但实际绑定的资源、动词和作用域仍应从 RBAC 对象确认。讲解者对“现实中不少见”的判断表示怀疑,文章保留这种谨慎,避免用工具列表或单个案例代替集群配置证据。
资料阅读与复现顺序
原稿建议先阅读 Kubernetes 组件文档,再阅读云安全资料。这个顺序与案例推进一致:没有控制平面、node、Pod 和 ServiceAccount 的基础,读到 Token、Secret 或逃逸工具时很容易把对象混为一谈。先建立架构,再看攻击面,最后回到具体脚本和版本条件,能够减少盲目执行。
Docker 官方文档虽然篇幅较长,但其中的隔离、网络和运行时说明是后续分析的基础。原稿认为官方文档比二手问答更准确和具体,文章保留这一经验判断,同时不把文档内容扩展成附件之外的新结论。
成稿的重构方式
成稿没有按课堂问答逐句转写,而是把每个案例重构为背景、判断、操作、结果、失败分析和结论。比如 2375 案例先讲 daemon 配置,再讲重启失败,再讲端口验证、容器列表、根目录挂载和任务计划;runC 案例先讲运行时位置和版本,再讲靶场准备、网络问题和未完成结果;Kubernetes 部分先讲架构,再讲 Token、RBAC 和节点范围。
图片也被安排在对应技术步骤之后。架构图跟随隔离和组件说明,命令截图跟随配置与验证,目录截图跟随挂载结果,Kubernetes 图跟随组件和权限说明。重复图片按正文再次引用,不通过复制文件制造新的独立图片。这样既保留附件视觉证据,也避免读者需要在文末来回查找。
最终可复用的排查清单
面对容器疑似逃逸时,先问是否真的处于容器内,再看 .dockerenv、PID、网络、挂载和命令集合;然后查 docker.sock、危险卷、特权参数和 Docker Remote API;若出现 runC 或版本提示,再核对宿主机运行时和 Docker 版本。每一步都记录命令、输出和失败原因。
面对 Kubernetes 凭据风险时,先确认 Pod、node 和控制平面的关系,再查 ServiceAccount 挂载、Token 有效期、RBAC 绑定、Secret 与 ConfigMap 内容;若从 Pod 逃逸到 node,先限定为当前 node 的工作负载,再判断是否存在高权限组件或控制面凭证。这个清单来自原稿的推进顺序,没有新增未出现的工具或结论。
结束性复盘
这场课程的技术价值不在于展示了多少条命令,而在于把看似分散的现象连接起来:容器中少量进程对应 PID 隔离,.dockerenv 对应容器环境,docker.sock 对应宿主机 daemon,根目录挂载对应文件系统边界,Token 对应 Pod 身份,RBAC 对应 API 权限,etcd 对应集群状态。每一个连接都通过实验输出、失败排查或讲师经验得到解释。
过程记录为何比结果口号更重要
如果把前三个案例压缩成“调整配置后成功”,读者就无法知道配置冲突、容器退出、权限不符和 shell 不兼容分别出现在哪里。原稿的价值恰恰在于保留了这些中间状态:配置文件有了但服务没起,端口通了但列表为空,文件写了但任务没触发,套接字在但必须再确认 API。成稿将这些状态拆开,是为了让技术结论可以被复查。
组件边界与权限边界的对应
Docker daemon 是管理边界,containerd 是任务管理边界,runC 是运行时执行边界;API Server 是 Kubernetes 控制入口,etcd 是状态边界,RBAC 是身份权限边界。每个案例都涉及至少一个边界被跨越。通过这种对应关系,读者可以在面对新组件时继续使用同样的分析方法,而不必依赖记忆某个漏洞名称。
版本条件不能被忽略
原稿分别提到 Docker 版本、runC 版本、Kubernetes 1.24 前后 Token 行为,并在靶场安装时反复遭遇依赖版本问题。版本决定组件是否存在、默认配置是否变化、凭证是否长期有效以及脚本是否能够运行。文章因此把版本放在每个相关案例中,而不是在文末用一条“注意版本”带过。
失败输出的技术含义
“没有网络”可能意味着镜像源不可达,也可能意味着快照恢复后代理配置丢失;“没有候选版本”说明软件源和目标版本不匹配;“没有任务执行日志”说明文件写入和调度触发之间仍有断点;“没有容器列表”可能只是容器已经退出。原稿没有把这些输出合并成一个模糊的失败,而是逐项追问原因。
从文件系统观察到控制面观察
前半段实验主要通过文件和进程确认容器边界,后半段实验则通过 API 返回和 RBAC 拒绝确认控制面权限。文件系统中看到宿主机根目录,只能说明逃逸或挂载成立;API 返回允许创建或删除,才能说明身份权限进一步扩大。文章把两类证据分开,避免用宿主机目录可见直接推导集群已被控制。
对“高权限”的限定
特权容器、root、cluster-admin 和 node root 在原稿中都出现过,但它们不是同一个概念。特权模式扩大容器能力,node root 是宿主机文件和进程层面的权限,ServiceAccount 的 cluster-admin 是 Kubernetes API 层面的权限。某一层权限可能帮助获得下一层,但不能自动等同。成稿在每个章节中保持这种区分。
对“逃逸”的限定
课程把四类路径都称为容器逃逸,但其实际落点不同:有的直接看到宿主机根目录,有的控制宿主机 Docker daemon,有的能够访问宿主机磁盘,有的替换宿主机 runC。统一称呼便于归类,具体结果仍要以文件、设备、运行时或 API 的实际证据为准。
对面试复盘的限定
面试建议不是要求背出所有漏洞,而是要求能讲清一条链路。回答 2375 时说清监听和 daemon,回答 docker.sock 时说清套接字和嵌套容器,回答特权模式时说清 capability 和磁盘,回答 runC 时说清版本和被动触发。回答 Kubernetes 时说清组件、Token 和 RBAC。这样的组织方式沿用了原稿的教学重点。
对公开发布的语言控制
公开文章需要把课堂中的“我、你、大家”和连续反问改成客观技术说明,同时保留讲师的经验判断与失败复盘。成稿使用第三人称、条件句和引用块表达观点,不把讲师的口头停顿当成技术结论,也不把课堂中针对靶场的操作说明扩展到未知目标。
交付范围的最后确认
本次交付只包含完整 Markdown 博客和 assets 图片目录。文章主题根据附件内容判断为 Docker 容器逃逸、Kubernetes 架构与云安全权限链;正文含摘要、标题层级、图文引用、原稿顺序、失败尝试、经验判断和交付自检。附件中的图片没有遗漏,重复引用保持在正文中。
再看第一条链路:从 docker pull 到 runC
原稿用 Nginx 镜像拉取过程重新演示了命令下发方向。用户输入 docker pull nginx 后,命令先进入 Docker daemon,再由 containerd 组织任务,最终由 runC 或相关运行时完成容器准备。拉取、创建和启动并不是同一个动作,镜像成功下载不代表容器已经运行,容器列表出现也不代表服务进程仍在。
再看第二条链路:从套接字到内层容器
套接字案例把 Docker 管理面搬进了外层容器。宿主机二进制负责发出命令,套接字负责传递命令,daemon 负责创建内层容器,挂载参数负责改变内层容器的文件视图。任何一个环节没有验证,结论都只能停留在“存在潜在路径”。原稿通过 docker info、容器 ID、目录列表和 .dockerenv 的变化逐步确认。
再看第三条链路:从特权到设备
特权模式实验的关键转折不是启动容器本身,而是容器开始看到宿主机设备。课程先比较 capability,再通过 df -h 选择实际磁盘,最后观察挂载后的目录。设备名称、挂载点和根目录内容都属于环境相关证据,不能直接照抄另一台机器的输出。
再看第四条链路:从 Token 到 API Server
Kubernetes 凭据链也必须逐段验证。容器内读取 Token 证明当前进程可以代表某个 ServiceAccount;API Server 返回允许的资源证明该身份有相应权限;RBAC 绑定决定能否跨命名空间或执行写操作。只有这三段都成立,才可以讨论从单个 Pod 向集群对象扩散。
对原稿判断的完整保留
成稿保留了讲师对官方文档、云原生靶场、利用脚本、真实配置疏忽、网络隔离、过度授权和学习重点的评价,也保留了“当前 runC 演示未完成”“某些高权限绑定需要核实”“ConfigMap 价值相对有限”等判断。观点没有被简化为单一结论,失败也没有被隐藏。
对读者的最终提示
本文适合把一次云安全授课整理为可公开阅读的技术复盘,不替代具体环境的安全评估。阅读时应关注每个案例的前提、证据和失败边界:管理接口是否暴露、套接字是否挂载、容器是否特权、运行时版本是否满足、Token 绑定了什么角色,以及每一步是否在授权环境中完成验证。
读图与读命令的结合
附件中的架构图、终端截图和目录截图承担了不同证据角色。架构图说明组件关系,终端截图说明命令和返回,目录截图说明文件视图,流程图说明控制方向。成稿将图片放在对应段落后,并为每张图写出重新组织的标题,使读者可以把视觉证据与前后的判断连接起来。
原稿顺序带来的理解路径
先讲 Docker 隔离,再讲运行时,再讲逃逸,最后讲 Kubernetes,是一条从底层边界到上层权限的理解路径。如果先讲 Token 或 etcd,读者容易忽略容器为何能接触 node;如果先讲 runC,读者也难以理解它为何处于 Docker 管理链的最底层。文章维持原顺序,保留了这种递进关系。
关于结论强度
本文把已观察到的结果、讲师的经验判断和仍待验证的推测分开表达。已观察内容包括容器标志、进程数量、接口返回、挂载目录和配置错误;经验判断包括现实中配置疏忽、工具便利性和面试重点;待验证内容包括本次 runC 复现、高权限绑定的普遍性和具体环境中的控制面节点条件。
交付文件的可读性
正文使用较短段落和分层标题,命令、参数、路径和组件名采用反引号,关键失败原因和结论采用加粗或引用块。交付自检置于文末,数字仅用于核验,不参与正文的字数统计,也不替代技术内容。
由四类案例得到的排查顺序
当一个容器环境出现异常时,第一问应是“当前看到的系统属于谁”。通过 .dockerenv、进程列表、网络接口、挂载点和命令集合建立初步判断后,第二问才是“当前环境能访问谁”。此时需要查看 Docker 套接字、远程 API、危险卷、特权能力和运行时文件。第三问是“访问能力是否已经转化为控制能力”,需要使用 API 返回、容器 ID、挂载后的文件内容或日志进行确认。
这个顺序能够解释原稿中看似零散的停顿。进入容器后发现命令缺失,说明镜像精简,但还没有回答宿主机是否可见;发现 2375 在监听,说明管理面可能暴露,但还没有回答 API 是否接受创建请求;发现宿主机根目录出现在挂载点,说明文件系统边界被跨越,但还没有回答任务计划或其他持久化动作是否执行;读到 ServiceAccount Token,说明身份被取得,但还没有回答 RBAC 是否允许进一步操作。
失败结果对后续推理的作用
原稿中的失败并不是与主线无关的课堂插曲。Docker 重启失败让排查从网络转向配置冲突,容器列表为空让排查从 API 转向容器生命周期,任务计划没有日志让排查从文件写入转向调度器和 shell,runC 没有候选版本让排查从漏洞原理转向软件源和版本条件。每个失败都改变了下一步问题,因此技术复盘应把失败放在它发生的位置。
公开文章中的安全边界
由于主题包含 Docker 管理接口、宿主机挂载、任务计划和容器逃逸,文章必须持续保留授权边界。原稿的实验对象是本地环境、教学服务器或云原生靶场,成稿不把这些操作改写成面向未知服务器的建议,也不补充附件没有出现的目标发现、隐蔽持久化或绕过防护方法。技术步骤只用于解释原稿中的判断和验证结果。
对读者最有用的结论
读者最终需要掌握的不是某条命令,而是三种能力:能够从 Docker 的隔离模型判断容器与宿主机的边界,能够从 daemon、containerd 和 runC 的链路解释命令为什么会产生某个结果,能够从 Kubernetes 的 node、Pod、ServiceAccount 和 RBAC 关系判断一次容器入侵是否可能继续扩大。若没有足够证据,应明确说出尚未验证,而不是用经验替代结果。
证据链的最小闭环
每个案例至少需要形成一个可复查的证据闭环。先记录输入,例如命令、配置、挂载参数或 Token 路径;再确认执行者,例如 Docker daemon、调度器、Controller Manager 或 API Server;最后记录输出,例如端口响应、容器 ID、目录内容、日志或 RBAC 结果。只有输入、执行者和输出能够对应起来,结论才不会停留在名称相似或经验推测。
在 2375 案例中,配置文件和端口监听只是入口证据,真正的控制能力还要通过 API 请求、容器状态和挂载结果确认;在 docker.sock 案例中,套接字存在只是前提,内层容器是否出现以及文件视图是否变化才是结果;在 Kubernetes 案例中,Token 文件可读只是身份证据,API Server 的允许或拒绝才决定权限边界。这样的闭环也解释了为什么失败输出不能被删掉。
二十、复盘结论与面试表达
整场课程最终归纳了四类容器逃逸:2375 未授权接口、docker.sock 套接字挂载、特权启动参数,以及旧版 runC 运行时缺陷。前三类主要是配置或部署边界被主动放宽,第四类是软件版本和设计缺陷。课程同时提醒,工具列表中还可能出现更多漏洞,但不同名称可能共享同一种危险挂载或接口控制路径,复盘时应按根因归类,而不是机械背诵漏洞数量。
Kubernetes 复盘则至少要能说明控制平面与节点的分工、API Server、etcd、调度器、Controller Manager、kubelet 和 Pod 的作用,并解释水平伸缩、垂直伸缩与节点伸缩的差异。安全面试中还应把容器逃逸与 ServiceAccount、Token、RBAC、Secret 之间的关系说清楚:容器被拿下只是起点,能否继续扩大取决于逃逸后的 node 权限、该 node 上其他 Pod 的身份以及这些身份的实际绑定权限。
最终经验:面对云安全问题,最有价值的回答不是罗列最多名词,而是能按照“入口、组件、权限、验证结果、下一步推理”的顺序说明一条完整链路,同时明确哪些步骤在当前环境中失败、哪些结论仍需授权实验确认。