分析 Docker 容器取证
何时使用
- 在调查被入侵的 Docker 容器或容器主机时
- 在分析从镜像仓库拉取的恶意 Docker 镜像时
- 在涉及容器化应用漏洞的事件响应过程中
- 在检查容器逃逸尝试或权限提升时
- 在审计容器配置并识别错误配置时
前提条件
- 在取证工作站上拥有 Docker CLI 访问权限
- 对 Docker 主机文件系统(取证镜像或实时系统)的访问权限
- 了解 Docker 分层文件系统(overlay2、aufs)
- 安装 dive、docker-explorer 或 container-diff 用于镜像分析
- 了解 Docker 守护进程配置和套接字安全
- 安装 Trivy 或 Grype 用于容器镜像漏洞扫描
工作流程
步骤 1:保存容器状态和证据
# 列出所有容器(包括已停止的)
docker ps -a --no-trunc > /cases/case-2024-001/docker/container_list.txt
# 检查受入侵的容器
CONTAINER_ID="abc123def456"
docker inspect $CONTAINER_ID > /cases/case-2024-001/docker/container_inspect.json
# 将容器文件系统导出为 tarball(保存当前状态)
docker export $CONTAINER_ID > /cases/case-2024-001/docker/container_export.tar
# 从容器当前状态创建镜像
docker commit $CONTAINER_ID forensic-evidence:case-2024-001
docker save forensic-evidence:case-2024-001 > /cases/case-2024-001/docker/container_image.tar
# 捕获容器日志
docker logs $CONTAINER_ID --timestamps > /cases/case-2024-001/docker/container_logs.txt 2>&1
# 捕获运行中的进程(如果容器仍在运行)
docker top $CONTAINER_ID > /cases/case-2024-001/docker/container_processes.txt
# 捕获网络连接
docker exec $CONTAINER_ID netstat -tlnp 2>/dev/null > /cases/case-2024-001/docker/container_network.txt
# 从容器中复制特定文件
docker cp $CONTAINER_ID:/var/log/ /cases/case-2024-001/docker/container_var_log/
docker cp $CONTAINER_ID:/tmp/ /cases/case-2024-001/docker/container_tmp/
docker cp $CONTAINER_ID:/etc/passwd /cases/case-2024-001/docker/container_passwd
# 对所有导出的证据进行哈希处理
sha256sum /cases/case-2024-001/docker/*.tar > /cases/case-2024-001/docker/evidence_hashes.txt
步骤 2:分析容器镜像层
# 安装 dive 用于镜像层分析
wget https://github.com/wagoodman/dive/releases/latest/download/dive_linux_amd64.deb
sudo dpkg -i dive_linux_amd64.deb
# 交互式分析镜像层
dive forensic-evidence:case-2024-001
# 非交互式层分析
dive forensic-evidence:case-2024-001 --ci --json /cases/case-2024-001/docker/dive_analysis.json
# 提取并检查各个层
mkdir -p /cases/case-2024-001/docker/layers/
tar -xf /cases/case-2024-001/docker/container_image.tar -C /cases/case-2024-001/docker/layers/
# 列出镜像清单和层顺序
cat /cases/case-2024-001/docker/layers/manifest.json | python3 -m json.tool
# 检查每个层的更改
for layer in /cases/case-2024-001/docker/layers/*/layer.tar; do
echo "=== 层: $(dirname $layer | xargs basename) ==="
tar -tf "$layer" | head -20
echo "..."
done
# 使用 container-diff 与原始基础镜像进行比较
# 安装 container-diff
curl -LO https://storage.googleapis.com/container-diff/latest/container-diff-linux-amd64
chmod +x container-diff-linux-amd64
# 比较已提交镜像和原始镜像
./container-diff-linux-amd64 diff daemon://nginx:latest daemon://forensic-evidence:case-2024-001 \
--type=file --type=apt --type=history --json \
> /cases/case-2024-001/docker/container_diff.json
步骤 3:检查 Docker 主机工件
# Docker 数据目录(默认:/var/lib/docker/)
DOCKER_ROOT="/mnt/evidence/var/lib/docker"
# 检查 overlay2 文件系统层
ls -la $DOCKER_ROOT/overlay2/
# 查找容器的合并文件系统
CONTAINER_HASH=$(docker inspect $CONTAINER_ID --format '{{.GraphDriver.Data.MergedDir}}' 2>/dev/null)
# 或从取证镜像手动查找:
# 查看 /var/lib/docker/containers/<container_id>/config.v2.json
# 分析容器配置文件
cat $DOCKER_ROOT/containers/$CONTAINER_ID/config.v2.json | python3 -m json.tool \
> /cases/case-2024-001/docker/container_config.json
# 检查 Docker 守护进程配置
cat /mnt/evidence/etc/docker/daemon.json 2>/dev/null > /cases/case-2024-001/docker/daemon_config.json
# 检查 Docker 事件日志
cat $DOCKER_ROOT/containers/$CONTAINER_ID/*.log > /cases/case-2024-001/docker/container_json_logs.txt
# 检查卷挂载(潜在的主机文件系统访问)
python3 << 'PYEOF'
import json
with open('/cases/case-2024-001/docker/container_inspect.json') as f:
data = json.load(f)
inspect = data[0] if isinstance(data, list) else data
print("=== 容器安全分析 ===\n")
# 检查挂载
print("卷挂载:")
for mount in inspect.get('Mounts', []):
rw = "读写" if mount.get('RW') else "只读"
print(f" {mount.get('Source', 'N/A')} -> {mount.get('Destination', 'N/A')} ({rw})")
if mount.get('Source') in ('/', '/etc', '/var', '/root') and mount.get('RW'):
print(f" 警告:敏感主机路径以读写模式挂载!")
# 检查特权模式
host_config = inspect.get('HostConfig', {})
if host_config.get('Privileged'):
print("\n警告:容器以特权模式运行!")
# 检查能力
cap_add = host_config.get('CapAdd', [])
if cap_add:
print(f"\n添加的能力: {cap_add}")
dangerous_caps = ['SYS_ADMIN', 'SYS_PTRACE', 'NET_ADMIN', 'SYS_MODULE']
for cap in cap_add:
if cap in dangerous_caps:
print(f" 警告:危险能力: {cap}")
# 检查 PID 命名空间
if host_config.get('PidMode') == 'host':
print("\n警告:容器共享主机 PID 命名空间!")
# 检查网络模式
if host_config.get('NetworkMode') == 'host':
print("\n警告:容器共享主机网络命名空间!")
# 检查用户
user = inspect.get('Config', {}).get('User', 'root (默认)')
print(f"\n以用户身份运行: {user}")
# 检查环境变量中的秘密
env_vars = inspect.get('Config', {}).get('Env', [])
print(f"\n环境变量数量: {len(env_vars)}")
for env in env_vars:
key = env.split('=')[0]
if any(s in key.upper() for s in ['PASSWORD', 'SECRET', 'KEY', 'TOKEN', 'CREDENTIAL']):
print(f" 敏感: {key}=***已编辑***")
PYEOF
步骤 4:分析容器文件系统更改
# 将容器文件系统与原始镜像比较
docker diff $CONTAINER_ID > /cases/case-2024-001/docker/filesystem_changes.txt
# A = 添加,C = 更改,D = 删除
# 分析更改
python3 << 'PYEOF'
added = []
changed = []
deleted = []
with open('/cases/case-2024-001/docker/filesystem_changes.txt') as f:
for line in f:
line = line.strip()
if line.startswith('A '):
added.append(line[2:])
elif line.startswith('C '):
changed.append(line[2:])
elif line.startswith('D '):
deleted.append(line[2:])
print(f"添加的文件数: {len(added)}")
print(f"更改的文件数: {len(changed)}")
print(f"删除的文件数: {len(deleted)}")
# 标记可疑添加
suspicious = [f for f in added if any(s in f for s in
['/tmp/', '/dev/shm/', '/root/', '.sh', '.py', '.elf', 'reverse', 'shell', 'backdoor'])]
if suspicious:
print(f"\n可疑添加的文件:")
for f in suspicious:
print(f" {f}")
# 标记可疑更改
sus_changed = [f for f in changed if any(s in f for s in
['/etc/passwd', '/etc/shadow', '/etc/crontab', '/etc/ssh', '.bashrc'])]
if sus_changed:
print(f"\n可疑更改的文件:")
for f in sus_changed:
print(f" {f}")
PYEOF
# 解压并检查容器导出
mkdir -p /cases/case-2024-001/docker/container_fs/
tar -xf /cases/case-2024-001/docker/container_export.tar -C /cases/case-2024-001/docker/container_fs/
# 扫描 webshells 和恶意文件
find /cases/case-2024-001/docker/container_fs/tmp/ -type f -exec file {} \;
find /cases/case-2024-001/docker/container_fs/ -name "*.php" -newer /cases/case-2024-001/docker/container_fs/etc/hostname
步骤 5:扫描漏洞并生成报告
# 扫描镜像中已知漏洞
trivy image forensic-evidence:case-2024-001 \
--format json \
--output /cases/case-2024-001/docker/vulnerability_scan.json
# 扫描导出的文件系统
trivy fs /cases/case-2024-001/docker/container_fs/ \
--format table \
--output /cases/case-2024-001/docker/fs_vulnerabilities.txt
# 检查镜像中的秘密
trivy image forensic-evidence:case-2024-001 \
--scanners secret \
--format json \
--output /cases/case-2024-001/docker/secrets_scan.json
关键概念
| 概念 | 描述 |
|---|---|
| 镜像层 | 堆叠起来构成容器镜像的只读文件系统层 |
| overlay2 | 默认 Docker 存储驱动,使用联合文件系统实现层 |
| 容器差异 | 运行时文件系统更改与原始镜像的比较 |
| 特权模式 | 容器拥有完整主机能力(绕过大多数隔离) |
| Docker 套接字 | 控制 Docker 守护进程的 Unix 套接字(/var/run/docker.sock) |
| 容器逃逸 | 突破容器隔离到达主机的技术 |
| 卷挂载 | 在容器内部可访问的主机文件系统路径 |
| 镜像历史 | 用于构建每一层的 Dockerfile 指令记录 |
工具与系统
| 工具 | 目的 |
|---|---|
| docker inspect | 详细的容器配置和状态信息 |
| docker diff | 显示运行/停止容器中的文件系统更改 |
| dive | 交互式 Docker 镜像层分析工具 |
| container-diff | Google 工具,用于比较容器镜像内容 |
| Trivy | 容器镜像和文件系统的漏洞扫描器 |
| docker-explorer | 离线 Docker 工件分析取证工具 |
| Sysdig | 容器运行时安全监控和取证 |
| Falco | 容器和 Kubernetes 的运行时威胁检测 |
常见场景
场景 1:Web 应用容器入侵 导出容器文件系统,在 Web 根目录中识别 webshell,分析访问日志寻找利用尝试,检查添加的文件和修改的配置,检查网络连接以发现 C2 通信,审查容器能力以发现提权路径。
场景 2:通过恶意镜像进行供应链攻击 使用 dive 分析镜像层以识别哪一层添加了恶意内容,使用 container-diff 与官方基础镜像比较,检查镜像历史中可疑的 RUN 命令,扫描嵌入的后门和加密货币挖矿程序,从镜像仓库日志中追踪镜像拉取行为。
场景 3:容器逃逸调查 检查容器是否以特权模式或具有危险能力运行,检查主机文件系统挂载点以发现未经授权的访问,审查 Docker 套接字挂载(可能被滥用为 Docker-in-Docker),分析主机系统日志以发现容器逃逸指标,检查内核漏洞利用痕迹。
场景 4:容器环境中的加密劫持 识别高 CPU 使用的容器,导出并分析容器镜像中的挖矿二进制文件,检查镜像仓库中未经授权的镜像,审查容器创建事件以发现恶意部署,检查网络连接以发现矿池通信。
输出格式
Docker 容器取证摘要:
容器:abc123def456(nginx-app)
镜像:company/web-app:v2.1
状态:运行中(启动时间:2024-01-10 09:00 UTC)
主机:docker-host-01.corp.local
安全配置:
特权模式:否
添加的能力:NET_ADMIN(警告)
卷挂载:/var/log -> /host-logs(读写)
网络模式:bridge
用户:root(警告)
文件系统更改:
添加:23 个文件(5 个可疑)
更改:12 个文件(2 个可疑)
删除:0 个文件
可疑发现:
/tmp/reverse.sh - 反向 shell 脚本(已添加)
/var/www/html/.hidden/shell.php - PHP webshell(已添加)
/etc/crontab - 已修改(已添加持久化 cron 条目)
/root/.ssh/authorized_keys - 已修改(已添加未授权密钥)
漏洞扫描:
严重:3(CVE-2024-xxxx 在基础镜像中)
高:12
中:34
证据位置:/cases/case-2024-001/docker/