当生产环境的Docker容器出现可疑进程或异常外联时,安全团队需要在不中断业务的前提下完成事件溯源。Docker的隔离机制虽然削弱了传统主机攻防的可见性,但守护进程、存储驱动与内核审计模块依然留下了充足的线索。把不同平面的数据拼接起来,就能还原出从镜像构建到容器运行的完整攻击链。

守护进程与事件日志的取证价值
Docker守护进程默认会通过docker.events向本地UNIX套接字广播所有生命周期事件,包括container.create、container.start、image.pull等。这些事件带有精确到纳秒的时间戳与 actor 信息,是确认“谁在什么时候启动了什么容器”的第一手资料。很多团队只配置了集中式日志收集却忽略了事件流,导致事后无法判断恶意容器是由CI流水线还是人工SSH会话拉起。
在宿主机上可以通过 docker events 命令回放指定时间窗口的记录。下面的示例过滤了某次入侵时间段内的所有容器执行事件,输出中包含镜像ID与发起用户的身份信息,便于初步圈定可疑操作。
docker events --since '2023-05-12T02:00:00' --until '2023-05-12T03:00:00' --filter 'event=start' --filter 'type=container'
除了实时事件,还应定期把 /var/lib/docker 下的 buildkit 元数据与 daemon.json 审计配置归档。若开启了 debug 模式,守护进程会记录更详细的插件加载与网络调用,不过这会带来性能开销,建议仅在排查期临时打开。将事件日志与后续文件系统变更关联,就能排除误报并锁定真实入口。
overlay2存储层与镜像digest的比对方法
Docker默认使用overlay2存储驱动,每个容器在 /var/lib/docker/overlay2 下拥有独立的上层可读写目录。安全事件发生后,直接对容器对应的merged目录做哈希比对,可发现被植入的提权脚本或挖矿程序。由于容器层叠特性,攻击者若仅修改了上层,底层镜像的digest保持不变,这反而方便我们确认原始镜像是否可信。
镜像溯源的关键是基于内容寻址的digest。通过 docker images --digests 列出的 sha256 值,能够与镜像仓库中的构建记录交叉验证。以下代码演示如何导出当前节点全部镜像的仓库名、标签与digest,用于事后审计是否与准入清单一致。
docker images --format '{{.Repository}}:{{.Tag}} {{.Digest}}' |
grep -v '<none>' > image_inventory.txt
若发现某业务容器运行的digest在仓库中查无构建记录,基本可判定为本地篡改或使用了来路不明的私建镜像。此时应结合 buildkit 的 BUILDKIT_PRODUCED_IMAGE_DESCRIPTORS 输出,回溯CI中的Dockerfile每一层指令。将各层diff_id与本地overlay2的lowerdir逐一对比,能精确定位是在 RUN curl 还是 COPY 阶段引入了风险文件,从而划分开发、运维或供应链责任。
借助auditd与容器运行时钩子补全系统调用链
仅靠Docker自身日志难以覆盖容器内的进程派生与文件读取,因为容器共享宿主机内核。部署 auditd 并添加针对 /var/lib/docker 与容器进程族的规则,可以捕获 execve、connect 等底层系统调用。如下规则监控所有对overlay2目录的写操作,帮助发现逃逸尝试。
auditctl -w /var/lib/docker/overlay2 -p wa -k docker_write ausearch -k docker_write --start recent
另一种思路是利用OCI运行时钩子,在容器创建前后注入轻量采集 agent。通过在 config.json 的 hooks 字段挂载 eBPF 程序,无需修改业务镜像即可观测容器内 syscall。相比纯日志方案,这种方法能还原攻击者执行的完整命令行参数,例如某次容器逃逸中利用 mount -t cgroup 释放权能的具体动作。
把 auditd 的 raw 日志、Docker事件与存储层比对结果统一汇入时序数据库,就能用一条 trace_id 串联起“拉取恶意镜像、启动容器、修改宿主机文件”的全过程。在真实溯源中,我们发现超过七成的事故由复用老旧base镜像引起,因此在责任定位后应立即在准入控制中阻断对应digest并通知镜像所有者重新构建。只有把平面日志转化为关联视图,Docker安全事件溯源才真正具备闭环能力。